写代码的一些常识
1 关于常识: 2 1、参数为指针类型,为了鲁棒性,就要首先判断指针是否NULL, 3 ---若函数返回值不是bool或者int型或者指针类型,则就判断指针不为NULL; 4 ---若函数返回值是bool或者int型或者指针类型,则就判断指针为NULL; 5 参数为vector<int> array,则需要计算出int len = array.size(),然后判断len的大小,从而实现鲁棒性 6 2、二维数组的行和列 7 ---若用一维数组存放二维数组时,则前提必须已知行rowCount和列colCount; 8 ---若用vector<vector<int>> array保存二维数组时,行数为rowCount = array.size(), 列数为colCount = array[0].size(); 9 3、关于链表常识 10 //链表是一种动态数据结构,再创建链表时,无需知道链表的长度。 11 //插入链表时,分配内存(每添加一个)--调整指针指向---使其插入到链表中。 12 //遍历链表是从头结点遍历的,但是要备份头结点,用临时结点去遍历 13 //首先必须会定义一个链表 14 struct ListNode 15 { 16 int val; 17 struct ListNode *next; 18 }; 19 4、关于树的常识 20 ---首先定义一个二叉树的结点 21 struct BinaryTreeNode 22 { 23 int value; 24 BinaryTreeNode* m_pLeft; 25 BinaryTreeNode* m_pRight; 26 } 27 然后定义保存树的容器--前序和结束的容器,中序和结束容器; 28 创建二叉树,定义二叉树的头结点指针 29 { 30 TreeNode* root = new TreeNode();//根据构造函数确定形式 31 root->val = rootValue; 32 root->left = root->right = NULL; 33 } 34 5、vector容器是可以直接用下标进行索取元素; 35 6、参数是指针类型表示--用指针充当数组,前提必须知道数组长度 36 7、递归和函数调用的区别 37 递归由于函数调用自身,而函数调用是有时间和空间的消耗的---每一次函数调用都需要在内存栈中分配空间以保存参数、返回地址及临时变量,而且往栈中压入数据和弹出数据都需要时间。 38 因而递归实现的效率就不如循环。 39 递归中有可能很多计算是重复的,从而对性能带来很大的负面影响。递归的本质---是把一个问题分解成2个或者多个小问题,如果多个小问题存在相互重叠部分,那么就存在重复的计算。 40 递归可能出现栈溢出。每一次函数调用在内存栈中分配空间,而每个进程的栈的容量是有限的。当递归调用的层级太多时,就会超出栈的容量,从而导致调用栈溢出。 41 8、如何定义一个二维数组 42 int **doubleArray; 43 int num = 6; 44 doubleArray = new int*[num]; 45 for(int i = 0; i<num; i++) 46 { 47 doubleArray[i] = new int[num]; 48 } 49 9、求斐波那契数列时,从最小开始计算,然后总结规律推导出通用公式,然后就可以直接写出代码。通用公式为最简公式。 50 10、位运算符 51 左移运算符<< 52 --m<<n表示把m左移n位,最左边的舍弃,右边补0 53 右移运算符>> 54 --m>>n表示把m右移n位,最右边的舍弃,左边的分情况; 55 --若数字是一个无符号数值,用0填补最左边的n位, 56 --若数字是一个有符号数值,用数字的符号位填补最左边的n位。 57 11、判断两个浮点数是否相等或者一个浮点数是否等于0 58 double num1, num2; 59 if(num1-num2 > -0.0000001 && num1 - num2 < 0.0000001) 60 { 61 return true; 62 } 63 return false; 64 12、判断一个数是否是奇数和偶数,要用与运算符 65 int count = num; 66 判断它不是偶数--(num & 0x1)!= 0; 67 判断它不是奇数--(num & 0x1)== 0; 68 13、一维指针若是表示一维数组,则数组的起始位置和终端位置表示如下: 69 int *pData; 70 int length;//数组的长度 71 int *pBegin = pData; 72 int *pEnd = pData + length -1; 73 14、vector使用元素时,不仅可以用下标运算符指定某一元素,而且还可以使用迭代器指向某一元素。 74 最好使用迭代器,因为要使用vector的操作时,很多参数类型都是迭代器。例如erase(iteator类型) 75 15、 vector<int>::iterator pEnd = array.end(); 76 while(pBegin != array.end())//注意此处不可以用pEnd代替array.end()。否则会发生栈溢出。 77 { 78 pBegin = array.erase(pBegin);//erase会造成迭代器pEnd失效,故不能将array.end()等同 79 } 80 16、关于队列,判断条件是否为空 81 while(!queue.empty()) 82 队列的头元素为queue.front() 83 17、清除vector的内存,应该用swap()而不是clear()操作。 84 18、实现代码的通用性,在判断奇数和偶数、正负数排序等时,记得用函数指针 85 typedef bool (*func)(int); 86 bool isEven(int n) 87 { 88 return (n & 1) != 0; 89 } 90 func = isEven; 91 func(5); 92 19、对链表进行遍历时;要判断链表结点的下个结点是否为空 93 for (int i = 0; i < k-1; ++i) 94 { 95 if (pAhead->next != NULL) 96 { 97 pAhead = pAhead->next; 98 } 99 else 100 { 101 return NULL; 102 } 103 } 104 20、什么是尾结点?自然是next为NULL的时候。 105 21、对链表进行遍历操作时,要对链表进行移动操作,要注意将当前节点转为先前节点,下一个节点转为当前节点 106 while(pNode != NULL) 107 { 108 ListNode* pNext = pNode->next; 109 if (pNext == NULL)//表示只有头结点的时候 110 { 111 pReverseHead == pNode;//反转链表的头结点就是之前的头结点 112 } 113 pNode->next = pPrev;//当前结点的方向反转 114 pPrev = pNode;//当前结点变成了之前的结点 115 pNode = pNext;//下一个节点变成了当前节点1 116 } 117 22、注意递归的时候,不需要备份节点然后进行使用以及返回, 118 但是转为循环的时候,必须将遍历的节点进行备份,然后返回源节点 119 23、看到树,二话不说,先建立树节点的结构 120 struct TreeNode 121 { 122 int val; 123 struct TreeNode* left; 124 struct TreeNode* right; 125 TreeNode(int x):val(x), left(NULL), right(NULL) 126 { 127 128 } 129 }; 130 24、首先考虑的就是范围,应该定义为long类型。关于表达一个大数问题,最常用的方法就是用字符串或者数组表达大数。 131 25、如何在字符串表达的数字上模拟加法,二是把字符串表达的数字打印出来。 132 字符串中的个位+1操作; 133 bool Increment(char* number) 134 { 135 bool isOverFlow = false;//是否溢出 136 int nTakeOver = 0; 137 int nLength = strlen(number); 138 for (int i = nLength-1; i >= 0; --i) 139 { 140 int nSum = number[i] - '0' + nTakeOver;//当前位+进位标识符 141 if (i == nLength-1) 142 { 143 nSum++; 144 } 145 if (nSum >= 10)//最低位大于10 146 { 147 if (i == 0) 148 { 149 isOverFlow = true;//已经达到最大值 150 } 151 else 152 { 153 nSum -= 10; 154 nTakeOver = 1;//进位标识符 155 number[i] = '0'+nSum; 156 } 157 158 } 159 else 160 { 161 number[i] = '0'+nSum; 162 break; 163 } 164 } 165 return isOverFlow; 166 } 167 26、如何打印用字符串表示的数字。 168 void PrintNumber(char* number) 169 { 170 bool isBegining0 = true; 171 int nLength = strlen(number); 172 for (int i = 0; i < nLength; ++i) 173 { 174 if(number[i] != '0') 175 { 176 printf("%c", number[i]); 177 } 178 } 179 } 180 27、如何用在堆上创建一个n维数组 181 char* number = new char[n+1]; 182 number[n] = '\0'; 183 但是别忘了 184 delete[] number; 185 28、 186 关于如何使用递归策略来处理程序? 187 1、结束条件---递归什么时候结束; 188 2、非结束的条件--如何由情况n变成情况n+1,即非结束的条件, 189 3、初始条件---递归调用以什么样的初始条件开始。 190 综上,写出一个递归函数。 191 数学归纳法,对使用递归很重要。 192 不要试图去解决问题,而只解决n=1的问题。 193 29、如果链表中只有一个节点,我们要删除链表的头结点也是尾结点,此时我们再删除节点之后,还要把链表的头结点设置为NULL。 194 30、遇到栈,首先要定义栈,然后再做辅助栈 195 stack<int> mStack; 196 备份栈 197 stack<int> tmpStack; 198 一般循环结束条件尾mStack.size()或者mStack.empty() 199 31、先进先出原则,要考虑到队列,循环结束条件通常是 200 queue<元素的类型> mQueue; 201 mQueue.size(), mQueue.empty() 202 32、通常将栈和队列作为辅助的时候,循环结束条件通常是.empty() 203 33、看见树,想到分左右,左右就要想到递归操作。 204 34、首先了解什么是路径? 205 路径是从根节点出发到叶节点,也就是说路径总是从根节点为起始点,因此需要首先遍历根节点。 206 从而选择前序遍历。保存路径的一个数据结构就是一个栈。 207 对路径查找的时候,若不满足条件,记得要删除其之前的结点,然后进行另一子节点操作。 208 35、分治法:就是把分解之后的小问题各个解决。然后把小问题的解决方案结合起来来解决大问题。 209 通常分治法思路都可以用递归的代码来实现。 210 36、将二叉搜索树转换成排序的双向链表。 211 根节点、左子树和右子树。在把左、右子树都转换成排序的双向链表之后,再和根节点链表起来,整棵二叉搜索树也就转换成了排序的双向链表。 212 常识:由于遍历和转换过程是一样的,故可以想到递归。 213 37、补充树递归的思路:先遍历左子结点,然后递归到最底层之后,对其进行处理操作。然后递归层层返回,然后到根节点,再多根节点进行操作(就是左子结点遍历后的操作,不需要重新写)。最后再处理右子结点。 214 38、若参数类型是string,则鲁棒性代码是 215 if(string.size()==0) 216 { 217 return ""; 218 } 219 39、对组合进行排序的时候,输出结果注意要全排序。 220 容器中string类型有排序操作sort(迭代器1,迭代器2) 221 40、对于一个链表进行遍历即操作的时候 222 步骤如下: 223 先判断条件---头结点的下一个结点是否为NULL 224 再将循环条件的结点进行备份, 225 再做相应处理的操作; 226 然后将备份结点改变成当前要处理的结点; 227 41、成熟的O(n)算法--Partition函数的O(n)的算法--得到数组中任意第K大的数字 228 找任意的第K大的数字,就要想到快排。 229 int index = Partition(array[], n, int start, int end); 230 while(index != K-1) 231 { 232 if(index > k-1) 233 { 234 end = index-1; 235 Partition(array[],n, start, end); 236 } 237 else 238 { 239 start = index+1; 240 Partition(array[], n, start, end); 241 } 242 } 243 42、在定义函数参数的时候,记得要用引用类型。 244 partion(vector<int>& input, int beg, int end) 245 43、使用vector容器的时候,添加元素的时候,不要总是使用push_back(); 246 还可以使用迭代器, 247 例如:vector<int> tmpVec(input.begin(), input.begin() + k); 248 44、 249 常识:想到二叉树来解决问题,就要想到用什么二叉树为好? 250 如该题就是每次都需要找到K个整数中最大的数字,很容易想到最大堆。在最大堆中,根节点的值总是大于它的子树中任意结点的值。 251 最大堆常识--在O(1)得到已有的K个数字中的最大值,但需要O(logk)时间完成删除和插入操作。 252 253 也可以用红黑树来代替最大堆来实现容器。红黑树通过把结点分为红黑两种颜色并根据一些规则确保树在一定程度上是平衡的,从而保证在红黑书中查找、删除和插入都只需要O(logk)时间。 254 44、 255 typedef multiset<int, greater<int>> intset; 256 typedef multiset<int ,greater<int>>::iteator setIterator; 257 intset hasKDigits; 258 使用set或者map中最好先类型别名,然后在定义容器以及迭代器。使用容器之前,先对容器进行清空操作。 259 hasKDigits.insert(元素),插入操作; 260 hasKDigits.erase(迭代器),删除操作; 261 262 45、当返回类型是vector<int>时,处理鲁棒性问题的时候,可以在函数中 263 写return{} 或者写成return vector<int>{} 264 46、在STL中有sort算法---该算法的时间复杂度为O(nlogn) 265 --在一些容器中常用,vector 266 sort(迭代器1,迭代器2),对[迭代器1, 迭代器2)进行排序操作 267 47、不要总是使用push_back,来对vector进行填充,还可以使用初始化方式和迭代器进行操作 268 返回匿名vector 269 return vector<int>(hasKDigits.begin(), hasKDigits.end()) 270 48、 271 在条件判断语句中,不要 272 // iterEnd = iter.end(); 273 // if(iter != iterEnd);而应该写成if(iter!=iter.end()) 274 // 原因,是因为你在循环条件语句中做迭代器操作,会使迭代器失效。 275 if (*iter < *(hasKDigits.begin())) 尽量不要写if (*iter < *(iterGreatest)) 276 //原因,在set容器中被删除的迭代器会失效。即不能使用erase(iter++),而应该使用 277 //for( iter = c.begin(); iter != c.end(); ) 278 //iter = c.erase(iter); 279 return {};可以写成return vector<int>(); 280 49、使用STL中的最大堆时 281 建立堆,首先要堆原始数据进行备份,通过备份数据建立堆 282 vector<int> res(input.begin(),input.begin()+k); 283 //建最大堆 284 make_heap(res.begin(),res.end()); 285 //在堆中删除数据--要先调用pop_heap,再在容器中删除数据 286 pop_heap(res.begin(),res.end()); 287 //在堆中添加数据-要先在容器中加入数据,再调用push_heap () 288 push_heap (_First, _Last) 289 //堆排序--排序之后就不再是一个合法的heap了 290 sort_heap(_First, _Last) 291 292 50、如何将本次循环与上次循环的结果进行比较,做取舍? 293 int maxSum =array[0] ; 294 int result = array[0]; 295 if (result > maxSum ) 296 { 297 maxSum = result; 298 } 299 51、判断一个数的个位是不是1,通过对10求余数进行判断。 300 52、sprintf(strN, "%d", n);把整数n打印成一个字符串保存在strN中 301 eg://把整数123 打印成一个字符串保存在s 中。 302 sprintf(s, "%d", 123); //产生"123" 303 可以指定宽度,不足的左边补空格: 304 sprintf(s, "%8d%8d", 123, 4567); //产生:" 123 4567" 305 当然也可以左对齐: 306 sprintf(s, "%-8d%8d", 123, 4567); //产生:"123 4567" 307 也可以按照16 进制打印: 308 sprintf(s, "%8x", 4567); //小写16 进制,宽度占8 个位置,右对齐 309 sprintf(s, "%-8X", 4568); //大写16 进制,宽度占8 个位置,左对齐 310 53、atoi (表示 ascii to integer)是把字符串转换成整型数的一个函数 311 54、 C++ 排序函数 sort(),qsort()的用法 312 ---Sort(start,end,排序方法) 313 ---void qsort(void*base,size_t num,size_t width,int(__cdecl*compare)(const void*,const void*)); 314 各参数:1 待排序数组首地址 2 数组中待排序元素数量 3 各元素的占用空间大小 4 指向函数的指针 315 55、函数原型: 316 string to_string (int val); 317 string to_string (long val); 318 string to_string (long long val); 319 string to_string (unsigned val); 320 string to_string (unsigned long val); 321 string to_string (unsigned long long val); 322 string to_string (float val); 323 string to_string (double val); 324 string to_string (long double val); 325 功能: 326 将数值转化为字符串。返回对应的字符串。 327 56、MIN(number1,number2, ...)Number1, number2,... 是要从中找出最小值的 1 到 30 个数字参数。 328 57、vector<int> tmpVec(index);//分配容器大小,并初始化为0 329 58、三个有序组合成一个有序数组思想很好 330 tmpVec[i] = min(tmpVec[t2]*2, min(tmpVec[t3]*3, tmpVec[t5]*5));//保存丑数 331 if (tmpVec[i] == tmpVec[t2]*2) 332 { 333 t2++; 334 } 335 if (tmpVec[i] == tmpVec[t3]*3) 336 { 337 t3++; 338 } 339 if (tmpVec[i] == tmpVec[t5]*5) 340 { 341 t5++; 342 } 343 59、string类型 344 if (str == "") 345 { 346 return -1; 347 } 348 或者 349 if (str.empty()) 350 { 351 return -1; 352 } 353 60、 354 举例: 355 map<char, int> hash1; 356 for (auto x : hash1) 357 { 358 if (x.second == 1) 359 { 360 if (hash2[x.first] < min_index) 361 { 362 min_index = hash2[x.first]; 363 } 364 } 365 } 366 基于范围的for循环后的括号由冒号“:”分为两部分,第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。 367 注意:auto不会自动推导出引用类型,如需引用要加上& 368 auto& :修改 369 auto:不修改, 拷贝对象 370 基于范围的循环在标准库容器中时,如果使用auto来声明迭代的对象的话,那么该对象不会是迭代器对象,而是解引用后的对象。 371 372 continue与break的作用与原来的for循环是一致的。 373 374 使用条件: 375 (1)for循环迭代的范围是可确定的:对于类,需要有begin()与end()函数;对于数组,需要确定第一个元素到最后一个元素的范围; 376 (2)迭代器要重载++; 377 (3)迭代器要重载*, 即*iterator; 378 (4)迭代器要重载== / !=。 379 380 对于标准库中的容器,如string, array, vector, deque, list, queue, map, set,等使用基于范围的for循环没有问题,因为标准库总是保持其容器定义了相关操作。 381 382 注意:如果数组大小不能确定的话,是不能使用基于范围的for 循环的。 383 61、 384 INT_MAX = 2147483647 385 INT_MIN = -2147483648 386 62、如何在堆上定义一个内存 387 int *space =(int*)malloc(sizeof(int)max); 388 如何求出一个数组的长度 389 int array[] = {1,2,3,4,5,6}; 390 int len = sizeof(array)/sizeof(*array); 391 63、当出现2个链表的时候,记得用两个指针分别进行对齐遍历和比较。 392 或者是求两个链表长度之差。然后让长的先遍历--两个链表长度之差,短的后遍历。 393 64、已知数组是排序的,则马上要想到二分查找。 394 65、如果用递归对数组进行操作,前半段和后半段 395 则若参数类型是指针类型,则就已知长度,以及开始和末尾参数 396 若参数类型是vector<int> ,则不需要知道容器的长度,但是要知道开始和末尾的下标参数。 397 66、二分查找先操作,在递归 398 组合先递归在操作 399 67、与或一起操作的时候要记得带括号
在代码的世界尽情的翱翔吧!