剑指offer :数值类题目汇总

 

15.数值的整数次方:

            给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。不得使用库函数,不需要考虑大数问题。

           解题思路:不使用库函数实现乘方问题,需要考虑几种特殊情况,当输入为0时,输出为1,当输入为负数时,需要先转换成绝对值,最终的结果用1整除即可。

public class Solution {
    public double Power(double base, int exponent) {
        if(exponent==0)
            return 1;
        if(exponent==1)
            return base;     
        boolean flag = false;
        if( exponent<0){
            exponent=(-1)* exponent;
            flag = true;
        }
        if(exponent%2==0)
            exponent = exponent/2;
        else
            exponent = (exponent-1)/2;
        double result = Power(base,exponent);
        result *=result;
        if(exponent%2!=0)
            result = result*base;
        if(flag)
            result =1/result;
        return result;
             
  }
}

问题可以继续优化,采用递归的方式,时间复杂度O(logn)

          n为偶数,a^n=a^n/2*a^n/2;

          n为奇数,a^n=(a^(n-1)/2)*(a^(n-1/2))*a

          同时,位运算的效率比乘除法效率要高,用右移代替除以2。

public class Solution {
    public double Power(double base, int exponent) {
        if(exponent==0)
            return 1;
        if(exponent==1)
            return base;      
        boolean flag = false;
        if( exponent<0){
            exponent=(-1)* exponent;
            flag = true;
        }
        double result = Power(base,exponent>>1);
        result *=result;
        if((exponent & 1)==1)
            result = result*base;
        if(flag)
            result =1/result;
        return result;
            
  }
}

 

17.打印1到最大的n位数:

          输入数字n,按顺序打印出从1到最大的n位十进制数,比如输入3,则打印出1,2,3一直到最大的3位数即999.

          解题思路:考虑大数问题,字符串是一种简单有效的表示大数的方法。有两种方式解决,第一种方式为在字符串上模拟数字加法。加法的终止条件是高位溢出,打印需要从第一个非0字符开始;

public class Solution {
   //字符串上模拟加法
    public static void printToMax(int n){ 
        if(n <= 0){ 
            return; 
        } 
        char [] number = new char[n] ; 
        Arrays.fill(number,'0');
        while(!increment(number)){ 
            printNumber(number); 
        } 
    } 
    public static boolean increment(char [] num ){
        boolean isOverflow = false; 
        int carry = 0; 
        int size = s.length(); 
        for(int i = size - 1; i >= 0; i--){
            int temp = num[i] - '0' + carry; 
            if( i == size - 1){ 
                temp++; 
            } 
            if(temp >= 10){ 
                if(i == 0){ 
                    isOverflow = true;
                }else{ 
                    temp -= 10; 
                    carry = 1; 
                    num[i] = (char) ('0' + temp); 
                } 
            }else{
                num[i] = (char) ('0' + temp); 
                break; 
            }
        } 
        return isOverflow; 
    } 
    public static void printNumber(char [] num){
        int size = num.length; 
        int i = 0;
        while(i < size && num[i] == '0') //i < size在前,否则越界
            i++;
        if(i ==size) //不打印全0
            return;
        char[] printNum = Arrays.copyOfRange(num,i,size);
        System.out.println(printNum); 
    }
}

          第二种通过递归方式实现全排列。递归结束的条件是已经设置了数字的最后一位。算法中实现时就是在循环中指定第一个数字后,再去for指定第二个,第三个;在这期间需要去判断,指定的位数是否已满,已满则直接将这个拼好的 int[] 输出即可。

