王禹 406130917327

来源:互联网 发布:淘宝店铺代运营协议 编辑:程序博客网 时间:2024/06/16 13:08

对编程之法中算法的实现

王禹 406130917327






摘要:将编程之美中的15个题目调试成功,并且对其做了详细的报告,报告中包括该数据结构,以及这个题目的简单应用。还有这些代码的出处,和测试情况。并且根据这些调试以及应用的情况,思考这些代码的巧妙之处以及可能会有哪些不足。并针对这些地方提出一些自己的看法。






关键字:编程之美,调试,应用。




1.数塔问题

这里写图片描述
在上面的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或 右下走。只需要求出这个最大和即可,不必给出具体路径。 三角形的行数大于1小于等于100,数字为 0 - 99

解法一
刚拿到这道题目想到的是用递归的方法来解决题目

具体代码为:
这里写图片描述
运行结果为:
这里写图片描述

但是递归算法的时间复杂度较为高,并且由于是递归的方式,会对一个子问题进行多次计算。于是我们可以想到是否可以将子问题的答案保存起来。这样就不必多次计算了。

解法2

这里写图片描述

编译成功,但是未产生合理的值。

这里写图片描述

经过调试发现第10行应该是maxsum[i][j]!=-1代表已经i,j位置上的值已经被计算出来了。才能够返回。

调试完毕 成功得出想要的结果
这里写图片描述

但是可否能够写出一个不需要递归的算法呢?应该是可以的。

解法3

选择如下算法
这里写图片描述

编译结果为:
这里写图片描述
编译成功,但是没有得到想要的解。

