0.总结
- 文章来源:CSDN@LawsonAbs
- 记录我自己编写算法题时犯过的错误
1.踩过的坑
-
1.见到大批的WA,想想题意搞清楚了吗?
-
2.写
<
,>
号的时候,需要考虑会用到=
号吗?
上面这种问题,经常出现在二分法,排序等情况下。比如说在快排中,哪些语句需要使用等号,哪些不用? -
3.写
if
条件的时候,要考虑后面的else
是什么样的情况?是二选一的吗?if
内的代码块有必要二选一执行吗? -
4.
if
中的条件是什么样的情况成立?&&
和||
符号正确吗? -
5.数组下标越界了吗?
如果出现Runtime Error
,通常就是出现了数组越界的情况,这时,就需要考虑是否在判断数组边界是否越界。 -
6.数组开的够大吗?是导致
Runtime Error
的原因吗? -
7.递归有明确的返回条件吗?递归哪里可能会出现死循环?
-
8.
while
,for
循环是导致死循环的原因吗? -
9.二分法时,如果涉及到比较精度的问题,可能会因为精度而导致死循环。
-
10.可能在有些变量没有初始化时,导致本地运行和评判机运行出现偏差。从而出现明明“对的程序”无法AC
-
11.二分法的边界一定要搞清楚!十分重要
-
12.一些常数的取法是否有问题?比如说
π
是取成3,还是3.14,还是3.1415926?如果题意没有说明,那么这些就是容易出现问题的地方。 -
13.贪心算法能证明出来是正确的吗?如果不能,请谨慎使用,否则在你不仅WA了答案,还浪费了时间。
给我这样的感受的题有很多,比如背包问题(让你求出平均价值最大的放法,如果是直接按照 价值/体积 形成的权重来从大到小放,是会出现问题的。);再比如络谷 P1514 引水入城 问题,这里是我的关于此题的题解,我最开始的想法是: dfs+贪心(失败)。直到WA之后才恍然大悟自己是多么幼稚! -
14.
--y
与y--
是有很大区别的。
第一种写法是先判断下一个,第二个是先判断当前的情况,再将y的值减一。即使是高手,有时候也会在这个简单的问题上犯糊涂,一定要注意! -
15.如果数组没有初始化,很有可能得到错误的答案。如果数组初始化不够(意思就是只初始化了一部分的数组),也会导致错误的答案。
比如,有如下语句:
const int maxN = 105;
const int maxV = 100005;
int f[maxV];
fill(f,f+maxN,0);
当计算 f[i]
的值的时候,就很容易出现问题。这是一个很隐蔽的错误,而且不易察觉,大多数时候,我们都以为是自己写的代码或者是思想错了,哪知道问题在于这个细节问题!因为我们申请的是 maxV
大小的 int
型数组,结果只初始化了一部分(maxN大小),导致WA!我就曾因为这个问题浪费了30min 来找bug!【蒟蒻实证!】
-
16.复制得到的代码一定要对具体的条件做修改,否则很难发现这种隐蔽的问题。常见的复制代码的情况有:
01.朝不同的方向【N,S,E,W】行驶,比如迷宫问题,棋盘问题等等。
02.switch...case
语句中的条件
03.if...else if...else
中语句的条件 -
17.代码顺序的颠倒可能会导致严重的错误。
【20200410】我在写一道dijkstra
算法题时,计算最短路径的个数。但是因为搞反了两条语句,就几乎WA了大半的结果。编码十分钟,调试2小时!!血的教训。写代码时一定要全神贯注,切莫开小差! -
18.不要拿到一道题就开始编代码, 一定要先动脑,再动手!否则就是编码10分钟,调试1小时。
-
19.做题有大致的步骤:
(1)抽象题目。对应于学过的什么知识点,主要考什么?
(2)分析时间复杂度,该用什么样的算法解决这个问题?【这一步骤很关键,我经常把贪心的题搞出dp,然后怎么也推导不出来dp的转移方程,后来才知道应该是O(N)复杂度的贪心。-_-||】 -
20.昨天【20200620】在写洛谷的一道题【SP4033 PHONELST - Phone List 】的时候,我因为把输出放到main外的void函数之中,导致在调用这个void函数中可能会多次输出同一个答案,从而导致输出不对。
因为一份电话名单可能会出现多个重复前缀的情况,如果这种输出放在build()函数里,就会出现错误!这个bug 我找了1h+。
-
21.今天【20200621】在写洛谷的一道题【P3879 [TJOI2010]阅读理解】时,碰到一个bug,我觉得我写trie树应该是很熟练的了,但就是死活过不了…就是下面这种情况:
于是,我想啊想,难道是因为我数组开的范围不够大? 代码内容如下:
在蹦出这个念头之后,我细想了一下,好像真有问题。而且对于这种二维数组,这种越界问题,就会导致**“修改数据”** 的情况。将endF[maxN]maxN]
修改为endF[maxN][maxM]
就可以一下全部AC了。【因为题目中说一篇短文的字数<103,而且每个单词的长度小于20。所以在去掉部分空格之后,即使没有任何公共的节点,都可以在20000内将所有节点全部容纳!所以可以这么定义一个二维数组。】
跟随上面这个问题,我们来看一个很好玩儿的东西。
#include<iostream>
using namespace std;
const int maxN = 10;
int arr[maxN][maxN]; //
int tot = 0; //记住tot不要超过100就行
int main(){
for(int i = 0;i<10;i++)
for(int j = 0;j< 10;j++)
arr[i][j] = 1;//全部赋值为1
//下面见证一下“奇迹”
int cnt = 1;
for(int i = 0;i<5;i++){
/*主要j的取值 =>
01.因为这里j的取值越界了,但是整个二维数组的值
却没有越界,所以你会得到Wrong Answer的界面,但是得不到Runtime Error
的提醒。
02.同时,可以看到这里的赋值手法有点儿“一维”状态空间的意思。
03.在起初的位置上,往后推20个。比如arr[4][20]就是在arr[4][0]后推20个,
这样得到的结果就是arr[4][0] = 81,…… arr[4][20] = arr[5][10] = 100;
*/
for(int j = 0;j<20;j++){
arr[i][j] = cnt ;
cnt ++;
}
}
for(int i = 0;i< 10;i++){
for(int j = 0;j< 10;j++){
printf("%4d",arr[i][j]);
}printf("\n");
}
}
执行结果如下:
2.define
使用不当
#define N 100000000001
define 定义的值不能过大,否则会到值编译错误,停止工作。
#define
的用法不仅仅局限于对数据,同样对于字符串,以及其他的都是适用的,比如以下的程序。
#include <stdio.h>
#define P printf("I Love programming\n");
int main()
{
P;
return 0;
}
如果觉得define
不好使,也可以使用const来定义静态变量。
3. 未引用正确的方法库
报错:[Error] 'strcmp' was not declared in this scope
需要针对具体的报错信息添加头文件。比如在上面的这个报错信息中,就需要添加
#include<cstring>
【c++】或者是#include<string.h>
【c】,cstring
表示的是使用c语言中常用的string函数,这和<string>
库是不同的。
4.整型的范围【非常重要!】
算法题中经常会出现整数范围的考察,这是一个非常基础但重要的知识点,务必掌握。这里我讲解一些最常用的整型。
4.1 int
范围大小
int
声明的数据是带正负号的整数,一般情况其占内存大小是4B
,即32位,有一位是符号位,故最大正整数是2^31-1
,最大负数是2^31
【这与计算机的存储方式有关,因为是二进制补码存储】。在 #include<climits>
头文件中可以看到int的最大值和最小值。代码如下所示:
#include<iostream>
#include<climits>
using namespace std;
int main(){
cout << "INT_MAX="<< INT_MAX<<"\n";
cout << "INT_MIN="<< INT_MIN<<"\n";
}
4.2 long long
的范围大小
同样的道理,我们可以获取到long long
类型的最大、小值。
4.3 其它
这个库里 (应该) 还有很多其它好东西,值得研究。
4.4 关于cpp
中的一个整数是什么类型的讨论
使用如下代码测试:
int main(){
cout << sizeof(1)<<"\n";
cout << sizeof(1ll)<<"\n";
}
执行结果如下:
可以看到所占内存大小分别是4B
和8B
。
我就是因为这个问题没搞清楚,在写一道程序题时就出现了问题。本来可以用long long
存储的数,我自己硬是写了一个高精!
5.二叉树相关常见错误
5.1 对指针理解不够
如下代码是我在为每个节点设置树高。但是其中有一处很明显的错误。
void btHigh(node* root)
{
if (root == NULL){
root->height = 0;
return ;//一定不要少了这个边界条件
}
btHigh(root->lchild);
btHigh(root->rchild);
//二叉树的高度为左子树和右子树中高的那个高度加1
int ret1 = root->lchild->height;
int ret2 = root->rchild->height;
root->height = ret1 > ret2 ? ret1 + 1 : ret2 + 1;
}
里面存在的错误代码是:
if (root == NULL){
root->height = 0;
return ;//一定不要少了这个边界条件
}
root->height = 0
是典型的低级错误,怎么能在root=NULL
的条件下给root->height
赋值呢? 也就是说,if
中的判断是要有意义的,不是白判断的。
6. Reference to ' ' is ambiguous
- 报
Reference to ' ' is ambiguous
错,而代码没有错误时,大概率是因为自定义的函数名和引用文件中的函数名冲突了。建议替换一个较为复杂且不通用的函数名即可。