与数字相关算法

丑数
问题描述:
我们把只包含因子(因子都是质数,合数不叫做因子)2、3、5的数称为丑数,如6、8都是丑数,14不是丑数,因为14包含因子7,习惯上1也是丑数。求出1到1500之间的所有丑数。
思路:
解法一: 逐个判断是不是丑数,时间复杂度比较大
从丑数的定义可知,如果一个数是丑数,那么该数一定能被2、3、5整除,故判断一个数m是不是丑数,可以通过执行下面操作来完成:
while(m%2==0){m /= 2;}
while(m%3==0){m /= 3;}
while(m%5==0){m /= 5;}
如果最后得出的m等于1,则原来的m是丑数,否则不是丑数。
怎么证明?假如最后的值为n,只要能证明n是一个质数(则n本身是m的一个因子,而n不等于2、3、5)或者n包含除2、3、5以外的因子:
假如n是一个质数,则m便是丑数
假如n是合数,那么n可以表示为若干个质数的积,如果这些质数包含了2、3、5,那么在之前的循环中已经被除去了,所以合数n必然包含了出2、3、5以外的因子,那么m自然也就包含除2、3、5以外的因子了。
知道怎么判断一个数是不是丑数,那么可以通过逐个判断来求1到1500之间的所有丑数。
解法二: 通过已知的丑数,求后面剩余的丑数,避免对非丑数的计算,用空间换时间
假如,已知有序的丑数1、2、3、5、7,怎么求下一个丑数?
有规则如:
丑数乘以2、3或5得到的一定也是丑数,而任一个丑数也一定可以由另一个丑数乘以2、3或5得到。
所以,求下一个 最小 丑数,可以通过前面的丑数乘以2、3或5(注意不能是乘以多个,如m*2*3,那在其之前就有m*2和m*3了,所以就直接用(m*2)*3或(m*3)*2便可,不需要乘以多个)。
所有的有可能的下一 最小 丑数:
前面出现的所有丑数(已经保存在数组中)分别乘以2,然后取其中一个最小的且大于数组中最大的一个(数组的最后一个,因为数组是有序的),如,已出现的丑数为:1、2、3、5、7,那可以求的2、4、6、10、14,由于10是比7大的最小一个,所以取10。
同理求乘以3和乘以5的情况,如,已出现的丑数为1、2、3、5、7,乘以3对应的是3、6、9、15、21,取9,乘以5对应的是5、10、15、25、35,取10。
然后取三者中的最小作为下一个最小丑数,这里是9,。为什么取最小,因为较大可以通过重复以上操作来求得。
由于数组本身有序,故在求乘2(乘3或乘5)的候选丑数时,可以通过二分法,如上面的1、2、3、5、7,我们可以先求3*2,得到6,由于6<7,不满足,再进一步对5、7二分,求5*2,得到10,10>7,但可能还存在比10小且比7大的数,故还要进一步二分,直到无法二分为止,这里要对5的左边3的右边部分二分,由于已经没有数了,故到此为止,所以候选值为10。同理,也通过二分求乘3和乘5的候选值。

随机生成和为S的N个正整数——投影法 

以生成和为204个数为例,可以先生成随机生成020之间的三个数字再排序,假设得到了4718。然后在X-Y数轴上画出这三个数,如下图:


Y

由图很容易看出ABBCCDDE这四段的长度和肯定为20。因此ABBCCDDE这四段的长度即和为204个数,这4个数分别为43112。这种方法只要随机生成N - 1个小于S的不同数字,排序后计算两两差值就可以得到和为SN个正整数。

C++实现:

#include<cstdio>

#include<ctime>

#include<set>

#include<algorithm>

usingnamespace std;

//[s, e)区间上随机取n个不同的数并存放到a[]

void GetRandomNum(int *aintnintsinte)

{

    set<int> set_a;

    srand(time(0));

    for (int i = 0; i < n; i++)

    {

        int num = (rand() % (e-s)) + s;

        if (set_a.find(num) == set_a.end())

            set_a.insert(num);

        else

            i--;

    }

    int i = 0;

    set<int>::iterator pos;

    for (pos = set_a.begin(); pos != set_a.end(); pos++)

        a[i++] = *pos;

}

int main()

