王禹 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则原先的暴力算法复杂度为
运用素数求解
用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函数代码为
运行结果为:
- 王禹 406130917327
- java主要城市时区对照表(包含时区显示)
- 按行输出,之字形输出,翻转二叉树----层次遍历的应用
- PAT 甲级 1133. Splitting A Linked List (25)
- 数据科学比赛公开代码学习链接
- Javascript学习笔记_闭包
- 王禹 406130917327
- 微信小程序异步获取app.js的函数
- 二维码生成和pdf添加文件和图片
- mkv210_image.c文件详解
- java—变量的加载过程
- 一些入门级函数
- 如何通过百度指数分析用户的真实需求?
- 火狐、谷歌、IE关于document.body.scrollTop和document.documentElement.scrollTop 以及值为0的问题
- ubuntu声音设置不显示声卡设备的解决方法