《剑指offer》------抽象建模/发散思维专题





解法一:基于递归求骰子的点数,时间效率不够高

现在我们考虑如何统计每一个点数出现的次数。要想求出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;
    }
}




基于加减法  或者基于异或运算~



  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值