public class Solution {
    //字符每一位进行全排列
    public  void printToMax2(int n){ 
        if(n <= 0)
            return; 
        char [] number = new char[n] ; 
        Arrays.fill(number,'0');        
        printOrder(number,n,0); 
    } 
    public void printOrder(char [] number,int n,int loc){
        if(loc == n)
            return;
        for(int i = 0; i <= 9; i++){
             number[loc] = (char)( '0' + i); 
            if( loc ==n - 1){ 
                printNumber(number); 
            } 
            printOrder(number,n,loc+1); 
        } 
    } 
    public static void printNumber(char [] num){
        int size = num.length; 
        int i = 0;
        while(i < size && num[i] == '0') //i < size在前,否则越界
            i++;
        if(i ==size) //不打印全0
            return;
        char[] printNum = Arrays.copyOfRange(num,i,size);
        System.out.println(printNum); 
    }
}

          相关题目:定义一个函数,在该函数中实现任意两个整数的加法。由于没有限定两个数的范围,需要考虑大数问题,参考在字符串上实现加一的方法,实现两个数字相加的功能。

 

49.丑数:

       把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

      解题思路:创建数组保存已经找到的丑数,后面出现的丑数一定是前面的丑数乘以2、3、5中的一个,选取其中最小的一个保存到丑数数组中,直到找到结果。用空间来换取了时间。

public class Solution {
    public int minResult(int a, int b,int c){
       int temp = Math.min(a,b);
       return Math.min(c,temp);
    }
        
    public int GetUglyNumber_Solution(int index) {
        if(index<=0)
            return 0;
        int[] result = new int[index];
        result[0]=1;
        int i2=0,i3=0,i5=0,count=0;
        int temp =0;
        while(count<index-1){
            temp = minResult(result[i2]*2,result[i3]*3,result[i5]*5);
            if(temp ==result[i2]*2)
                i2++;
            if(temp ==result[i3]*3)
                i3++;
            if(temp ==result[i5]*5)
                i5++;
            result[++count]=temp;
        }
        return result[index-1];
    }
}

 

43.1-n整数中1出现的次数

       输入一个整数n,求1-n的n个整数的十进制表示中1出现的次数。例如,输入12,1-12这些整数中包含1 的数字的有1,10,11,12,一共出现了5次。

      解题思路:最直接的方式,将整数转化为字符数组,扫描数组判断数组中1的个数,时间复杂度较高。

public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        int count = 0;
        while(n>0){
            String str = String.valueOf(n);
            char [] numbers = str.toCharArray();
            for(int i=0;i< numbers.length;i++){
                if(numbers[i]=='1')
                    count++;
            }
            n--;
        }
        return count;
    }
}

       解题思路:(对于出现0-9的任意数字的解法相同取k即可) https://blog.youkuaiyun.com/doujinlong1/article/details/81366217

       1. 如果第i位上的数字为0,则第i位可能出现1的次数由更高位决定(若没有高位,视高位为0),等于更高位数字X当前位数的权重10^(i-1)。

       2. 如果第i位上的数字为1,则第i位上可能出现1的次数不仅受更高位影响,还受低位影响(若没有低位,视低位为0),等于更高位数字X当前位数的权重10^(i-1)+(低位数字+1)。

      3. 如果第i位上的数字大于1,则第i位上可能出现1的次数仅由更高位决定(若没有高位,视高位为0),等于(更高位数字+1)X当前位数的权重10i-1。

public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        int count = 0;
        for (int i = 1; i <= n; i = i * 10){
            int a = n / i;//高位
            int b = n % i;//剩余数
            if( a % 10 >= 2)
                count = count + (a / 10 + 1) * i;
            if(a % 10 == 1)
                count = count + (a / 10) * i + (b + 1);
            if(a % 10 == 0)
                count = count + (a / 10) * i;
        }
        return count;
    }
}

 

44.数字序列中某一位的数字:

         数字以0123456789101112……的格式序列化到一个字符序列中,在这个序列中,第5位是5,第13位是1,请写出一个函数,求任意第n位对应的数字。

         解题思路:最直观的方式是,从0开始枚举每一位的数字,每枚举一位数求出该位数是几位数,并把该数字的位数和前面所有数字的位数相加,直到位数之和等于输入数字。也可以寻求数字间的规律,进行取巧,首先得到输入数字是几位数,然后判断属于哪个范围,最后确定属于哪个数字,然后找到最终的第几位,输出。

  public class Solution {
        public int digitAtIndex(int index) { 
            if (index < 0) 
                return -1; 
            if (index <= 9) 
                return index; 
            int curIndex = 10; 
            int boundNum = 10; 
            int digit = 2; 
            while (index > curIndex + lengthSum(digit)) { 
                curIndex += lengthSum(digit);
                boundNum *= 10; 
                digit++; 
            } 
            int addNum = (index - curIndex) / digit; 
            int curNum = boundNum + addNum; 
            result = result % 10; 
            return Integer.toString(curNum).charAt(index-curIndex-addNum*digit)-'0';
        } 

        public int lengthSum(int digit) {
            if (digit == 1)
                return 10; 
            int temp = 9; 
            for (int i = 1; i < digit; i++) { 
                temp = temp * 10;
            } 
            return digit * temp; 
        }
}

 