{

    constint NSUM = 20;

    constint NCOUNT = 4;

    printf("生成和为%d%d个数 \n", NSUM, NCOUNT);

    int a[NCOUNT];

    GetRandomNum(a, NCOUNT - 1, 10, NSUM);

    sort(a, a + NCOUNT - 1);

    a[NCOUNT - 1] = NSUM;

    printf("已经生成和为%d%d个数: \n", NSUM, NCOUNT);

    printf("%d ", a[0]);

    for (int i = 1; i < NCOUNT; i++)

        printf("%d ", a[i] - a[i - 1]);

    putchar('\n');

    return 0;

}


n个骰子的点数
问题:
n个骰子的点数之和s,求s的所有可能的值及其出现的概率
思路:
用一个数组A保存s出现的次数,A[s]表示和为s的值出现的次数,假如有n个骰子,那么数组的大小为6n,算上A[0],那么数组大小为6n+1,因为所有骰子都为6的时候和最大,最大的和便是6n。
数组A的初始时每个元素的值都为0,我们逐个骰子往里面加:
首先加入第一个骰子,则A[0]=0、A[1]=1、A[2]=1、A[3]=1、A[4]=1、A[5]=1、A[6]=1、A[7]=0、A[8]=0。。。
然后加入第二个骰子,我们从坐标为2*6=12的位置开始向前修改数组的值,因为坐标大于12的元素都不受影响,依旧是0,怎么求新的A[s]的值呢?因为新加进来的骰子的值只能从1到6,假设其为i,要想和为s,那么原来骰子的和要是s-i,原来骰子的和为s-i出现的次数为A[s-i],则有当新骰子为i时,总骰子和为s出现的次数为A[s-i],由于新骰子可以从1到6,所以:
A[s]=A[s-1]+A[s-2]+A[s-3]+A[s-4]+A[s-5]+A[s-6]
加入第二个骰子时,我们需要修改A[12]到A[1]的值
加入第三个骰子时,需要修改A[18]到A[2]的值
依次类推
C实现(windows):

#include<stdio.h>

double * func(intn, intmaxValue)

{

    if (n< = 0 || maxValue <= 0)

        returnNULL;

    int size = maxValue * n + 1;

    //用于存放和出现的次数

    int * sumArray = (int *)malloc(size * sizeof(int));

    //初始化数组

    for (int i = 0; i < size; i++)

    {

        sumArray[i] = 0;

    }

    //加入第一个骰子

    for (int i = 1; i <= maxValue; i++)

    {

        sumArray[i] = 1;

    }

    //逐个加入骰子

    for (int i = 2; i <= n; i++)

    {

        for (int j = i * maxValue; j >= i - 1; j--)

        {

            sumArray[j] = 0;

            for (int k = 1; k <= maxValue; k++)

            {

                if (j - k <= i - 2)

                    break;

                sumArray[j] += sumArray[j - k];

            }

        }

    }

    //用于存放和出现的概率

    double * probabilitiesArray = (double *)malloc((size + 1) * sizeof(double));

    //和出现的总次数,可以通过求sumArray数组的和得到,也可以通过排列数得到

    int sum = 1;

    for (int i = 0; i < n; i++)

        sum *= maxValue;

    for (int i = 0; i < size; i++)

    {

        /*

        double除以intdoubleint除以double也得double(double)sumArray[i]/sum

        C会先将sumArray[i]转换成double,然后再除以sum,得到的是double,故小数部分不会丢失,

        如果改成(double)(sumArray[i]/sum)虽然结果被转换成了double,但转换之前的结果是int

        小数部分已经丢失了

        */

        probabilitiesArray[i] = (double)sumArray[i] / sum;

    }

    free(sumArray);

    probabilitiesArray[size] = -1;

    return probabilitiesArray;

}

int main()

{

    double * result = func(3, 6);

    for (int i = 1;; i++)

    {

        if (result[i] == -1)

            break;

        printf("%d,%f\n", i, result[i]);

    }

   free(result);

}

maxValue用于设置一个骰子的面数,当然,默认情况下一个骰子有6面,为了使函数更具通用性,这里允许设置骰子的面数


求1+2+3+4+....+n
问题:
求1+2+3+4+....+n,要求不能使用乘除法、for、while、,if、else、switch及(A?B:C)