观察到第22行应该为cout<<maxsum[1[1]才对修改后的出正确值

2.背包问题

用动态规划求解0,1背包问题代码如下:

这里写图片描述

这个时候虽然能编译过,但是报错如图所示。

这里写图片描述

通过在
这里写图片描述

加入打印here以及lala语句发现进入这个循环之后就出现错误。于是认为这个地方出现了错误。

发现了一个逻辑错误。我只在C<0的时候返回了值。但是还有C不小于0的情况没有考虑。通过添加
else return 0;语句编译通过。

通过的解为
这里写图片描述

3.旋转字符串


问题描述:

给定一个字符串,要求把字符串前面的若干个字符移动到字符串的尾部,如把字符串“abcdef”前面的2个字符’a’和’b’移动到字符串的尾部,使得原字符串变成字符串“cdefab”。请写一个函数完成此功能,要求对长度为n的字符串操作的时间复杂度为 O(n),空间复杂度为 O(1)。

解法一:暴力移位法

可以逐个将字符串的最前面的元素移动到最后的位置,以达到这种效果。具体代码为:
这里写图片描述

并且可以运行成功。

这里写图片描述
但是其中left_shift_one函数需要调用m次每次的时间复杂度为n。那么总的时间复杂度就为O(n*m)远远超出了要求。于是可以有下面一种方法。

解法2:三步反转法

将一个字符串分成X和Y两个部分,在每部分字符串上定义反转操作,如X^T,即把X的所有字符反转(如,X=”abc”,那么X^T=”cba”),那么就得到下面的结论:(X^TY^T)^T=YX,显然就解决了字符串的反转问题。

代码分两个部分第一部分实现反转功能。第二部分分别对X和Y进行反转之后对整个字符串进行反转。于是便实现了功能。

代码为:


运行结果为:


这里写图片描述

4.字符串包含



问题描述


给定两个分别由字母组成的字符串A和字符串B,字符串B的长度比字符串A短。请问,如何最快地判断字符串B中所有字母是否都在字符串A里?

暴力解法

针对B中的每一个字符判断它是否存在于A串中。
代码可编写为下:
这里写图片描述


代码运行结果:


这里写图片描述


显然对于A,B两个串长度分别为n和m来说这个算法需要O(n*m)的时间复杂度。

排序解法

如果允许排序的话,我们可以考虑下排序。比如可先对这两个字符串的字母进行排序,然后再同时对两个字串依次轮询。两个字串的排序需要(常规情况)O(m log m) + O(n log n)次操作,之后的线性扫描需要O(m+n)次操作。


其代码为:

这里写图片描述

结果为:

想法:当n和m相进的时候才适合用这个方法。设n=m则原先的暴力算法复杂度为 n2 而这个算法的复杂度为nlogn。较为适合使用这个方法。但是一般查询的时候m都会远小于n。较长出现的情况为m<logn 所以使用这个算法并不合适。


运用素数求解

用26个素数分别代表A到Z并且将长字符串中的字母对应的数字相乘。得到一个整数。利用上面字母和素数的对应关系,对应第二个字符串中的字母,然后轮询,用每个字母对应的素数除前面得到的整数。如果结果有余数,说明结果为false。如果整个过程中没有余数,则说明第二个字符串是第一个的子集了(判断是不是真子集,可以比较两个字符串对应的素数乘积,若相等则不是真子集)


具体步骤如下:
1.按照从小到大的顺序,用26个素数分别与字符’A’到’Z’一一对应。
2.遍历长字符串,求得每个字符对应素数的乘积。
3.遍历短字符串,判断乘积能否被短字符串中的字符对应的素数整除。
4.输出结果。


代码为:
这里写图片描述
该算法之中有两个循环长度分别是n与m其时间复杂度为O(n+m).。最好情况为O(n)即遍历短串的第一个数与长串相除就有余数。该算法时间复杂度较为优秀,但是多个素数相乘有可能会产生溢出的情况。

用hash表求解


这种包含问题很容易便能想到hash表求解。并且只有26个字母hash表的表长很短。


其代码为:
这里写图片描述


运行结果为

这里写图片描述


该算法时间复杂度为O(n+m)并且不会有上溢的风险。


5.字符串转换为整数问题

题目描述

输入一个由数字组成的字符串,把它转换成整数并输出。例如:输入字符串”123”,输出整数123。

给定函数原型int StrToInt(const char *str) ,实现字符串转换成整数的功能.

3.2分析与解法

对于一个字符串来说。比如说字符串”123” 。

  • 首先我们扫描到了字符 ‘1’我们知道这是第一位。则令结果为1。
  • 之后我们扫描到了字符 ‘2’我们知道这是第二位。将原先的结果一往左移动一位并且往后面加入2。
  • 最后我们扫描到了字符’3’按照以前的步骤将原先的结果往前移动一位。之后在后面加入3。

所以我们具有了思路,即对一个字符串我们从左到右的扫描,把以前的数字乘上10,之后加上现在的数字。

具体算法为:

这里写图片描述

需要加上许多限制条件


6. 寻找数组中最小的K个数

问题描述:

输入n个整数,输出其中最小的k个。

解法1

可以先将数组排序,之后将排序号的数组的前k个元素拿过来使用。


该方法虽然简单,但是其中后n-k个元素并没有要求你排序,这样就浪费了时间。所以应该存在更号的方法。

解法2

1、遍历n个数,把最先遍历到的k个数存入到大小为k的数组中,假设它们即是最小的k个数;
2、对这k个数,利用选择或交换排序找到这k个元素中的最大值kmax(找最大值需要遍历这k个数,时间复杂度为O(k));
3、继续遍历剩余n-k个数。假设每一次遍历到的新的元素的值为x,把x与kmax比较:如果x < kmax ,用x替换kmax,并回到第二步重新找出k个元素的数组中最大元素kmax‘;如果x >= kmax,则继续遍历不更新数组。
每次遍历,更新或不更新数组的所用的时间为O(k)或O(0)。故整趟下来,时间复杂度为n*O(k)=O(n*k)。

其代码为

这里写图片描述


运行结果为:

这里写图片描述

解法3

有一种平均复杂度为O(n)的算法
选取S中一个元素作为枢纽元v,将集合S-{v}分割成S1和S2,就像快速排序那样

如果k <= |S1|,那么第k个最小元素必然在S1中。在这种情况下,返回QuickSelect(S1, k)。
如果k = 1 + |S1|,那么枢纽元素就是第k个最小元素,即找到,直接返回它。
否则,这第k个最小元素就在S2中,即S2中的第(k - |S1| - 1)个最小元素,我们递归调用并返回QuickSelect(S2, k - |S1| - 1)。


具体代码如下

这里写图片描述


7.寻找数组中和为定值的两个数

输入一个数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字。

要求时间复杂度是O(N)。如果有多对数字的和等于输入的数字,输出任意一对即可。
例如输入数组1、2、4、7、11、15和数字15。由于4+11=15,因此输出4和11。

:问题观察
这个问题相当于对于每个a[i],查找sum-a[i]是否也在原始序列中,每一次要查找的时间都要花费为O(N),这样下来,最终找到两个数还是需要O(N^2)的复杂度。那么如何减少查找的时间复杂度呢?
:解法2
此时我们相既然时间复杂度都为O(n*n)了不如先排个序吧。先将数组排序后。排序了之后我们发现确实可以减少查找的时间复杂度了。因为这个时候我们可以使用二分查找了。使用二分查找的话,相当与对于每个a[i]只用log n的时间复杂度就可以将它找出来。那么总的时间复杂度就为nlogn
:解法三
对于数组有序的情况,我们是否有更高效的方法去得到问题的解呢?我们可以这样:
用两个指针i,j,各自指向数组的首尾两端,令i=0,j=n-1,然后i++,j–,逐次判断a[i]+a[j]?=sum,
如果某一刻a[i]+a[j] > sum,则要想办法让sum的值减小,所以此刻i不动,j–;
如果某一刻a[i]+a[j] < sum,则要想办法让sum的值增大,所以此刻i++,j不动。
这样的话找到算法的解的时间复杂度为O(n)。
在数组有序的情况下总的时间复杂度为O(n),在数组无序的情况下总的时间复杂度为O(nlogn).


具体代码如下:
这里写图片描述


代码的运行结果
这里写图片描述

8.寻找和为定值的多个数

6解法1

注意到取n,和不取n个区别即可,考虑是否取第n个数的策略,可以转化为一个只和前n-1个数相关的问题。
如果取第n个数,那么问题就转化为“取前n-1个数使得它们的和为sum-n”,对应的代码语句就是sumOfkNumber(sum - n, n - 1);
如果不取第n个数,那么问题就转化为“取前n-1个数使得他们的和为sum”,对应的代码语句为sumOfkNumber(sum, n - 1)。

具体代码为
这里写图片描述


运行结果为:
这里写图片描述

9.最大连续子数组和

输入一个整形数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。 求所有子数组的和的最大值,要求时间复杂度为O(n)。

这里写图片描述
这样的时间复杂度为O(n^3) 当然有更好的方法

int MaxSubArray(int* a, int n)
{
int currSum = 0;
int maxSum = a[0]; //全负情况,返回最大数

for (int j = 0; j < n; j++){    currSum = (a[j] > currSum + a[j]) ? a[j] : currSum + a[j];    maxSum = (maxSum > currSum) ? maxSum : currSum;}return maxSum;

}
代码运行结果为。
这里写图片描述

10.跳台阶问题

一个台阶总共有n 级,如果一次可以跳1 级,也可以跳2 级。
求总共有多少总跳法,并分析算法的时间复杂度。其可以简化为求fibonacci数问题

long long Fibonacci(unsigned int n)
{
int result[3] = {0, 1, 2};
if (n <= 2)
return result[n];

return Fibonacci(n - 1) + Fibonacci(n - 2);

}

这里写图片描述

当然fibonacci是可以优化的可以将其优化为
int ClimbStairs(int n)
{
int dp[3] = { 1, 1 };
if (n < 2)
{
return 1;
}
for (int i = 2; i <= n; i++)
{
dp[2] = dp[0] + dp[1];
dp[0] = dp[1];
dp[1] = dp[2];
}
return dp[2];
}
这样就可以降低时间复杂度。
换硬币问题。想兑换100元钱,有1,2,5,10四种钱,问总共有多少兑换方法

const int N = 100;
int dimes[] = { 1, 2, 5, 10 };
int arr[N + 1] = { 1 };
for (int i = 0; i < sizeof(dimes) / sizeof(int); ++i)
{
for (int j = dimes[i]; j <= N; ++j)
{
arr[j] += arr[j - dimes[i]];
}
}

11.奇偶调序

输入一个整数数组,调整数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。要求时间复杂度为O(n)。

解法1:
借助快速排序的想法,用一个主元将这个数组划分为两个堆一堆奇数一堆偶数,具体代码为:

这里写图片描述

得到的结果为:

这里写图片描述

12.荷兰国旗问题

下面是问题的正规描述: 现有n个红白蓝三种不同颜色的小球,乱序排列在一起,请通过两两交换任意两个球,使得从左至右,依次是一些红球、一些白球、一些蓝球。

荷兰国旗问题类似于有三个指针的快速排序问题。

具体代码如下
这里写图片描述
代码运行结果为
这里写图片描述

13.完美洗牌算法

有个长度为2n的数组{a1,a2,a3,…,an,b1,b2,b3,…,bn},希望排序后{a1,b1,a2,b2,….,an,bn},请考虑有无时间复杂度o(n),空间复杂度0(1)的解法。

解法一为暴力求解法逐次找到b1,b2,的位置之后将其插入进去。其代码为:O(n^2);
可以采用利用空间来换取时间复杂度的想法。算法如下:
这里写图片描述
时间空间复杂度均为O(n);

完美洗牌算法的进阶版本:

void CycleLeader(int *a, int from, int mod){    int t,i;    for (i = from * 2 % mod; i != from; i = i * 2 % mod)    {        t = a[i];        a[i] = a[from];        a[from] = t;    }}void RightRotate(int *a, int num, int n){    reverse(a, 1, n - num);    reverse(a, n - num + 1, n);    reverse(a, 1, n);}void reverse(int *a,int i,int j){ while(i<j) {    temp=a[i];    a[i]=a[j];    a[j]=temp;    i++;    j--;}}void PerfectShuffle2(int *a, int n){    int n2, m, i, k, t;    for (; n > 1;)    {        // step 1        n2 = n * 2;        for (k = 0, m = 1; n2 / m >= 3; ++k, m *= 3)          ;        m /= 2;        // 2m = 3^k - 1 , 3^k <= 2n < 3^(k + 1)        // step 2        right_rotate(a + m, m, n);        // step 3        for (i = 0, t = 1; i < k; ++i, t *= 3)        {          cycle_leader(a , t, m * 2 + 1);        }        //step 4        a += m * 2;        n -= m;    }    // n = 1    t = a[1];    a[1] = a[2];    a[2] = t;} 

程序主体为:
这里写图片描述

其获得的结果为

这里写图片描述

14.最大连续乘积字串问题

给一个浮点数序列,取最大乘积连续子串的值,例如 -2.5,4,0,3,0.5,8,-1,则取出的最大乘积连续子串为3,0.5,8。也就是说,上述数组中,3 0.5 8这3个数的乘积30.58=12是最大的,而且是连续的。

解法一:暴力解法

double maxProductSubstring(double *a, int length){    double maxResult = a[0];    for (int i = 0; i < length; i++)    {        double x = 1;        for (int j = i; j < length; j++)        {            x *= a[j];            if (x > maxResult)            {                maxResult = x;            }        }    }    return maxResult;}


该方法将数组中每一个子串都进行计算。显然时间复杂度为O(n^2)。

解法2:

这里写图片描述

运行结果为:

这里写图片描述

15.交替字符串问题

输入三个字符串s1、s2和s3,判断第三个字符串s3是否由前两个字符串s1和s2交错而成,即不改变s1和s2中各个字符原有的相对顺序,例如当s1 = “aabcc”,s2 = “dbbca”,s3 = “aadbbcbcac”时,则输出true,但如果s3=“accabdbbca”,则输出false。

public boolean IsInterleave(String s1, String 2, String 3){    int n = s1.length(), m = s2.length(), s = s3.length();    if (n + m != s)        return false;    boolean[][]dp = new boolean[n + 1][m + 1];    //在初始化边界时,我们认为空串可以由空串组成,因此dp[0][0]赋值为true。    dp[0][0] = true;    for (int i = 0; i < n + 1; i++){        for (int j = 0; j < m + 1; j++){            if ( dp[i][j] || (i - 1 >= 0 && dp[i - 1][j] == true &&                //取s1字符                s1.charAT(i - 1) == s3.charAT(i + j - 1)) ||                (j - 1 >= 0 && dp[i][j - 1] == true &&                //取s2字符                s2.charAT(j - 1) == s3.charAT(i + j - 1)) )                dp[i][j] = true;            else                dp[i][j] = false;        }    }    return dp[n][m]}

main函数代码为
这里写图片描述
运行结果为:
这里写图片描述

原创粉丝点击