57.和为s的数字:

         输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

        解题思路:设置两个辅助变量,指向数组的头和尾,当辅助变量所指数字的和<S,第一个变量向后移动,当辅助变量所指数字的和>S,第二个变量向前移动,当辅助变量所指数字的和=S时,比较乘积,并移动两个变量,当乘积较小时,清空列表,并重新添加。

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
        ArrayList<Integer> result = new ArrayList<Integer>();
        if(array.length ==0 || sum==0)
            return result;
        int head = 0;
        int tail = array.length-1;
        int mul = Integer.MAX_VALUE ;
        while(head<tail){
            if(array[head]+array[tail]>sum)
                tail--;
            if(array[head]+array[tail]<sum)
                head++;
            if(array[head]+array[tail]==sum){
                int temp = array[head] *array[tail];
                if(temp<mul){
                    result.clear();
                    result.add(array[head]);
                    result.add(array[tail]);
                    mul=temp;
                }
                head++;
                tail--;
            }
         }
         return result;
    }
}

 

57.和为S的连续正数序列 :

         输入一个正数S,打印出所有和为S的连续正数序列(至少含有两个数)。例如,输入15,由于1+2+3+4+5=4+5+6=6+7+8=15,所以打印出三个连续数列,1~5、4~6、7~8.

         解题思路:设置两个辅助变量,初始值为1和2,当辅助变量之间的数字的和<S,第二个变量向后移动,当辅助变量所指数字的和>S,和减去第一个变量的值,并且第一个指针向前移动;当辅助变量所指数字的和=S时,添加到结果列表中。每次重新新建一个list来存放结果,最终的结果添加到result中。注意,循环结束的条件不能按照最大的数设置,需要按照最小的数的最大值设置

import java.util.ArrayList;
public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
       ArrayList<ArrayList<Integer> >  result = new ArrayList<ArrayList<Integer> > ();      
       if(sum<3)
           return result;
        int small =1;
        int big = 2;
        int mid = (sum+1)/2;
        int temp = small+big;
        while(small<mid){
            ArrayList<Integer> list = new ArrayList<Integer>();
            if(temp<sum){
                big++;
                temp +=big;
            }else if(temp>sum){
                temp = temp-small;
                small++;
            }else{
                for(int i = small;i<=big;i++)
                    list.add(i);
                result.add(list);
                big++;
                temp = temp+big;
            } 
           
        }
        return result;
    }
}

 

40.最小的K个数:

       输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。

      解题思路:最直接的思路是把n个整数进行排序,然后输出前K个值,这样的时间复杂度为O(nlogn).