解法一:利用函数指针实现递归
C实现:

#include<stdio.h>

//定义一个函数指针

typedefint(*fun)(int);

int method1(intn)

{

    return 0;

}

int method2(intn)

{

    fun f[2] = { method1, method2 };

    return f[!!n](n-1) + n;

}

int main()

{

    int result = method2(5);

    printf("%d", result);

}

说明:
!!n:当n非零时 对n连续做两次反运算得到true,C/C++里面true与1是等价的,当n为零时,对n连续做两次反运算得到false,C/C++里false与0等价。
method2实际上是一个递归,因为当n不为0时,都变成执行f[1](n-1)+n,即method2(n-1)+n,当n为0时,调用f[0](n),即method1(n)来结束递归。

解法二:利用构造函数实现循环
C++实现: 因为C没有类

#include<iostream>

usingnamespace std;

classTemp

{

public:

    Temp(){ ++n; sum += n; }

    staticvoid Reset(){ n = 0; sum = 0; }

    staticint GetSum(){ return sum; }

private:

    staticint n;

    staticint sum;

};

int method(intn)

{

    Temp::Reset();

    Temp *a = newTemp[n];

    delete[]a;

    a = NULL;

    returnTemp::GetSum();

}

void main()

{

    int sum = method(5);

    cout<< sum << endl;

}

说明:
method中创建一个大小为n的数组,会调用Temp构造函数n次,每调用一次Temp构造函数便执行一次相加,结果保存在静态变量中,因而每次调用都共享这个静态结果。
注意:
如果换成Java便不可行,因为Java创建数组的时候构造函数不会被调用:

class SuperClass{

   public SuperClass(){

      System.out.println("SuperClass构造函数被调用");

   }

}

publicclass Test {

    publicstaticvoid main(String[]args){

       SuperClass[]sup = new SuperClass[5];

    }

}

以上代码不会有任何输出

解法三:利用虚函数实现递归
C++实现:

#include<iostream>

usingnamespace std;

classA;

A * Array[2];

classA

{

public:

    virtualint sum(intn){ return 0; }

};

classB :publicA

{

public:

    int sum(intn)

    {

        return Array[!!n]->sum(n - 1) + n;

    }

};

int method(intn)

{

    A a;

    B b;

    Array[0] =& a;

    Array[1] =& b;

    int value = Array[1]->sum(n);

    return value;

}

void main()

{

    int sum = method(5);

    cout<< sum << endl;

}


求素数(质数)
问题:
给定正整数参数 N,求小于 N的质数,从小到大打印出来
思路:
解法一:
分别判断1到N,判断n是否为质数,可以分别用n除以2到(n-1)的每个数,都不能整除则n为质数,该解法时间复杂度为O(N^2)。
解法二:
除了2以外,所有的质数一定是奇数,所以不需要从1到N逐个判断,只要判断3到N的所有奇数便可,也就是循环时不再用for(int n= 1;n<=N;n++),而是用for(int n = 3;n<=N;n+=2)。解法二比解法一减少了一半的运算量。
解法三:
n如果是非质数,那么n一定有一个小于等于n/2的质因子,证明:
假设n=x*y,因为x>=2,所以y<=n/2,怎么证明"质因子"?假设y是质数,则可证,假设y不是质数,则y有两个因子,即y=a*b,如果a或b是质数,则可证,否则进一步对a或b求因子,这样直到找到一个质数因子为止,为什么算法一定会结束?因为每次分解后的因子都小于等于分解前的一半,所以算法总会结束。
这样的话,试除的时候就不必从2到(n-1),只要从2到(n/2)便可,所以时间复杂度又减少了一半。还可以进一步优化,只要除以2到(n/2)的质数便可,而我们在求n之前的质数时,已经把这些质数保存起来了,所以不必从2到(n/2),可以从保存起来的质数数组或链表中去取,取出来后与n/2比较一下,只比较小于等于n/2的便可。
解法四:
如果n是非质数,则n一定有小于等于根号n的质因子,证明:
假设n=x*y,假设x>=根号n,则y<=根号n,进一步对y分解,最终一定找到一个质因子。
所以,只要试除根号n以内的质数便可,这样一来时间复杂度进一步减少了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值