解法一:基于递归求骰子的点数,时间效率不够高
现在我们考虑如何统计每一个点数出现的次数。要想求出n个骰子的点数和,可以先把n个骰子分为两堆:第一堆只有一个,另一个有n-1个。单独的那一个有可能出现从1到6的点数。我们需要计算从1到6的每一种点数和剩下的n-1个骰子来计算点数和。接下来把剩下的n-1个骰子还是分成两堆,第一堆只有一个,第二堆有n-2个。我们把上一轮哪个单独骰子的点数和这一轮单独骰子的点数相加,再和n-2个骰子来计算点数和。分析到这里,我们不难发现这是一种递归的思路,递归结束的条件就是最后只剩下一个骰子。
解法二:基于循环求骰子的点数,时间性能好
可以换一个思路来解决这个问题,我们可以考虑用两个数组来存储骰子点数的每一个综述出现的次数。在一次循环中,每一个数组中的第n个数字表示骰子和为n出现的次数。在下一轮循环中,我们加上一个新的骰子,此时和为n出现的次数。下一轮中,我们加上一个新的骰子,此时和为n的骰子出现的次数应该等于上一次循环中骰子点数和为n-1,n-2,n-3,n-4,n-5,n-6的次数之和,所以我们把另一个数组的第n个数字设为前一个数组对应的第n-1,n-2,n-3,n-4,n-5,n-6
解法三: 使用动态规划法
1.现在变量有:骰子个数,点数和。当有k个骰子,点数和为n时,出现次数记为f(k,n)。那与k-1个骰子阶段之间的关系是怎样的?
2.当我有k-1个骰子时,再增加一个骰子,这个骰子的点数只可能为1、2、3、4、5或6。那k个骰子得到点数和为n的情况有:
(k-1,n-1):第k个骰子投了点数1
(k-1,n-2):第k个骰子投了点数2
(k-1,n-3):第k个骰子投了点数3
....
(k-1,n-6):第k个骰子投了点数6
在k-1个骰子的基础上,再增加一个骰子出现点数和为n的结果只有这6种情况!
所以: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)
3.有1个骰子,f(1,1)=f(1,2)=f(1,3)=f(1,4)=f(1,5)=f(1,6)=1。
抽象数据结构: 数组 0为大小王 其他依序
import java.util.Arrays;
public class Solution {
public boolean isContinuous(int [] numbers) {
int numOfZero = 0;
int numOfInterval = 0;
int length = numbers.length;
if(length == 0){
return false;
}
//对数组进行排序
Arrays.sort(numbers);
//计算0的数量
for (int i = 0; i < length - 1; i++) {
if (numbers[i] == 0) {
numOfZero++;
continue;
}
// 对子,直接返回
if (numbers[i] == numbers[i + 1]) {
return false;
}
//不是0 也不是对子 则计算差距
numOfInterval += numbers[i + 1] - numbers[i] - 1;
}
//若差距依然可以弥补 则true
if (numOfZero >= numOfInterval) {
return true;
}
return false;
}
}
另一种思路:计算最大值和最小值 根据差值判断
import java.util.Arrays;
/*
max 记录 最大值
min 记录 最小值
min ,max 都不记0
满足条件 1 max - min <5
2 除0外没有重复的数字(牌)
3 数组长度 为5
*/
public class Solution {
public boolean isContinuous(int [] numbers) {
if(numbers==null || numbers.length==0)
return false;
//用数组记录数字
int[]d = new int[14];
d[0] = -5; //记录0的个数(最多也就5个)
int len = numbers.length;
int max = -1;
int min = 14;
for(int i =0;i<len;i++){
d[numbers[i]]++; //给对应数字位置+1
if(numbers[i] == 0){ //很重要!不能忽略~ 因为后文要统计除0之外的最大值和最小值 必须跳过0
continue;
}
if(d[numbers[i]]>1){ //有重复数字了 不止一个
return false;
}
if(numbers[i] >max){ //记录最大值
max = numbers[i];
} if(numbers[i] <min){ //记录最小值
min = numbers[i];
}
}
if(max -min<5){ //若差<5则true
return true;
}
return false;
}
}
时间优化最好的方法:采用bit数组
public class Solution {
public boolean isContinuous(int [] numbers) {
if(numbers.length != 5) return false;
int min = 14;
int max = -1;
int flag = 0;
for(int i = 0; i < numbers.length; i++) {
int number = numbers[i];
if(number < 0 || number > 13) return false;
if(number == 0) continue;
if(((flag >> number) & 1) == 1) return false; //检测是否有重复数字
flag |= (1 << number);
if(number > max) max = number;
if(number < min) min = number;
if(max - min >= 5) return false;
}
return true;
}
}
//太机智了!! if(((flag >> number) & 1) == 1) return false; //检测是否有重复数字
flag |= (1 << number);
方法1 :采用LinkedList模拟循环链表结构
import java.util.LinkedList;
public class Solution {
public int LastRemaining_Solution(int n, int m) {
if(n<1 || m<1)
return -1;
//采用模拟循环链表方式
LinkedList<Integer> list=new LinkedList<Integer>();
for(int i=0;i<n;i++){
list.add(i);
}
//开始进行删除第m个数的操作
int bt=0;
while(list.size()>1){
bt=(bt+m-1)%list.size(); //模拟循环
list.remove(bt);
}
//判断 最终list是否只剩下一个数
return list.size()==1? list.get(0): -1; //直接返回list.get(0)也对~
}
}
方法2 : 依然采用数组的方式 给去掉的元素标记为-1 每次计数时跳过标记的数字即可
public class Solution {
public int LastRemaining_Solution(int n, int m) {
//采用数组模拟环
if(n<1 || m<1)
return -1;
int [] arr=new int[n];
int i=-1; //下标 0--n-1
int step=0; //数是第几个数
int count=n; //数还剩多少个数
while(count>0){
i++; //从0开始
if(i>=n)
i=0; //循环 从头开始
if(arr[i]==-1)
continue; //跳过已经被删除的数字(被标记为-1的数字)
step++; //计步数
if(step==m){
arr[i]=-1; //应该被删除的数字标记为-1
step=0; //重新开始计数
count--; //剩余数字减去1
}
}
return i;
}
}
方法3 :数学的魅力
public class Solution {
public int LastRemaining_Solution(int n, int m) {
if(n<1 || m<1)
return -1;
int last=0; //当数字只有一个时 则数字0即为最后一个剩下的数字
for(int i=2;i<=n;i++){
last=(last+m)%i;
}
return last;
}
}
方法1 : 利用&&的短路特性
/*1.需利用逻辑与的短路特性实现递归终止。
2.设置flag是为了停止递归,当sum值递归到0的时候,&&运算符左边已经不满足true了,直接不进行判断运算符右边,也就是不会在调用递归 */
public class Solution {
public int Sum_Solution(int n) {
int sum = n;
boolean flag = (sum>0)&&((sum+=Sum_Solution(n-1))>0); // --n也可以
return sum;
}
}
方法2 :采用异常退出递归的方法
public class Solution {
public int Sum_Solution(int n) {
return sum(n);
}
public int sum(int n){
try{
int i=1/n; //当n为0时即跳出递归 --捕捉异常
return sum(n-1)+n;
}catch(Exception e){
return 0;
}
}
}
方法3 :使用数学公式n(n+1)/2 -----(n2+n)/2
public class Solution {
public int Sum_Solution(int n) {
int sum=(int)(Math.pow(n,2)+n)>>1;
return sum;
}
}
解题思路:
1 首先看十进制是如何做的: 5+7=12,三步走
第一步:相加各位的值,不算进位,得到2。
第二步:计算进位值,得到10. 如果这一步的进位值为0,那么第一步得到的值就是最终结果。
第三步:重复上述两步,只是相加的值变成上述两步的得到的结果2和10,得到12。
2 计算二进制值相加: 5-101,7-111
第一步:相加各位的值,不算进位,得到010,二进制每位相加就相当于各位做异或操作,101^111。
第二步:计算进位值,得到1010,相当于各位做与操作得到101,再向左移一位得到1010,(101&111)<<1。
第三步 重复上述两步, 各位相加 010^1010=1000,进位值为100=(010&1010)<<1。
继续重复上述两步:1000^100 = 1100,进位值为0,跳出循环,1100为最终结果。
public class Solution {
public int Add(int num1,int num2) {
int sum,jump; //定义异或结果 /进位结果
do{
sum=num1^num2;
jump=(num1&num2)<<1;
num1=sum; //第三步 重复上次两步
num2=jump;
}while(num2!=0);
return sum;
}
}
public class Solution {
public int Add(int num1,int num2) {
while(num2!=0){
int sum=num1^num2;
int jump=(num1&num2)<<1;
num1=sum; //第三步 重复上次两步
num2=jump;
}
return num1;
}
}
public class Solution {
public int Add(int num1,int num2) {
return num2!=0?Add(num1^num2,(num1&num2)<<1):num1;
}
}
基于加减法 或者基于异或运算~