import java.util.*;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> result = new ArrayList<Integer>();
        if(input.length<k || input.length==0 || k==0)
            return result;
        Arrays.sort(input);
        for(int i =0;i<k;i++)
            result.add(input[i]);
        return result;
    }
}

       采用快速排序的思路,基于数组的第K个数字进行调整,使得比K大的数都在右边,比K小的数都在左边,然后输出前K个值,(这K个数不一定是排序的),这样的时间复杂度为O(n).快速排序的思路是,以第一个数为基准,然后从数组最后开始寻找到第一个比基准小的数,从数组开始寻找到第一个比基准大的数,交换,最后将基准与最大数交换。

  import java.util.*;
  public class Solution {
      public int Partition(int [] input, int start, int end) {
        int flag = input[start];
        int first = start;
        start++;
        while(start<end){           
           while(input[end]>flag && start<=end)
               end--;
           while(input[start]<flag && start<=end)
               start++; 
            if(start<end){
                int temp =  input[end];
                input[end] = input[start];
                input[start] = temp;
            }
          }
       input[first] = input[end];
       input[end] =flag;
       return end;
    }

    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> result = new ArrayList<Integer>();
        if(input.length<k || input.length==0 || k==0)
            return result;
        int start = 0;
        int end = input.length-1;
        int index = Partition(input,start,end);
        while(index!=k-1){
            if(index<k-1){
                start = index+1;
                index =  Partition(input,start,end);
            }
             if(index>k-1){
                 end= index-1;
                 index =  Partition(input,start,end);
             }
        }
        for(int i =0;i<k;i++)
            result.add(input[i]);
        return result;
    }
  }

        如果题目要求不能修改数组,可以考虑创建一个大小为K的容器,当容器不满时,依次添加,当容器满后,需要先找到K个数中的最大数,然后进行删除添加操作,可以使用二叉树来实现这个数据容器,例如最大堆、红黑树。时间复杂度为O(nlogk)。当需要在某个数据容器中频繁查找即替换最大值时,二叉树是一个合适的选择,可以利用红黑树或者堆等特殊的二叉树。

64.求1+2+3+...+n:

      求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

      解题思路:可以直接利用公式,n(n+1)/2,利用自带函数计算乘方,对于除以2可以利用位运算,向右移一位。

public class Solution {
    public int Sum_Solution(int n) {
        int result =(int) Math.pow(n,2)+n;
        return result>>1;
    }
}

       利用递归的形式,从后往前求取结果,利用布尔类型来代替if判断语句。

public class Solution {
    public int Sum_Solution(int n) {
        int sum = n;
        boolean flag = (n>0) && ((sum +=Sum_Solution(n-1))>0);
        return sum;
    }
}

 

63.股票的最大利润:

         假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可获得的最大利润是多少?例如,一只股票在某些时间节点的价格为{9,11,8,5,7,12,16,14}。如果我们能在价格为5的时候买入并在价格为16时卖出,则能获得最大的利润为11.

         解题思路: 最大利润无外乎就是计算后面的数字减去前面的数字得到的一个最大的差值;遍历求解当前的最小值,当前的最大差值即可。时间复杂度:O(n),空间复杂度:O(1)

public class Solution {
    public int maxProfit(int[] prices){
        if(prices ==null || prices.length ==0)
            return 0;
        int buy = prices[0];
        int max = 0;
        for(int i =0;i<prices.length;i++){
            if(price[i]<buy){
                buy = prices[i];
            }else{
                int profit = price[i]-buy;
                if(max<profit)
                    max = profit;
            }
        }
        return max;
    }
}

        如果可以多次卖出,求最大利润。

        解题思路:只要后面的价格可以盈利,也就是相邻两天,后者大于前者,则当天进行两种操作,前一天买入,后一天卖出;如果相邻的两天不能盈利,则不进行买卖。

public class Solution {
    public int maxProfit(int[] prices){
        if(prices.length ==0)
            return 0;
        int profit = 0;
        for(int i =0;i<prices.length-1;i++){
            if(price[i+1]-price[i]>0){
                profit = profit + prices[i+1]-prices[i];
            }
        }
        return profit;
    }
}

 

62.圆圈中最后剩下的数字:

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

       解题思路:利用队列来解决,设置一个队列,将所有元素添加到队列中,从第一个元素开始,逐个添加到队列末尾,移除第M个即可,当队列元素只有一个时结束循环。

import java.util.*;
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if(n<1 || m<1)
            return -1;
        Queue<Integer> queue = new LinkedList<Integer>();
        for(int i =0;i<n;i++)
            queue.add(i);
        int num = 0;
        while(queue.size()>1){
            int temp = queue.poll();
            num++;
            if(num !=m)
                queue.add(temp);
            else
                num=0;
        }
        return queue.poll();
    }
}

    约瑟夫环问题,可得到递推公式:

      f(n,m)={    0                            n=1

                     [f(n-1,m)+m]%n     n>1

public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if(n<1 || m<1)
            return -1;
        if(n==1)
            return 1;
        int last =0;
        for(int i = 2; i<=n;i++){
           last =  (last+m)%i;
        }
        return last;
    }
}

 

60.N个骰子的点数:

        把n个骰子扔在地上,所有骰子朝上一面的点数之和为S。输入n,打印出S的所有可能的值出现的概率。

        解题思路:n个骰子点数总和在n-6n之间,申请6n-n+1的数组长度来存放每个点数之和出现的次数, 统计每个点数之和出现的次数,通过递归的方式。每个骰子有六种可能结果,n个骰子共有6^n种组合,在点数数组基础上每个元素除以6^n即为每个点数出现的概率。

private static final int g_maxValue = 6;
    //基于递归求骰子点数,时间效率不高
    public static void PrintProbability(int number){
        if(number<1) return;
        int maxSum = number*g_maxValue;
        int[] pProbabilities = new int[maxSum-number+1];
        //初始化,开始统计之前都为0次
        for(int i=number;i<=maxSum;i++){
            pProbabilities[i-number] = 0;
        }
        double total = Math.pow(g_maxValue,number);
        //probability(number,pProbabilities);这个函数计算n~6n每种情况出现的次数
        probability(number,pProbabilities);
        for(int i=number;i<=maxSum;i++){
            double ratio = pProbabilities[i-number]/total;
            System.out.println("i: "+i+" ratio: "+ratio);
        }
    }
    public static void probability(int number,int[] pProbabilities){
        for(int i=1;i<=g_maxValue;i++){//从第一个骰子开始
            probability(number,number,i,pProbabilities);
        }
    }
    //总共original个骰子,当前第 current个骰子,当前的和,贯穿始终的数组
    public static void probability(int original,int current,int sum,int[] pProbabilities){
        if(current==1){
            pProbabilities[sum-original]++;
        }else{
            for(int i=1;i<=g_maxValue;i++){
                probability(original,current-1,sum+i,pProbabilities);
            }
        }
    }

          采用循环的方式,自底向上,用两个数组来存储骰子点数的每个总数出现的次数。在一轮循环中,第一个数组中的第n个数字表示骰子和为n出现的次数,在下一轮循环中,加上一个新的骰子,此时和为n的骰子出现的次数等于上一轮循环中骰子点数和为n-1,n-2,n-3,n-4,n-5,n-6的次数的总和。

//基于循环求骰子点数
    public static void PrintProbability_1(int number){
        if(number<1){
            return;
        }
        int[][] pProbabilities = new int[2][g_maxValue*number +1];
        for(int i=0;i<g_maxValue;i++){//初始化数组
             pProbabilities[0][i] = 0;
             pProbabilities[1][i] = 0;
        }
        int flag = 0;
        for(int i=1;i<=g_maxValue;i++){//当第一次抛掷骰子时,有6种可能,每种可能出现一次
            pProbabilities[flag][i] = 1;
        }
        //从第二次开始掷骰子,假设第一个数组中的第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之和
        for(int k =2;k<=number;k++){
            for(int i=0;i<k;i++){//第k次掷骰子,和最小为k,小于k的情况是不可能发生的!所以另不可能发生的次数设置为0!
                pProbabilities[1-flag][i] = 0;
            }
            for(int i=k;i<=g_maxValue*k;i++){//第k次掷骰子,和最小为k,最大为g_maxValue*k
                pProbabilities[1-flag][i] = 0;//初始化,因为这个数组要重复使用,上一次的值要清0
                for(int j=1;j<=i&&j<=g_maxValue;j++){
                    pProbabilities[1-flag][i] += pProbabilities[flag][i-j];
                }
            }
            flag = 1-flag;
        }
        double total = Math.pow(g_maxValue, number);
        for(int i=number;i<=g_maxValue*number;i++){
            double ratio = pProbabilities[flag][i]/total;
            System.out.println("sum: "+i+" ratio: "+ratio);
        }
    }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值