├── QR.png ├── README.md ├── pics ├── README.md └── 完全平方数.png └── 思维导图.jpg /QR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YouLookDeliciousC/Clearest-LeetCode-Cpp-Solutions/15d13b25f50cd4f4aa427f852cb781838f4242d0/QR.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :cat2: Clearest-LeetCode-Cpp-Solutions 2 | Clearest LeetCode C++ Solutions. This project is intended to clarify the problem solving ideas. 我们追求的目标:清晰简单的思路 + 畅快巧妙的代码 3 | 4 | # 前言 5 | - 本项目意在整理笔者在刷 leetcode 中各大题型解题思路 😉,帮助短时间内捋顺 C++ 编程知识体系、掌握常规通用、简单有效的解题方法,理解并记忆一些避免代码冗余的黑科技模块。 6 | - 项目持续更新中,优先使用 C++,不支持的题目使用 C 代替,如果您有希望分享的清晰解法欢迎联系更新~ [直接发issue 或 fork,记得留下署名和联系方式 :bear:] 鉴于追求的主题,本项目收录题解需满足:1.思路简单清晰,容易理解 2.代码轻巧,不冗余 3.执行效率较高,时间复杂度低 7 | - 水平有限,若您发现已存在的代码中如有冗余部分,欢迎 issue 或 PR。 8 | - 另外这里有一份[ 🐍 Python 最短题解](https://github.com/cy69855522/Shortest-LeetCode-Python-Solutions),带您体验 python 中各种让人叹为观止的奇巧解法,如果您对俩门语言都感兴趣的话,同时服用效果更佳。 9 | - 欢迎加入QQ交流群:902025048 [∷二维码](QR.png) 群内提供更多相关资料~ 10 | - 推荐使用【Ctrl + F】按键搜索题号 11 | # 专题探索 12 | ![](思维导图.jpg) 13 | 14 | 以上是一张互联网公司面试中经常考察的问题类型总结的思维导图,此栏目将根据 LeetCode 中文版探索板块给出的路线制作题解,各专栏将尽力覆盖各大知识要点并总结知识点和套路。相比于[题库解析](#题库解析)部分追求代码的绝对精简,本专题追求以**高可读性**呈现各大专题的**常规思路**,为后续的题库解析部分做铺垫。俩部分题目可能重复,但专题部分会有更详细的解析,且可能运用不同解法。 15 | 16 | ## 数据结构,说难也不难 17 | ### [队列 & 栈](https://leetcode-cn.com/explore/learn/card/queue-stack/) 18 | - :black_joker:【知识卡片】**队列**中的数据呈线性排列,就和“队列”这个名字一样,把它想象成排成一 队的人更容易理解。在队列中,处理总是从第一名开始往后进行,而新来的人只能排在队尾。像队列这种最先进去的数据最先被取来,即“先进先出”的结构,我们称为 First In First Out,简称 FIFO 19 | - :black_joker:【知识卡片】**栈**也是一种数据呈线性排列的数据结构,不过在这种结构中,我们只能访问最新添加的数 据。栈就像是一摞书,拿到新书时我们会把它放在书堆的最上面,取书时也只能从最上面的新书开始取。Last In First Out,简称 LIFO 20 | - :black_joker:【知识卡片】**广度优先搜索 BFS **是一种对图进行搜索的算法。假设我们一开始位于某个顶点(即起点),此 时并不知道图的整体结构,而我们的目的是从起点开始顺着边搜索,直到到达指定顶点(即终 点)。在此过程中每走到一个顶点,就会判断一次它是否为终点。广度优先搜索会优先从离起点近的顶点开始搜索,这样由近及广的搜索方式也使得。根据 BFS 的特性,其常常被用于 `遍历` 和 `搜索最短路径` 21 | - :tophat:【套路】**广度优先搜索一般流程** 22 | ``` 23 | # 1.初始化队列 24 | # 2.选择合适的根节点压入队列 25 | 26 | # 3.使用 while 进入队列循环,直到搜索完毕 27 | # { 28 | # 4.取出一个节点 29 | # 5.放入这个节点周围的节点 30 | # } 31 | ``` 32 | 33 | **队列:先入先出的数据结构** 34 | #### [622. 设计循环队列](https://leetcode-cn.com/problems/design-circular-queue/) 35 | ```cpp 36 | 代码 37 | ``` 38 | - 解析 39 | 40 | **队列和广度优先搜索** 41 | #### [200. 岛屿的个数](https://leetcode-cn.com/problems/number-of-islands/) 42 | ```cpp 43 | class Solution { 44 | public: 45 | bool inGrid(vector>& grid, pair a) //判断点是否超出边界 46 | { 47 | if(a.first >= 0 && a.first < grid.size() && a.second >= 0 && a.second < grid[0].size()) 48 | return true; 49 | else 50 | return false; 51 | } 52 | 53 | /* void BFS(vector>& grid, queue>& po) //BFS的过程 54 | { 55 | if(po.empty()) return; 56 | pair temp = po.front(); 57 | po.pop(); 58 | grid[temp.first][temp.second] = '0'; 59 | pair up = {temp.first + 1, temp.second}; 60 | pair down = {temp.first - 1, temp.second}; 61 | pair left = {temp.first, temp.second - 1}; 62 | pair right = {temp.first, temp.second + 1}; 63 | if(inGrid(grid,up) && grid[temp.first + 1][temp.second] == '1') //若拓展出去的点仍在边界之内,且是岛屿的一部分,将其push入队列内,作为之后迭代的起始点 64 | po.push(up); 65 | if(inGrid(grid,down) && grid[temp.first - 1][temp.second] == '1') 66 | po.push(down); 67 | if(inGrid(grid,left) && grid[temp.first][temp.second - 1] == '1') 68 | po.push(left); 69 | if(inGrid(grid,right) && grid[temp.first][temp.second + 1] == '1') 70 | po.push(right); 71 | return BFS(grid,po); 72 | } 73 | */ 74 | int numIslands(vector>& grid) { 75 | int ans = 0; 76 | queue > po; 77 | if(grid.empty()) return 0; //若地图为空,直接返回0 78 | int m = grid.size(); 79 | int n = grid[0].size(); 80 | for(int y = 0; y < m; ++y) //遍历整张地图的每个点 81 | { 82 | for(int x = 0; x temp = po.front(); 93 | po.pop(); 94 | if(grid[temp.first][temp.second] == '0') 95 | continue; 96 | grid[temp.first][temp.second] = '0'; 97 | pair up = {temp.first + 1, temp.second}; 98 | pair down = {temp.first - 1, temp.second}; 99 | pair left = {temp.first, temp.second - 1}; 100 | pair right = {temp.first, temp.second + 1}; 101 | if(inGrid(grid,up) && grid[temp.first + 1][temp.second] == '1') //若拓展出去的点仍在边界之内,且是岛屿的一部分,将其push入队列内,作为之后迭代的起始点 102 | po.push(up); 103 | if(inGrid(grid,down) && grid[temp.first - 1][temp.second] == '1') 104 | po.push(down); 105 | if(inGrid(grid,left) && grid[temp.first][temp.second - 1] == '1') 106 | po.push(left); 107 | if(inGrid(grid,right) && grid[temp.first][temp.second + 1] == '1') 108 | po.push(right); 109 | } 110 | 111 | } 112 | } 113 | } 114 | return ans; 115 | } 116 | }; 117 | ``` 118 | - 这里用queue来实现BFS来解决这道题。使用了queue先入先出的性质 119 | - 想象:先搜索到一个岛屿的某点,先遍历该点的四周,将‘1’转化为0,进入下一个候补点,再遍历四周……以此类推直到遍历完整座岛屿(‘1’) 120 | 121 | #### [752. 打开转盘锁 队列+广度优先搜索](https://leetcode-cn.com/problems/open-the-lock/submissions/) 122 | ```cpp 123 | class Solution { 124 | public: 125 | int openLock(vector& deadends, string target) { 126 | set dead(deadends.begin(),deadends.end()); //收录死锁的数据 127 | set done; //用来记录已经遍历过的数字 128 | string beginstr = "0000"; //记录起始数据 129 | done.insert(beginstr); 130 | 131 | // BFS 从这里开始-------------------------------------------------------------- 132 | queue>record; //创建队列记录当前深度和节点 133 | record.push({0,beginstr}); 134 | 135 | if (dead.count(beginstr)) return -1; //如果死锁数据中包含起始数据,直接返回-1 136 | 137 | while(!record.empty()) //用BSF遍历所有可能的数据 138 | { 139 | auto tmp=record.front(); //提取队列中第一个数据 140 | record.pop(); 141 | 142 | if (tmp.second==target) return tmp.first; //将目标的判断放在加减操作之前,还可以判断起始数据是否是目标数据,较严谨 143 | for (int i=0; i<4; i++) //对提取出来的数据的每一位做+1和-1的操作 144 | { 145 | string spls = tmp.second; 146 | string ssbs = tmp.second; 147 | spls[i] = (spls[i] - '0' + 1) % 10 +'0'; //用mod来处理循环的数字 148 | ssbs[i] = ((ssbs[i] - '0') + 9) % 10 + '0'; 149 | if (!dead.count(spls) && !done.count(spls)) //若加一后的数据不存在死锁集合中,且不是之前遍历过的数据 150 | { 151 | record.push({tmp.first+1, spls}); //加到record队列中 152 | done.insert(spls); 153 | } 154 | if (!dead.count(ssbs) && !done.count(ssbs)) //若减一后的数据不存在死锁集合中,且,不是之前遍历过的数据 155 | { 156 | record.push({tmp.first+1, ssbs}); // 加到record队列中 157 | done.insert(ssbs); 158 | } 159 | } 160 | } 161 | return -1; //若无法得到目标密码,返回-1 162 | } 163 | }; 164 | 165 | ``` 166 | - 使用BFS解题的关键:1.根节点是什么?周围节点是哪些?什么时候停止? 167 | - 本题通过使用BFS,将问题转化为一个图,每一个点都与其它八个点相连接e.g. 0000 与0001,0009,0010,0090,0100,0900,1000,9000八个点相连,通过对每一个点的每一位数进行+1和-1的操作获得新的点,判断新的点是否在死锁集合内 168 | - dead集合的作用有两点:①题目给的deadends变量是数组类型的,每次判断某个目标是否在其中需要遍历一边数组,时间复杂度O(N),而dead变量是set类型的,内部实现是哈希表,每次根据key取value值的时间复杂度是O(1),快非常多。 ②因为题目给出的deadends中有很多重复的内容,所以判断的时候重复的值也搜索一遍更慢了,使用set可以把所有重复的值去掉。 169 | - 若不设置集合储存遍历过的点,会产生无限循环。 170 | - 本题使用mod和ASCII码来处理9->0和0->9的转换;也可设置两个flag1,flag2,当flag==‘9’+1或‘0’-1时,对答案进行重新赋值。 171 | - 可以通过first和second的调用来分别访问队列对中的数据; continue仅跳出一层循环一次。 172 | #### [279. 完全平方数 队列+广度优先搜索](https://leetcode.com/problems/perfect-squares/) 173 | ```cpp 174 | class Solution { 175 | public: 176 | int numSquares(int n) { 177 | vector f (n+1,-1); //初始化 构造一个数组,容量为n+1,所有空间初始化为-1 178 | f[0] = 0; //组成0的完全平方数的个数为0 179 | 180 | //BFS从这里开始------------------------- 181 | queue q; 构造一个队列储存nodes,等待处理 182 | q.push(0); 183 | while (!q.empty()) 184 | { 185 | int m = q.front(); //取出第一个数据 186 | q.pop(); 187 | for(int i = 1; i*i+m <=n; i++) //最小的完全平方数是1 188 | { 189 | if(f[i*i+m] == -1) //若i*i+m这个数字还没有出现过 190 | { 191 | f[i*i+m] = f[m] + 1; //加一次完全平方数,步骤加一 192 | q.push(i*i+m); //放入队列,接着搜索 193 | } 194 | } 195 | } 196 | return f[n]; 197 | } 198 | }; 199 | ``` 200 | - 本题使用队列+广度优先搜索来解决, 思维过程:以n=5为例,通过构建树形图形,root是0,通过第一次for循环,构建第一层node,此时m为0。如图 201 | - ![](pics/完全平方数.png) 202 | - 因为队列FIFO的性质,先达到目标的必是最少的个数 203 | 204 | **栈:后入先出的数据结构** 205 | #### [155. Min Stack 栈](https://leetcode.com/problems/min-stack/) 206 | ```cpp 207 | class MinStack { 208 | public: 209 | /** initialize your data structure here. */ 210 | stacks; //主数据栈 211 | stackminn; //辅助数据栈 212 | MinStack() { 213 | 214 | } 215 | 216 | void push(int x) { 217 | s.push(x); 218 | if(minn.empty()||x<=minn.top()) //关键点,见总结 219 | minn.push(x); 220 | } 221 | 222 | void pop() { 223 | if(s.top() == minn.top()) //判断数据栈的顶部是否是当前主数据栈的最小值,若是,需要将两个栈的top都pop掉 224 | minn.pop(); 225 | s.pop(); 226 | } 227 | 228 | int top() { 229 | return s.top(); //返回栈的顶部 230 | } 231 | 232 | int getMin() { 233 | return minn.top(); //返回最小值 234 | } 235 | }; 236 | ``` 237 | - 本题使用双栈,利用栈的后入先出的特性,将最小值推入minn栈,使minn栈的top始终为s栈的最小值。 238 | - 关键点:判断条件为当minn栈为空,或当前数据小于等于minn栈的top值。条件若为true,则将当前数据也push到minn栈中。若判断条件改为‘小于’minn栈的top值,而不是‘小于等于’minn栈的top值时,代码提交失败。问题在于,测试数据可能有大于1个的最小值,若s栈中的最小值个数多于1个,当minn栈和s栈的最小值被pop一个后,s栈中还有之前的最小值,而minn栈中的最小值变得比s栈中的最小值大,答案error。 239 | #### [20. Valid parentheses 栈](https://leetcode.com/problems/valid-parentheses/) 240 | ```cpp 241 | class Solution { 242 | public: 243 | bool isValid(string s) { 244 | if(s.size() % 2 != 0) return false; //若字符串s的字符数不为偶数,直接返回false. 不过没有也可 245 | stack a; 246 | map bra; //建立右括号和左括号之间的映射 247 | bra.insert(pair(')','(')); 248 | bra.insert(pair(']','[')); 249 | bra.insert(pair('}','{')); 250 | int lo; 251 | lo = s.size(); 252 | for(int i = 0; i < lo; i++) 253 | { 254 | if(s[i] == '(' || s[i] == '[' || s[i] == '{') //若是左括号,直接推入栈 255 | a.push(s[i]); 256 | if(s[i] == ')' || s[i] == ']' || s[i] == '}') //若是右括号,先判断栈内是否有左括号 257 | { 258 | if(a.empty()) //见总结* 259 | return false; 260 | if(bra[s[i]] == a.top()) //若右括号与栈顶的左括号匹配,pop掉栈a最上方的左括号,否则返回false 261 | { 262 | a.pop(); 263 | } 264 | else 265 | return false; 266 | } 267 | } 268 | if(a.empty()) //若操作之后,栈内还有字符,返回false 269 | return true; 270 | else 271 | return false; 272 | } 273 | }; 274 | ``` 275 | - 有效的括号,通过建立左右括号之间的映射。使用栈后入先出的性质,压入左括号,若右括号和栈中顶部的左括号匹配,且最终栈内没有字符,返回true 276 | - 当操作右括号前,要判断栈是否为空,若缺少这一步,当栈内为空,右括号需要与空栈的top判断段是否相等的时候,会报错。 277 | ### [739. Daily Temperatures栈](https://leetcode.com/problems/daily-temperatures/) 278 | ```cpp 279 | class Solution { 280 | public: 281 | vector dailyTemperatures(vector& T) { 282 | vector ans (T.size(), 0); //创建与气温天数相同的数组,收录答案 283 | stack res; 284 | for(int i = T.size()-1; i >= 0; --i) //从最后一天开始往前 285 | { 286 | while(!res.empty() && T[i] >= T[res.top()]) res.pop(); //若当前数据大于等于栈顶数据,pop掉栈顶数据直到栈为空或当前数据小于栈顶数据 287 | if(res.empty()) 288 | ans[i] = 0; 289 | else 290 | ans[i] = res.top() - i; 291 | res.push(i); //栈顶始终是整个栈的最小数据 292 | } 293 | return ans; 294 | } 295 | }; 296 | ``` 297 | - 创建一个递减栈 后入栈的元素总比先入栈的元素小。 298 | - 若当前数据比栈top数据小, 则入栈;若当前数据比栈top大,先pop栈,直到当前数据比栈的top数据小,再入栈。 299 | - 索引相减 300 | ### [150. Evaluate Reverse Polish Notation栈](https://leetcode.com/problems/evaluate-reverse-polish-notation/) 301 | ```cpp 302 | class Solution { 303 | public: 304 | int str2num(string s) //将string转换为int类型的函数,在之后要调用 305 | { 306 | int num; 307 | stringstream ss(s); 308 | ss>>num; 309 | return num; 310 | } 311 | int evalRPN(vector& tokens) { 312 | stack s; 313 | int n = tokens.size(); 314 | int ans; 315 | if(tokens[0] != "+" && tokens[0] != "-" && tokens[0] != "*" && tokens[0] != "/") //防止测试数据仅有一个数字 316 | ans = str2num(tokens[0]); 317 | for(int i = 0; i <= n - 1 ; ++i) //运算过程 318 | { 319 | if(tokens[i] == "+") 320 | { 321 | int right = s.top(); 322 | s.pop(); 323 | int left = s.top(); 324 | s.pop(); 325 | ans = left + right; 326 | s.push(ans); 327 | } 328 | else if(tokens[i] == "-") 329 | { 330 | int right = s.top(); 331 | s.pop(); 332 | int left = s.top(); 333 | s.pop(); 334 | ans = left - right; 335 | s.push(ans); 336 | } 337 | else if(tokens[i] == "*") 338 | { 339 | int right = s.top(); 340 | s.pop(); 341 | int left = s.top(); 342 | s.pop(); 343 | ans = left * right; 344 | s.push(ans); 345 | } 346 | else if(tokens[i] == "/") 347 | { 348 | int right = s.top(); 349 | s.pop(); 350 | int left = s.top(); 351 | s.pop(); 352 | ans = left / right; 353 | s.push(ans); 354 | } 355 | else 356 | { 357 | int r = str2num(tokens[i]); //遇到数字直接push 358 | s.push(r); 359 | } 360 | } 361 | return ans; 362 | } 363 | }; 364 | ``` 365 | - 本题使用栈后入先出的性质,当遇到算术运算符时,pop出最近进入栈内的两个数字进行运算。运算后需要将结果push回栈内进行下一次运算。 366 | - 当遇到的不是算术运算符时,那就是数字了。直接push到栈中等待运算。 367 | - 注意:本题的初始数据是string类型,需要将其转换成int类型。 368 | 369 | **栈和深度优先搜索** 370 | ### [200. Number of Islands栈+DFS](https://leetcode.com/problems/number-of-islands/) 371 | ```cpp 372 | class Solution { 373 | public: 374 | bool inGrid(vector>& grid, pair a) //判断点是否超出边界 375 | { 376 | if(a.first >= 0 && a.first < grid.size() && a.second >= 0 && a.second < grid[0].size()) 377 | return true; 378 | else 379 | return false; 380 | } 381 | 382 | void DFS(vector>& grid, stack>& po) //DFS的过程 383 | { 384 | if(po.empty()) return; 385 | pair temp = po.top(); 386 | po.pop(); 387 | grid[temp.first][temp.second] = '0'; 388 | pair up = {temp.first + 1, temp.second}; 389 | pair down = {temp.first - 1, temp.second}; 390 | pair left = {temp.first, temp.second - 1}; 391 | pair right = {temp.first, temp.second + 1}; 392 | if(inGrid(grid,up) && grid[temp.first + 1][temp.second] == '1') //若拓展出去的点仍在边界之内,且是岛屿的一部分,将其push入栈内,作为之后迭代的起始点 393 | po.push(up); 394 | if(inGrid(grid,down) && grid[temp.first - 1][temp.second] == '1') 395 | po.push(down); 396 | if(inGrid(grid,left) && grid[temp.first][temp.second - 1] == '1') 397 | po.push(left); 398 | if(inGrid(grid,right) && grid[temp.first][temp.second + 1] == '1') 399 | po.push(right); 400 | return DFS(grid,po); 401 | } 402 | 403 | int numIslands(vector>& grid) { 404 | int ans = 0; 405 | stack > po; 406 | if(grid.empty()) return 0; //若地图为空,直接返回0 407 | int m = grid.size(); 408 | int n = grid[0].size(); 409 | for(int y = 0; y < m; ++y) //遍历整张地图的每个点 410 | { 411 | for(int x = 0; x & nums, int S, int& count, int counter, int sum) //DFS函数 433 | { 434 | if(counter == nums.size()) 435 | { 436 | if(sum == S) 437 | ++ count; 438 | return; 439 | } 440 | DFS(nums, S, count, counter + 1, sum + nums[counter]); 441 | DFS(nums, S, count, counter + 1, sum - nums[counter]); 442 | } 443 | int findTargetSumWays(vector& nums, int S) { 444 | int count = 0; 445 | int sum = 0; 446 | DFS(nums, S, count, 0, sum); 447 | return count; 448 | } 449 | }; 450 | ``` 451 | - 本题使用DFS来实现,通过计数器counter来记录深度,不断迭代直到遍历完数组内的全部数据。 452 | - 记下符合目标的支路。 453 | ### [94. Binary Tree Inorder Traversal](https://leetcode.com/problems/binary-tree-inorder-traversal/) 454 | ```cpp 455 | class Solution { 456 | public: 457 | vector rest; 458 | vector inorderTraversal(TreeNode* root) { 459 | if(root != NULL) 460 | { 461 | inorderTraversal(root -> left); 462 | rest.push_back(root -> val); 463 | inorderTraversal(root -> right); 464 | } 465 | return rest; 466 | } 467 | }; 468 | ``` 469 | - 考察到一个节点后,将其暂存,遍历完左子树后,再输出该节点的值,然后遍历右子树。(左根右) 470 | ### [232. Implement Queue using Stacks 用双栈实现队列](https://leetcode.com/problems/implement-queue-using-stacks/) 471 | ```cpp 472 | class MyQueue { 473 | public: 474 | /** Initialize your data structure here. */ 475 | stack a; 476 | stack b; 477 | MyQueue() { 478 | 479 | } 480 | 481 | /** Push element x to the back of queue. */ 482 | void push(int x) { 483 | a.push(x); 484 | } 485 | 486 | /** Removes the element from in front of queue and returns that element. */ 487 | int pop() { 488 | int len; 489 | len = a.size(); 490 | for(int i = 0; i < len; ++i) 491 | { 492 | b.push(a.top()); 493 | a.pop(); 494 | } 495 | int cur; 496 | cur = b.top(); 497 | b.pop(); 498 | for(int j = 0; j < len - 1; ++j) 499 | { 500 | a.push(b.top()); 501 | b.pop(); 502 | } 503 | return cur; 504 | } 505 | 506 | /** Get the front element. */ 507 | int peek() { 508 | int len; 509 | len = a.size(); 510 | for(int i = 0; i < len; ++i) 511 | { 512 | b.push(a.top()); 513 | a.pop(); 514 | } 515 | int cur; 516 | cur = b.top(); 517 | for(int j = 0; j < len; ++j) 518 | { 519 | a.push(b.top()); 520 | b.pop(); 521 | } 522 | return cur; 523 | } 524 | 525 | /** Returns whether the queue is empty. */ 526 | bool empty() { 527 | return a.empty() && b.empty(); 528 | } 529 | }; 530 | ``` 531 | - 使用双栈的原因:能通过b栈将原a栈的数据倒置,此时栈的top,也就是队列的peek。可以实现队列的peek和pop操作。操作完再把b栈转换回a栈,这样当再push数据进栈的时候顺序才会正确。 532 | ### [225. Implement Stack using Queues 用队列实现栈](https://leetcode.com/problems/implement-stack-using-queues/) 533 | ```cpp 534 | class MyStack { 535 | public: 536 | /** Initialize your data structure here. */ 537 | queue q1; 538 | queue q2; 539 | MyStack() { 540 | 541 | } 542 | 543 | /** Push element x onto stack. */ 544 | void push(int x) { //保证数据全部push到同一个队列 545 | if(q1.empty()) 546 | q2.push(x); 547 | else 548 | q1.push(x); 549 | } 550 | 551 | /** Removes the element on top of the stack and returns that element. */ 552 | int pop() { 553 | if(q1.empty()) 554 | { 555 | int len = q2.size(); 556 | for(int i = 0; i nums; 633 | stack strs; 634 | int num = 0; 635 | int len = s.size(); 636 | for(int i = 0; i < len; ++ i) 637 | { 638 | if(s[i] >= '0' && s[i] <= '9') 639 | { 640 | num = num * 10 + s[i] - '0'; 641 | } 642 | else if((s[i] >= 'a' && s[i] <= 'z') ||(s[i] >= 'A' && s[i] <= 'Z')) 643 | { 644 | res = res + s[i]; 645 | } 646 | else if(s[i] == '[') //将‘[’前的数字压入nums栈内, 字母字符串压入strs栈内 647 | { 648 | nums.push(num); 649 | num = 0; 650 | strs.push(res); 651 | res = ""; 652 | } 653 | else //遇到‘]’时,操作与之相配的‘[’之间的字符,使用分配律 654 | { 655 | int times = nums.top(); 656 | nums.pop(); 657 | for(int j = 0; j < times; ++ j) 658 | strs.top() += res; 659 | res = strs.top(); //之后若还是字母,就会直接加到res之后,因为它们是同一级的运算 660 | //若是左括号,res会被压入strs栈,作为上一层的运算 661 | strs.pop(); 662 | } 663 | } 664 | return res; 665 | } 666 | }; 667 | ``` 668 | - 这题看到括号的匹配,首先应该想到的就是用栈来解决问题。 669 | - 其次,读完题目,要我们类似于制作一个能使用分配律的计算器。想象:如3[a2[c]b] 使用一次分配律-> 3[accb] 再使用一次分配律->accbaccbaccb 670 | ### [733. Flood Fill 队列+BFS](https://leetcode.com/problems/flood-fill/) 671 | ```cpp 672 | class Solution { 673 | public: 674 | vector> floodFill(vector>& image, int sr, int sc, int newColor) { 675 | queue > olds; 676 | int old = image[sr][sc]; 677 | if(newColor == old) return image; //剪枝,若新颜色和旧色一样,直接返回原来的图像 678 | olds.push({sr,sc}); 679 | //BFS由此开始-------------- 680 | while(!olds.empty()) 681 | { 682 | pair temp = olds.front(); 683 | olds.pop(); 684 | image[temp.first][temp.second] = newColor; //因为放入队列中的像素都是旧色像素,直接变成新色 685 | vector> around = {{0,1},{0,-1},{-1,0},{1,0}}; //该像素四周的像素 686 | for(int i = 0; i < 4; ++i) 687 | { 688 | int y = temp.first + around[i].first; 689 | int x = temp.second + around[i].second; 690 | if(0 <= y && y < image.size() && 0 <= x && x < image[0].size() && image[y][x] == old) //若在图像内,且是旧色,则压入olds队列 691 | olds.push({y,x}); 692 | } 693 | } 694 | return image; 695 | } 696 | }; 697 | ``` 698 | - 本题的目的是在图像中,用新的色块代替旧的色块,图像中或许有一个以上相同颜色的色块,但是只改变输入希望改变的其中一个色块。 699 | - 这题类似于 [岛屿的数量](https://leetcode.com/problems/number-of-islands/) 。差别在于,岛屿的数量要将图中的所有点全部遍历一遍,而本题只需要遍历目标点所在的色块。 700 | - 由于其中一个测试用例的新旧颜色相同,所以需要剪枝,若新旧颜色相同,直接返回原图像。 701 | ### [542. 01 矩阵 BFS](https://leetcode.com/problems/01-matrix/) 702 | ```cpp 703 | class Solution { 704 | public: 705 | vector> updateMatrix(vector>& matrix) { 706 | queue > q; 707 | int m = matrix.size(); 708 | int n = matrix[0].size(); 709 | vector > around = {{0,1},{0,-1},{-1,0},{1,0}}; //周围节点 710 | for(int i = 0; i < m; ++i) 711 | { 712 | for(int j = 0; j < n; ++j) 713 | { 714 | if(matrix[i][j] == 0) q.push({i,j}); //将元素为0 的点推入队列 715 | else matrix[i][j] = INT_MAX; 716 | } 717 | } 718 | while(!q.empty()) 719 | { 720 | pair temp = q.front(); 721 | q.pop(); 722 | for(int b = 0; b < 4; ++ b) //探索周围节点 723 | { 724 | int y = temp.first + around[b].first; 725 | int x = temp.second + around[b].second; 726 | //判断在图内,且新点的元素大于该点元素。 727 | if(0 <= x && x < n && 0 <= y && y < m && matrix[temp.first][temp.second] < matrix[y][x]) 728 | { 729 | matrix[y][x] = matrix[temp.first][temp.second] + 1; 730 | q.push({y,x}); 731 | } 732 | } 733 | } 734 | return matrix; 735 | } 736 | }; 737 | ``` 738 | - 本题求最短路径,首先应该想到使用BFS,然后是与之相配的queue数据结构。 739 | - 以所有的0为起点遍历矩阵,离0越远的点,元素值逐渐增加。 740 | ### [841. 钥匙和房间](https://leetcode.com/problems/keys-and-rooms/) 741 | ```cpp 742 | class Solution { 743 | public: 744 | bool canVisitAllRooms(vector>& rooms) { 745 | set visited; //记录能进入的房间 746 | visited.insert(0); //从第0房间开始逛 747 | stack keys; //记录房间里的钥匙 748 | keys.push(0); //DFS从这里开始-------------- 749 | while(!keys.empty()) 750 | { 751 | int key = keys.top(); //取出钥匙走向房间 752 | cout << key; 753 | keys.pop(); 754 | int rs = rooms[key].size(); 755 | for(int i = 0; i < rs; ++i) //给这个房间里的钥匙做记录, 756 | { 757 | if(!visited.count(rooms[key][i])) //若钥匙通向已知能进入的房间,就不再次不把这个钥匙放进口袋 758 | { 759 | visited.insert(rooms[key][i]); 760 | keys.push(rooms[key][i]); //把这个房间中自己还没有的钥匙放入口袋 761 | } 762 | } 763 | } 764 | return visited.size() == rooms.size(); //如果遍历过的房间数等于实际房间数,返回true 765 | } 766 | }; 767 | ``` 768 | - 使用stack数据结构来实现DFS遍历所有能进入的房间,取到钥匙说明指向的房间能进入,直接放入visited。 769 | - 使用set visited来记录已经走过的房间,set内的元素不重复,且能查找某个元素是否存在于集合内。 770 | - 注意:房间从第0开始。 771 | 772 | **数组和字符串** 773 | ### [724. 寻找数组的中心索引](https://leetcode.com/problems/find-pivot-index/) 774 | ```cpp 775 | class Solution { 776 | public: 777 | int pivotIndex(vector& nums) { 778 | int size = nums.size(); 779 | int lsum = 0; 780 | int rsum = 0; 781 | int ans = -1; //若不存在中心索引,返回初始值 -1 782 | for(int i = 1; i& nums) { 807 | int ans = -1; //若不存在符合要求的元素,返回-1 808 | if(nums.size() == 1) return 0; //若只有一个元素,一定最大,直接返回它的索引 809 | vector copy(nums.begin(),nums.end()); //复制一份数组,用来找到索引 810 | sort(nums.begin(),nums.end(),greater()); //将数组从大到小排序 811 | if(nums[0] >= 2 * nums[1]) //若符合要求 812 | { 813 | for(int i = 0; i < copy.size(); ++i) //查找该元素原本的索引 814 | { 815 | if(copy[i] == nums[0]) 816 | { 817 | return ans = i; //返回索引 818 | } 819 | } 820 | } 821 | return ans; 822 | } 823 | }; 824 | ``` 825 | - 将数组从大到小排列,若位置【0】大于等于位置【1】,就符合条件,然后返回原始的索引(通过备份数组) 826 | ### [66. 加一](https://leetcode.com/problems/plus-one/) 827 | ```cpp 828 | class Solution { 829 | public: 830 | vector plusOne(vector& digits) { 831 | int size = digits.size(); 832 | if(digits[size-1] != 9) //若末位不等于9,正常加一 833 | { 834 | ++digits[size-1]; 835 | } 836 | else //若末位等于9,加一等于0 837 | { 838 | digits[size-1] = 0; 839 | for(int i = size - 1; i >0; --i) //若加完一后若等于0,下一位要进一 如869 840 | { 841 | if(digits[i] == 0) 842 | { 843 | digits[i-1] = (digits[i-1] + 1) % 10; 844 | } 845 | else 846 | break; //若某一位是数不需要进一,跳出循环 847 | } 848 | if(digits[0] == 0) //若到最后最高位也等于0,需要多一位数 如99 + 1 此时为答案为00,进行一下操作 849 | { 850 | digits.insert(digits.begin(),1); //在最高位插入1 851 | } 852 | } 853 | return digits; 854 | } 855 | }; 856 | ``` 857 | - 对于一般的数字,直接在末位加一即可 858 | - 本题特殊的两个点: 1.若加一之后的值为10,需要进一位。 2.若数字为类似999 ,加一之后需要多一位数。使用insert()来实现, insert函数 : vec.insert(begin()+i ,a) 在第i个元素插入a 859 | ### [498. 对角线遍历](https://leetcode.com/problems/diagonal-traverse/) 860 | ```cpp 861 | class Solution { 862 | public: 863 | vector findDiagonalOrder(vector>& matrix) { 864 | vector ans; 865 | if(matrix.empty()) return ans; //若矩阵为空,直接返回空的答案 866 | int m = matrix.size(); 867 | int n = matrix[0].size(); 868 | pair temp = {0,0}; //用来遍历的point 869 | int flag = 1; //记录遍历过程是否过半 870 | while(ans.size() != m*n) 871 | { //upward,这部分描述斜向上 872 | while(0 <= temp.first && temp.second < n) 873 | { 874 | ans.push_back(matrix[temp.first][temp.second]); 875 | -- temp.first; 876 | ++ temp.second; 877 | } 878 | ++ temp.first; 879 | if(flag > n/2) //当遍历过半后,要有额外的操作来将point放到正确的位置 880 | { 881 | -- temp.second; 882 | ++ temp.first; 883 | } 884 | //downward,这部分描述斜向下 885 | while(temp.first < m && 0 <= temp.second) 886 | { 887 | ans.push_back(matrix[temp.first][temp.second]); 888 | ++ temp.first; 889 | -- temp.second; 890 | } 891 | ++ temp.second; 892 | if(flag > m/2) ////当遍历过半后,要有额外的操作来将point放到正确的位置 893 | { 894 | -- temp.first; 895 | ++ temp.second; 896 | } 897 | if(flag == m/2 && m%2==0) //若在中间状态,比较特殊。 898 | { 899 | -- temp.first; 900 | ++ temp.second; 901 | } 902 | ++ flag; 903 | } 904 | return ans; 905 | } 906 | }; 907 | ``` 908 | - 通过题目描述可知,要对一个以特定顺序遍历矩阵的对角线。遍历的顺序是先斜上,再斜下,以此循环。因此我们可以将一次斜上加一次斜下遍历为一个循环。 909 | - |1. 先向斜上方遍历,直到超出矩阵范围,手动将它放到向斜下方向遍历的起始位置。 2. 再向斜下方遍历,直到超出矩范围,最后将它手动放到下一次循环的起始位置 3. 循环1,2步骤,直到答案的数组size 等于 matrix的元素个数| 910 | - 难点在于 1. 当遍历过程过半后,需要增加操作才将它放到下一次行动的起始位置。 2. 注意当遍历过程过半时,该次循环的判断条件,根据数组行数的奇偶而有所不同 911 | ### [54. 螺旋矩阵](https://leetcode.com/problems/spiral-matrix/) 912 | ```cpp 913 | class Solution { 914 | public: 915 | vector spiralOrder(vector>& matrix) { 916 | vector ans; 917 | if(matrix.empty()) return ans; //若数组为空,直接返回答案; 918 | int u = 0; //赋值上下左右边界 919 | int d = matrix.size() - 1; 920 | int l = 0; 921 | int r = matrix[0].size() - 1; 922 | while(true) 923 | { 924 | for(int i = l; i <= r; ++i) ans.push_back(matrix[u][i]); //向右移动直到最右 925 | if(++ u > d) break; //重新设定上边界,若上边界大于下边界,则遍历遍历完成,下同 926 | for(int i = u; i <= d; ++i) ans.push_back(matrix[i][r]); //向下 927 | if(-- r < l) break; //重新设定有边界 928 | for(int i = r; i >= l; --i) ans.push_back(matrix[d][i]); //向左 929 | if(-- d < u) break; //重新设定下边界 930 | for(int i = d; i >= u; --i) ans.push_back(matrix[i][l]); //向上 931 | if(++ l > r) break; //重新设定左边界 932 | } 933 | return ans; 934 | } 935 | }; 936 | ``` 937 | - 这里的方法不需要记录已经走过的路径,所以执行用时和内存消耗都相对较小 938 | - |1. 首先设定上下左右边界 2. 其次向右移动到最右,此时第一行因为已经使用过了,可以将其从图中删去,体现在代码中就是重新定义上边界 3. 判断若重新定义后,上下边界交错,表明螺旋矩阵遍历结束,跳出循环,返回答案 4. 若上下边界不交错,则遍历还未结束,接着向下向左向上移动,操作过程与第一,二步同理 5. 不断循环以上步骤,直到某两条边界交错,跳出循环,返回答案| 939 | ### [118. 杨辉三角](https://leetcode.com/problems/pascals-triangle/) 940 | ```cpp 941 | class Solution { 942 | public: 943 | vector> generate(int numRows) { 944 | vector> ans(numRows); 945 | if(numRows == 0) return ans; //若numRows为空,返回空数组 946 | for(int i = 0; i < numRows; ++ i ) //给数组一个个赋值 947 | { 948 | for(int j = 0; j <= i; ++ j) 949 | { 950 | if(j == 0 || j == i) //若是左右两边的边界,赋值为1 951 | ans[i].push_back(1); 952 | else 953 | ans[i].push_back(ans[i-1][j-1] + ans[i-1][j]); //否则赋值为该位置左上与右上的和 954 | } 955 | } 956 | return ans; 957 | } 958 | }; 959 | ``` 960 | - 杨辉三角即该位置的值为左上角与右上角的和 961 | ### [67. 二进制求和](https://leetcode.com/problems/add-binary/) 962 | ```cpp 963 | class Solution { 964 | public: 965 | string addBinary(string a, string b) { 966 | int al = a.size(); 967 | int bl = b.size(); 968 | while(al < bl) //让两个字符串等长,若不等长,在短的字符串前补零,否则之后的操作会超出索引 969 | { 970 | a = '0' + a; 971 | ++ al; 972 | } 973 | while(al > bl) 974 | { 975 | b = '0' + b; 976 | ++ bl; 977 | } 978 | for(int j = a.size() - 1; j > 0; -- j) //从后到前遍历所有的位数,同位相加 979 | { 980 | a[j] = a[j] - '0' + b[j]; 981 | if(a[j] >= '2') //若大于等于字符‘2’,需要进一 982 | { 983 | a[j] = (a[j] - '0') % 2 + '0'; 984 | a[j-1] = a[j-1] + 1; 985 | } 986 | } 987 | a[0] = a[0] - '0' + b[0]; //将ab的第0位相加 988 | if(a[0] >= '2') //若大于等于2,需要进一 989 | { 990 | a[0] = (a[0] - '0') % 2 + '0'; 991 | a = '1' + a; 992 | } 993 | return a; 994 | } 995 | }; 996 | ``` 997 | - 1. 首先让两个字符串等长,若不等长,在短的字符串前补零,否则之后的操作会超出索引。 998 | - 2. 然后从后到前遍历所有的位数,同位相加,这里有一个点,用的是字符相加,利用ASCII码,字符在内部都用数字表示,我们不需要知道具体数值,但可知‘0’-‘0’ = 0 , ‘0’+1=‘1’,以此类推 。字符的加减,大小比较,实际上都是内部数字的加减,大小比较 999 | - 3. 判断相加后的字符,若大于等于字符‘2’,下一位需要进一 1000 | - 4. 第0位数的相加在这里是单独处理的,因为它可能涉及到字符的插入(即是否需要在最前面加一位数‘1’) 1001 | - 笔记:若内存错误,需要查看是否出现死循环 1002 | ### [28. 实现strStr()](https://leetcode.com/problems/implement-strstr/) 1003 | ```cpp 1004 | class Solution { 1005 | public: 1006 | int strStr(string haystack, string needle) { 1007 | return haystack.find(needle); 1008 | } 1009 | }; 1010 | ``` 1011 | - 第一个方法当然就是调用库函数了,函数返回想要寻找的字符串或者字符的位置,若不存在,返回-1。刚好符合题意。 1012 | ### [14. 最长公共前缀](https://leetcode.com/problems/longest-common-prefix/) 1013 | ```cpp 1014 | class Solution { 1015 | public: 1016 | string longestCommonPrefix(vector& strs) { 1017 | string ans = ""; 1018 | if(strs.empty()) return ans; //输入为空,输出空ans 1019 | int arr = strs.size(); 1020 | string min = strs[0]; 1021 | for(int i = 1; i < arr; ++ i) //找到最短字符串 1022 | { 1023 | if(strs[i].size() < min.size()) 1024 | min = strs[i]; 1025 | } 1026 | for(int j = 0; j < min.size(); ++ j) //从第一个字符开始对比,若都一样,ans加上该字符,若不一样,返回答案; 1027 | { 1028 | for(int m = 0; m < arr; ++m) 1029 | { 1030 | if(min[j] != strs[m][j]) 1031 | return ans; 1032 | } 1033 | ans = ans + min[j]; 1034 | } 1035 | return ans; 1036 | } 1037 | }; 1038 | ``` 1039 | - 找到最短字符串,以它的长度为基准,从所有字符串的第一个字符开始对比,若都一样,ans加上该字符,若不一样,返回答案; 1040 | ### [334. 反转字符串 双指针](https://leetcode.com/problems/reverse-string/) 1041 | ```cpp 1042 | class Solution { 1043 | public: 1044 | void reverseString(vector& s) { 1045 | int i = 0; 1046 | int j = s.size() - 1; 1047 | while(i& nums) { 1062 | int ans = 0; 1063 | sort(nums.begin(), nums.end()); //将数组从小到大排列 1064 | for(int i = 0; i < nums.size(); i = i +2) //将每对的第一位数相加 1065 | ans = ans + nums[i]; 1066 | return ans; 1067 | } 1068 | }; 1069 | ``` 1070 | - 想象一下,每一组两个数字,将每组较小的数字相加。要得到的总和最大。可以假设,较大的数字减去较小的数字就是每组“浪费掉”的,当我们将他们从小到大排列之后,能保证每组浪费掉的数据是最少的。 1071 | ### [167. 两数之和 II - 输入有序数组 双指针](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/) 1072 | ```cpp 1073 | class Solution { 1074 | public: 1075 | vector twoSum(vector& numbers, int target) { 1076 | vector ans; 1077 | int i = 0; 1078 | int j = numbers.size() - 1; 1079 | while(i < j) //双指针,若sum太小,i增大,若sum太大,j减小 1080 | { 1081 | int sum = numbers[i] + numbers[j]; 1082 | if(sum == target) 1083 | { 1084 | ans.push_back(i + 1); 1085 | ans.push_back(j + 1); 1086 | return ans; 1087 | } 1088 | else if(sum < target) 1089 | ++ i; 1090 | else 1091 | -- j; 1092 | } 1093 | return ans; 1094 | } 1095 | }; 1096 | ``` 1097 | - 因为输入的是有序数组,通过运用双指针(i是较小数的位置,j是较大数的位置),求头尾两个元素的sum,若sum与target相比太小,i增大,若sum太大,j减小。 1098 | ### [27. 移除元素](https://leetcode.com/problems/remove-element/) 1099 | ```cpp 1100 | class Solution { 1101 | public: 1102 | int removeElement(vector& nums, int val) { 1103 | int k = 0; 1104 | for(int i = 0; i < nums.size(); ++ i) 1105 | { 1106 | if(nums[i] != val) 1107 | { 1108 | nums[k] = nums[i]; 1109 | ++ k; 1110 | } 1111 | } 1112 | return k; 1113 | } 1114 | }; 1115 | ``` 1116 | - 题目描述不允许使用额外的数组空间,所以只能在原数组上操作。 1117 | - 我们使用两个指针,一个快指针 i 和一个慢指针 k 。i 每次移动一步,而 k 只在添加新的被需要的值时才移动一步。 1118 | - 因为我们的新数组的长度会小于等于旧数组,调用者在调用函数时根据返回的长度,它会打印出数组中该长度范围(k)内的所有元素。因此,范围外的元素不会输出。 1119 | ### [485. 最大连续1的个数](https://leetcode.com/problems/max-consecutive-ones/) 1120 | ```cpp 1121 | class Solution { 1122 | public: 1123 | int findMaxConsecutiveOnes(vector& nums) { 1124 | int flag = 0; //记录每次连续1的个数 1125 | int ans = 0; //记录最大连续1的个数 1126 | for(int i = 0; i < nums.size(); ++ i) 1127 | { 1128 | if(nums[i] == 1) 1129 | { 1130 | ++flag; 1131 | } 1132 | else 1133 | { 1134 | if(ans < flag) 1135 | { 1136 | ans = flag; 1137 | flag = 0; 1138 | } 1139 | else 1140 | flag = 0; 1141 | } 1142 | } 1143 | if(ans < flag) 1144 | ans = flag; 1145 | return ans; 1146 | } 1147 | }; 1148 | ``` 1149 | - 单指针遍历,若遇到1,flag + 1, 遇到0,判断此时flag是否大于ans记录的个数,若是,令ans = flag,且让flag初始化。 若否,只让flag初始化。最终剩下的ans就是最大连续1的个数。 1150 | ### [209. 长度最小的子数组 双指针滑动窗口](https://leetcode.com/problems/minimum-size-subarray-sum/) 1151 | ```cpp 1152 | class Solution { 1153 | public: 1154 | int minSubArrayLen(int s, vector& nums) { 1155 | int ans = INT_MAX; 1156 | int i = 0; //滑窗的右边框 1157 | int sum = 0; //窗口间的和 1158 | int begin = 0; //滑窗的左边框 1159 | while(i < nums.size()) //滑窗的右边框不能超出界限 1160 | { 1161 | if(sum + nums[i] < s) //若滑窗之间的和小于s,右边框右移,sum增大 1162 | { 1163 | sum += nums[i]; 1164 | ++ i; 1165 | } 1166 | else //若滑窗之间的和大于等于s,左边框右移,sum减小 1167 | { 1168 | if(i - begin < ans) //若当前符合条件的连续子数组比ans内记录的长度更小,则更新ans值 1169 | ans = i - begin + 1; 1170 | sum = sum - nums[begin]; 1171 | ++ begin; 1172 | } 1173 | } 1174 | return ans == INT_MAX? 0:ans; 1175 | } 1176 | }; 1177 | ``` 1178 | - 双指针滑动窗口解法,时间复杂度O(N)。 1179 | - 滑动窗口,想象一下,在一个坐标上存在两个指针`begin` 和`i` ,`begin` 代表滑窗的左边框,`i`代表滑窗的右边框。两者通过分别向右滑动,前者能使窗口之间的和减小,后者能使窗口之间的和增大。开始时二者重合,窗口的和就是重合点所在的数。 1180 | - 1. 开始`i`向右滑动,使和变大。 1181 | - 2. 当恰好大于等于s时,记录滑窗所包括的子数组长度ans,若ans已有数值,需判断新值是否小于旧值,若是,更新ans。`begin`向右滑动 1182 | - 3. 判断是否仍大于等于s 1183 | - 4. 若是,重复步骤2,3。若否,转步骤1。直到右边框到达最右边 1184 | ### [189. 旋转数组](https://leetcode.com/problems/rotate-array/) 1185 | ```cpp 1186 | class Solution { 1187 | public: 1188 | void rotate(vector& nums, int k) { 1189 | if(k > nums.size()) k = k % nums.size(); 1190 | reverse(nums.begin(),nums.end()); 1191 | reverse(nums.begin(),nums.begin()+k); 1192 | reverse(nums.begin()+k,nums.end()); 1193 | } 1194 | }; 1195 | ``` 1196 | - 这题直接将尾部元素插入到数组的头部这种做法会超时。这里通过反转数组来做 1197 | - 想象一下,这里有一串数组[1,2,3,4,5,6,7] ,k = 3 。首先反转整个数组[7,6,5,4,3,2,1] 然后反转目标子数组[5,6,7,4,3,2,1] 最后反转目标外子数组[5,6,7,1,2,3,4]符合题目要求。 1198 | ### [119. 杨辉三角 II](https://leetcode.com/problems/pascals-triangle-ii/) 1199 | ```cpp 1200 | class Solution { 1201 | public: 1202 | vector getRow(int rowIndex) { 1203 | vector> vec(rowIndex+1); //想要第rowIndex行,由于从零开始,需要初始化rowIndex + 1行 1204 | if(rowIndex == 0) return {1}; //若需要第0行,返回1; 1205 | for(int i = 0; i <= rowIndex; ++ i) //一行一行生成 1206 | { 1207 | for(int j = 0; j <= i; ++ j) 1208 | { 1209 | if(j == 0 || j == i) //若位置在左右边界,直接填1 1210 | vec[i].push_back(1); 1211 | else 1212 | vec[i].push_back(vec[i-1][j-1] + vec[i-1][j]); //其它位置的值就是左上角与右上角的和 1213 | } 1214 | } 1215 | return vec[rowIndex]; //返回杨辉三角第rowIndex行 1216 | } 1217 | }; 1218 | ``` 1219 | - 杨辉三角每个位置的值就是该位置左上角与右上角的值。与杨辉三角Ⅰ的差别就是 只需要返回一层的数据。 1220 | ### [151. 翻转字符串里的单词](https://leetcode.com/problems/reverse-words-in-a-string/submissions/) 1221 | ```cpp 1222 | class Solution { 1223 | public: 1224 | string reverseWords(string s) { 1225 | string ans = ""; //用来储存答案 1226 | stack mid; //记录所有单词 1227 | string temp = ""; //收集完整的单词 1228 | int i = 0; 1229 | while (s[0] == ' ') //删除字符串前的空格,可以用来检测整个字符串是否都是空格 1230 | { 1231 | s.erase(0,1); 1232 | } 1233 | if(s.empty()) return ans; //若s为空,不需要处理,直接返回空字符串 1234 | int l = s.size(); 1235 | while(i <= l) //遍历当前字符串内的所有字符 1236 | { 1237 | if(s[i] == ' ' || s[i] == '\0') //当遇到空格或'\0',若temp里面有单词,压入栈mid 1238 | { 1239 | if(!temp.empty()) 1240 | { 1241 | mid.push(temp); 1242 | temp = ""; 1243 | } 1244 | } 1245 | else //若遇到字母,先加到temp内,使他成为一个完整的单词 1246 | { 1247 | temp = temp + s[i]; 1248 | } 1249 | ++ i; 1250 | } 1251 | int strsize = mid.size(); 1252 | for(int j = 0; j < strsize - 1; ++ j) //将栈内的单词放入ans,加上单词间的空格 1253 | { 1254 | ans = ans + mid.top() + ' '; 1255 | mid.pop(); 1256 | } 1257 | ans = ans + mid.top(); //手动添加最后一个单词,因为之前添加单词时,会在末位添加空格。最后一个不需要空格 1258 | return ans; 1259 | } 1260 | }; 1261 | ``` 1262 | - 由于单词本身不变,只反转单词的顺序,因此将每个单词看作一个整体,使用`temp`收集完整的单词后压入栈`mid`内 1263 | - 使用栈先入先出的数据结构,实现单词顺序的反转 1264 | ### [557. 反转字符串中的单词 III](https://leetcode.com/problems/reverse-words-in-a-string-iii/) 1265 | ```cpp 1266 | class Solution { 1267 | public: 1268 | string reverseWords(string s) { 1269 | string ans = ""; //记录答案 1270 | string temp = ""; //记录完整的单词 1271 | int l = s.size(); 1272 | for(int i = 0; i <= l; ++ i) //遍历字符串 1273 | { 1274 | if(s[i] == ' ' || s[i] =='\0') //遇到空格或字符串结束符,反转已储存单词的temp 1275 | { 1276 | reverse(temp.begin(),temp.end()); 1277 | ans += temp; 1278 | ans += s[i]; //将反转后的单词拼接到ans上,加上空格或字符串结束符 1279 | temp = ""; //初始化temp 1280 | } 1281 | else 1282 | temp += s[i]; //拼接每个字母成为完整的单词 1283 | } 1284 | return ans; 1285 | } 1286 | }; 1287 | ``` 1288 | - 遇到空格或字符串结束符,反转储存在temp内的单词,拼接到`ans`内 1289 | - note:\0表示空字符,作为字符串结束符使用 1290 | ### [26. 删除排序数组中的重复项](https://leetcode.com/problems/remove-duplicates-from-sorted-array/) 1291 | ```cpp 1292 | class Solution { 1293 | public: 1294 | int removeDuplicates(vector& nums) { 1295 | if(nums.empty()) return 0; 1296 | int i = 0; 1297 | for(int j = 1; j < nums.size(); ++ j) 1298 | { 1299 | if(nums[i] != nums[j]) 1300 | { 1301 | ++ i; 1302 | nums[i] = nums[j]; 1303 | } 1304 | } 1305 | return i + 1; 1306 | } 1307 | }; 1308 | ``` 1309 | - 同时有一个慢指针和一个快指针。慢指针指向新数组,快指针遍历旧数组 1310 | - 由于数组已排序,若nums[i] !=nums[j] 将i 的下一位赋值为nums[j] 1311 | ### [283. 移动零](https://leetcode.com/problems/move-zeroes/) 1312 | ```cpp 1313 | class Solution { 1314 | public: 1315 | void moveZeroes(vector& nums) { 1316 | int i = 0; 1317 | for(i = 0; i < nums.size() && nums[i] != 0; ++ i); //找到第一个0; 1318 | for(int j = i + 1; j < nums.size(); ++ j) //将第一个零之后的第一个非零数字与该0交换 1319 | { 1320 | if(nums[i] == 0 && nums[j] != 0) 1321 | { 1322 | swap(nums[i],nums[j]); 1323 | ++ i; 1324 | } 1325 | } 1326 | } 1327 | }; 1328 | ``` 1329 | - 0 移动到数组的末尾,相当于将非零数字全部移到第一个 0 的前面。 1330 | - 找到第一个零,将第一个零之后的第一个非零数字与该 0 交换,从此之后令 nums[i] 永远等于数组内的第一个 0,nums[j] 永远等于第一个 0 后面的第一个非零数字,交换他们俩即可 1331 | - 这样,既保持了非零元素的相对顺序,也将所有的 0 移动到了数组末尾 1332 | ###[707. 设计链表](https://leetcode.com/problems/design-linked-list/) 1333 | ```cpp 1334 | struct Node{ //构造链表Node结构 1335 | int val; 1336 | Node *prev, *next; 1337 | Node(int val): val(val), prev(nullptr), next(nullptr) {} //初始化 1338 | }; 1339 | class MyLinkedList { 1340 | public: 1341 | MyLinkedList(): head(nullptr), tail(nullptr),size(0){} //初始化链表 1342 | 1343 | 1344 | int get(int index) { //通过getNode函数返回第index个节点的地址,return 该节点的值 1345 | if(getNode(index)) 1346 | return getNode(index) -> val; 1347 | return -1; 1348 | } 1349 | 1350 | void addAtHead(int val) { //在head添加节点 1351 | auto node = new Node(val); //创建一个Node实例,下同 1352 | ++ size; //链表长度加一,下同 1353 | if(head == nullptr) //如果链表为空,新加的node既是head也是tail,下同 1354 | { 1355 | head = node; 1356 | tail = node; 1357 | } 1358 | else 1359 | { 1360 | node -> next = head; //常规的添加头节点步骤,参照代码后的图解 1361 | head -> prev = node; 1362 | head = node; 1363 | } 1364 | } 1365 | 1366 | void addAtTail(int val) { //在tail添加节点 1367 | auto node = new Node(val); 1368 | ++ size; 1369 | if(tail == nullptr) 1370 | { 1371 | head = node; 1372 | tail = node; 1373 | } 1374 | else 1375 | { 1376 | node -> prev = tail; //常规的添加尾节点步骤,参照代码后的图解 1377 | tail -> next = node; 1378 | tail = node; 1379 | } 1380 | } 1381 | 1382 | void addAtIndex(int index, int val) { 1383 | if(index > size) return; //如果索引大于链表长度,无效索引 1384 | if(index == size) //若索引等于链表长度,相当于添加尾节点,直接调用先前定义好的函数 1385 | { 1386 | addAtTail(val); 1387 | return; 1388 | } 1389 | if(index <= 0) //若索引小于链表长度,本题题目的bug,我们需要将它看成添加头节点,直接调用先前定义好的函数 1390 | { 1391 | addAtHead(val); 1392 | return; 1393 | } 1394 | auto node = new Node(val); //添加在非head非tail的位置的情况 1395 | auto nextNode = getNode(index); //过程参照代码后的图解 1396 | nextNode -> prev -> next = node; 1397 | node -> prev = nextNode -> prev; 1398 | node -> next = nextNode; 1399 | nextNode-> prev = node; 1400 | ++ size; 1401 | } 1402 | 1403 | void deleteAtIndex(int index) { //删除节点 1404 | if(auto node = getNode(index)) //若该节点不为nullptr,进行以下步骤 1405 | { 1406 | if(node == head) //若该节点为head,指针head更新为原来head的下一个点的位置 1407 | { 1408 | head = head -> next; 1409 | if(head != nullptr) head -> prev = nullptr; //若新head不为nullptr,将head的prev指针设为空,删除的节点的next指针设为空,即两者断开。 1410 | node -> next = nullptr; 1411 | } 1412 | if(node == tail) //若该节点为tail,与上一步类似 1413 | { 1414 | tail = tail -> prev; 1415 | if(tail != nullptr) tail -> next = nullptr; 1416 | node -> prev = nullptr; 1417 | } 1418 | //若目标节点上或下的指针还不为nullptr,说明指针还未独立出来,需要做以下操作 1419 | if(node -> next != nullptr) node -> next -> prev = node -> prev; 1420 | if(node -> prev != nullptr) node -> prev -> next = node -> next; 1421 | delete node; 1422 | -- size; 1423 | } 1424 | } 1425 | private: 1426 | Node* getNode(int index) //获得目标节点位置,因为是双向链表,通过判断目标点位置在前半段还是后半段来决定从head开始搜索还是从tail搜索 1427 | { 1428 | if(index >= size || index < 0) return nullptr; 1429 | Node* node; 1430 | int i; 1431 | if(size/2 >= index) 1432 | { 1433 | i = index; 1434 | node = head; 1435 | while(i -- > 0) 1436 | { 1437 | node = node -> next; 1438 | } 1439 | } 1440 | else 1441 | { 1442 | i = size - index - 1; 1443 | node = tail; 1444 | while(i -- > 0) 1445 | node = node -> prev; 1446 | } 1447 | return node; 1448 | } 1449 | 1450 | private: 1451 | Node* head; 1452 | Node* tail; 1453 | int size; 1454 | }; 1455 | ``` 1456 | - 注释相对清晰,就不多说了 1457 | - 注意size的正确更新,否则会影响到getNode函数返回值的正确与否 1458 | ### [141. 环形链表](https://leetcode.com/problems/linked-list-cycle/) 1459 | ```cpp 1460 | class Solution { 1461 | public: 1462 | bool hasCycle(ListNode *head) { 1463 | ListNode *faptr = head; 1464 | ListNode *slptr = head; 1465 | while(faptr != nullptr && faptr -> next != nullptr) 1466 | { 1467 | faptr = faptr -> next -> next; 1468 | slptr = slptr -> next; 1469 | if(faptr == slptr) 1470 | return true; 1471 | } 1472 | return false; 1473 | } 1474 | }; 1475 | ``` 1476 | - 当快指针套圈慢指针,双指针相遇时,说明链表中存在环 1477 | - 常规的快慢指针解法,需要注意的是小心处理指针指向空指针的下一个指针(不存在) 1478 | ### [142. 环形链表 II](https://leetcode.com/problems/linked-list-cycle-ii/) 1479 | ```cpp 1480 | class Solution { 1481 | public: 1482 | ListNode *detectCycle(ListNode *head) { 1483 | if(!head || !(head -> next)) return nullptr; 1484 | ListNode *faptr = head, *slptr = head; 1485 | while(faptr && faptr -> next) 1486 | { 1487 | faptr = faptr -> next -> next; 1488 | slptr = slptr -> next; 1489 | if(faptr == slptr) 1490 | { 1491 | slptr = head; 1492 | while(slptr != faptr) 1493 | { 1494 | slptr = slptr -> next; 1495 | faptr = faptr -> next; 1496 | } 1497 | return slptr; 1498 | } 1499 | } 1500 | return nullptr; 1501 | } 1502 | }; 1503 | ``` 1504 | - 待补充 1505 | ### [160. 相交链表](https://leetcode.com/problems/intersection-of-two-linked-lists/submissions/) 1506 | ```cpp 1507 | class Solution { 1508 | public: 1509 | ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { 1510 | if(!headA || !headB) return nullptr; 1511 | ListNode *countA = headA; 1512 | ListNode *countB = headB; 1513 | int lA = 0; 1514 | int lB = 0; 1515 | while(countA){ 1516 | ++ lA; 1517 | countA = countA -> next; 1518 | } 1519 | while(countB){ 1520 | ++ lB; 1521 | countB = countB -> next; 1522 | } 1523 | int i = max(lA,lB) - min(lA,lB); 1524 | if(lA > lB) for(i; i > 0; -- i) headA = headA -> next; 1525 | else for(i; i > 0; -- i) headB = headB -> next; 1526 | while(headA){ 1527 | if(headA == headB) return headA; 1528 | headA = headA -> next; 1529 | headB = headB -> next; 1530 | } 1531 | return nullptr; 1532 | } 1533 | }; 1534 | ``` 1535 | - 假设两条链表有交点,可知相交部分等长,那么交点位置距离链表尾的距离必小于等于较短的链表。先将较长的链表剪去前面部分,使其的长度等于较短的链表。此时将指针从当前的headA 和headB同时向后移动,且对比指针是否相同,若相同则输出指针。 1536 | ### [19. 删除链表的倒数第N个节点](https://leetcode.com/problems/remove-nth-node-from-end-of-list/) 1537 | ```cpp 1538 | class Solution { 1539 | public: 1540 | ListNode* removeNthFromEnd(ListNode* head, int n) { 1541 | if(!head || !(head -> next)) return nullptr; 1542 | ListNode *temp = head; 1543 | int i = 0; 1544 | while(temp){ //用来计算删除的点是正数第几个 1545 | ++ i; 1546 | temp = temp -> next; 1547 | } 1548 | if(i == n){ //此时删除的是head节点 1549 | head = head -> next; 1550 | return head; 1551 | } 1552 | temp = head; 1553 | for(int j = i - n - 1; j > 0; -- j) temp = temp -> next; //找到该点 1554 | temp -> next = temp -> next -> next; //将它的指针指向下下个节点 1555 | return head; 1556 | } 1557 | }; 1558 | ``` 1559 | - 删除某点,只需要找到该点的上一个节点,将上一个节点的指针指向目标点的下一个节点,使目标点无法被访问,这样相当于目标点被从链表中删除 1560 | - 当我们创建一个指针`ListNode *temp = head;`时,并没有创建一个新的链表,两个指针变量共用同一个链表。 1561 | ### [206. 反转链表](https://leetcode.com/problems/reverse-linked-list/) 1562 | ```cpp 1563 | class Solution { 1564 | public: 1565 | ListNode* reverseList(ListNode* head) { 1566 | if(!head || !(head -> next)) return head; 1567 | ListNode* x = head; 1568 | ListNode* y = head -> next; 1569 | ListNode* z = head -> next -> next; 1570 | x -> next = nullptr; 1571 | for(; z; z = z -> next){ 1572 | y -> next = x; 1573 | x = y; 1574 | y = z; 1575 | } 1576 | y -> next = x; 1577 | return y; 1578 | } 1579 | }; 1580 | ``` 1581 | - 迭代 1582 | ### [203. 移除链表元素](https://leetcode.com/problems/remove-linked-list-elements/) 1583 | ```cpp 1584 | class Solution { 1585 | public: 1586 | ListNode* removeElements(ListNode* head, int val) { 1587 | if(!head) return nullptr; 1588 | while(head -> val == val){ 1589 | head = head -> next; 1590 | if(!head) return nullptr; 1591 | } 1592 | ListNode* prev = head; 1593 | ListNode* cur = head -> next; 1594 | while(cur){ 1595 | if(cur -> val == val){ 1596 | prev -> next = cur -> next; 1597 | cur = prev -> next; 1598 | } 1599 | else{ 1600 | prev = cur; 1601 | cur = cur -> next; 1602 | } 1603 | } 1604 | return head; 1605 | } 1606 | }; 1607 | ``` 1608 | 1. 若head为nullptr,返回nullptr 1609 | 2. 若头节点的值与val相等,将头节点向后移一个位置 1610 | 3. 赋值prev节点和cur节点,判断cur节点的值是否与val相等,若是,将cur节点删除 1611 | - 删除方法,将prev节点的指针指向cur的下一个节点,这样,cur的值就无法被访问,等同于删除。 1612 | ### [328. 奇偶链表](https://leetcode.com/problems/odd-even-linked-list/) 1613 | ```cpp 1614 | class Solution { 1615 | public: 1616 | ListNode* oddEvenList(ListNode* head) { 1617 | if(!head) return nullptr; 1618 | ListNode* even = head -> next; 1619 | ListNode* oddtemp = head; 1620 | ListNode* eventemp = even; 1621 | while(oddtemp && eventemp && eventemp -> next){ 1622 | oddtemp -> next = eventemp -> next; 1623 | oddtemp = oddtemp -> next; 1624 | eventemp -> next = oddtemp -> next; 1625 | eventemp = eventemp -> next; 1626 | } 1627 | if(!eventemp){ 1628 | oddtemp -> next = even; 1629 | } 1630 | else{ 1631 | eventemp -> next = nullptr; 1632 | oddtemp -> next = even; 1633 | } 1634 | return head; 1635 | } 1636 | }; 1637 | ``` 1638 | - 常规做法,取`odd`指针指向第一个节点,`even`指针指向第二个节点 1639 | - 用指针`oddtemp` 和`eventemp` 来分离奇偶链表 1640 | - 分离结束后将`odd`段链表的尾指针指向`even`链表的`head`。 1641 | ### [234. 回文链表](https://leetcode.com/problems/palindrome-linked-list/) 1642 | ```cpp 1643 | class Solution { 1644 | public: 1645 | bool isPalindrome(ListNode* head) { 1646 | ListNode* count = head; 1647 | int i = 0; 1648 | stack value; 1649 | while(count){ 1650 | ++ i; 1651 | count = count -> next; 1652 | } 1653 | if(i == 1) return true; 1654 | for(int j = i / 2; j > 0; -- j){ 1655 | value.push(head -> val); 1656 | head = head -> next; 1657 | } 1658 | if(i % 2 == 1) head = head -> next; 1659 | for(int j = i / 2; j > 0; -- j){ 1660 | if(value.top() != head -> val) return false; 1661 | else{ 1662 | head = head -> next; 1663 | value.pop(); 1664 | } 1665 | } 1666 | return true; 1667 | } 1668 | }; 1669 | ``` 1670 | - 这种对称配对题很自然想到使用栈`stack`来进行前半段和后半段对比 1671 | ### [21. 合并两个有序链表](https://leetcode.com/problems/merge-two-sorted-lists/) 1672 | ```cpp 1673 | class Solution { 1674 | public: 1675 | ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { 1676 | if(!l1) return l2; 1677 | if(!l2) return l1; 1678 | if(l1 -> val < l2 -> val){ 1679 | l1 -> next = mergeTwoLists(l1 -> next, l2); 1680 | return l1; 1681 | } 1682 | else{ 1683 | l2 -> next = mergeTwoLists(l1, l2 -> next); 1684 | return l2; 1685 | } 1686 | } 1687 | }; 1688 | ``` 1689 | - 递归法 1690 | ```cpp 1691 | class Solution { 1692 | public: 1693 | ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { 1694 | if(!l1) return l2; 1695 | if(!l2) return l1; 1696 | ListNode *begin = l1 -> val < l2 -> val ? l1 : l2; 1697 | ListNode *ll1 = l1 -> val < l2 -> val ? l1 -> next : l1; 1698 | ListNode *ll2 = l1 -> val < l2 -> val ? l2 : l2 -> next; 1699 | ListNode *cur = begin; 1700 | while(ll2){ 1701 | if(ll1 && ll1 -> val <= ll2 -> val){ 1702 | cur -> next = ll1; 1703 | cur = cur -> next; 1704 | ll1 = ll1 -> next; 1705 | } 1706 | else{ 1707 | cur -> next = ll2; 1708 | cur = cur -> next; 1709 | ll2 = ll2 -> next; 1710 | } 1711 | } 1712 | if(ll1){ 1713 | cur -> next = ll1; 1714 | } 1715 | return begin; 1716 | } 1717 | }; 1718 | ``` 1719 | - 迭代法 1720 | - 想象:让两个队伍的小朋友自己根据从矮到高的原则排队。从两队的队首开始对比,由于时有序链表,若其中一队排列结束,另一队剩余的人直接接在整个队伍的后方。 1721 | ### [2. 两数相加](https://leetcode.com/problems/add-two-numbers/) 1722 | ```cpp 1723 | class Solution { 1724 | public: 1725 | ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { 1726 | queue list1; 1727 | queue list2; 1728 | queue ans; 1729 | ListNode *temp1 = l1; 1730 | ListNode *temp2 = l2; 1731 | ListNode *ex = new ListNode(1); 1732 | int size1 = 0; 1733 | int size2 = 0; 1734 | while(temp1){ 1735 | ++ size1; 1736 | temp1 = temp1 -> next; 1737 | } 1738 | while(temp2){ 1739 | ++ size2; 1740 | temp2 = temp2 -> next; 1741 | } 1742 | ListNode *head = size1 < size2 ? l2 : l1; 1743 | ListNode *copyhead = head; 1744 | for(int i = size1; i > 0; -- i){ 1745 | list1.push(l1 -> val); 1746 | l1 = l1 -> next; 1747 | } 1748 | for(int j = size2; j > 0; -- j){ 1749 | list2.push(l2 -> val); 1750 | l2 = l2 -> next; 1751 | } 1752 | int diff = abs(size1 - size2); 1753 | if(size1 > size2) for(; diff > 0; -- diff) list2.push(0); 1754 | else for(; diff > 0; -- diff) list1.push(0); 1755 | int q = list1.size(); 1756 | int flag = 0; 1757 | for(q; q > 0; -- q){ 1758 | int sum = list1.front() + list2.front() + flag; 1759 | flag = 0; 1760 | list1.pop(); 1761 | list2.pop(); 1762 | if(sum > 9){ 1763 | sum -= 10; 1764 | flag = 1; 1765 | } 1766 | ans.push(sum); 1767 | } 1768 | for(int len = ans.size() - 1; len > 0; -- len){ 1769 | copyhead -> val = ans.front(); 1770 | ans.pop(); 1771 | copyhead = copyhead -> next; 1772 | } 1773 | copyhead -> val = ans.front(); 1774 | if(flag == 1) copyhead -> next = ex; 1775 | return head; 1776 | } 1777 | }; 1778 | ``` 1779 | 1. 用queue来做,同位相加 1780 | 2. 将位数较少的数字在高位补零,使两个数字长度相同 1781 | 3. 注意进位的话需要在tail添加一个节点。 1782 | ### [430. 扁平化多级双向链表](https://leetcode.com/problems/flatten-a-multilevel-doubly-linked-list/) 1783 | ```cpp 1784 | class Solution { 1785 | public: 1786 | Node* flatten(Node* head) { 1787 | Node *temp = head; 1788 | Node *nextnode = nullptr; 1789 | Node *prevnode = head; 1790 | while(prevnode){ 1791 | if(temp && temp -> child){ 1792 | nextnode = temp -> next; //记录当前节点的下一个节点 1793 | temp -> child -> prev = temp; 1794 | temp -> next = flatten(temp -> child); //进入递归 1795 | temp -> child = nullptr; //注销当前节点的child; 1796 | } 1797 | prevnode = temp; //记录null节点的前一个节点 1798 | if(temp) temp = temp -> next; 1799 | if(nextnode && !temp){ //当同一级链表存在下一个节点(即,原来有child的节点的下一节点),且子链表到达null 1800 | prevnode -> next = nextnode; //连接子节点和之前记录的nextnode所指链表 ---->这一步将其中两级双向链表扁平化 1801 | nextnode -> prev = prevnode; 1802 | temp = prevnode -> next; 1803 | nextnode = nullptr; //记得清空使用过的nextnode,否则会将无限连接nextnode所指链表 1804 | } 1805 | } 1806 | return head; 1807 | } 1808 | }; 1809 | ``` 1810 | 递归法 1811 | 1. 若`child`为`nllptr`,将指针移向`next`节点 1812 | 2. 若`child`不为空,进入递归,传入头节点(即,`child`的第一位) 1813 | 3. 连接子链表的尾端和父节点的下一节点。 1814 | - 用`nextnode`记录有`child`的节点的下一个节点,用来连接在子链表的尾端 1815 | - 通过判断到达链表尾端时,`nextnode`是否为`nullptr`,若是,则该尾端就是第一级链表的尾端,若不是,该尾端就是子链表的尾端。(注意使用`nextnode`连接子链表后,需要将`nextnode`清空,否则会重复连接子链表) 1816 | - `prevnode`用来记录`temp`的前一个节点,当`temp`到尾端时为`null`,这时用`prevnode`来连接`nextnode`。 1817 | ```cpp 1818 | class Solution { 1819 | public: 1820 | Node* flatten(Node* head) { 1821 | if(!head) return nullptr; 1822 | Node *cur; 1823 | stack stk; 1824 | stk.push(head); 1825 | Node *pre = nullptr; 1826 | while(!stk.empty()){ 1827 | cur = stk.top(); 1828 | stk.pop(); 1829 | if(cur -> next){ 1830 | stk.push(cur -> next); 1831 | } 1832 | if(cur -> child){ 1833 | stk.push(cur -> child); 1834 | cur -> child = nullptr; 1835 | } 1836 | if(pre){ 1837 | pre -> next = cur; 1838 | cur -> prev = pre; 1839 | } 1840 | pre = cur; 1841 | } 1842 | return head; 1843 | } 1844 | }; 1845 | ``` 1846 | stack 1847 | - 常规DFS遍历,使用stack(LIFO)遍历整个链表 1848 | - 取出每个节点,压入栈内,再按顺序(LIFO)一个个取出,加上两个节点间的关系 1849 | - 记得清空`child`指针 1850 | - 注意while循环内前两个`if`语句的顺序,先`next`的节点,后`child`节点。(LIFO) 1851 | ### [138. 复制带随机指针的链表](https://leetcode.com/problems/copy-list-with-random-pointer/) 1852 | ```cpp 1853 | class Solution { 1854 | public: 1855 | Node* copyRandomList(Node* head) { 1856 | if(!head) return nullptr; 1857 | Node *cohead = head; 1858 | while(cohead){ 1859 | Node *copy = new Node(cohead -> val, cohead -> next, nullptr); //初始化要赋值,要不会出错 1860 | Node *temp = cohead -> next; 1861 | cohead -> next = copy; 1862 | cohead = temp; 1863 | } 1864 | cohead = head; 1865 | while(cohead){ 1866 | if(cohead -> random) cohead -> next ->random = cohead -> random -> next; 1867 | cohead = cohead -> next -> next; 1868 | } 1869 | cohead = head; 1870 | Node *ans = head -> next; 1871 | while(cohead -> next){ 1872 | Node *temp = cohead -> next; 1873 | cohead -> next = cohead -> next -> next; 1874 | cohead = temp; 1875 | } 1876 | return ans; 1877 | } 1878 | }; 1879 | ``` 1880 | - 相似的,克隆图形可以在原始图形上复制完全后,再分离,有点像染色体复制。 1881 | - 调用函数要尽量赋值形参,否则可能会出错 1882 | ### [61. 旋转链表](https://leetcode.com/problems/rotate-list/) 1883 | ```cpp 1884 | class Solution { 1885 | public: 1886 | ListNode* rotateRight(ListNode* head, int k) { 1887 | if(k == 0 || !head || !(head -> next)) return head; 1888 | ListNode *temp = head; 1889 | int len = 0; 1890 | while(temp){ 1891 | ++ len; 1892 | temp = temp -> next; 1893 | } 1894 | k = k % len; 1895 | temp = head; 1896 | for(int i = len - 1; i > 0; -- i) temp = temp -> next; 1897 | temp -> next = head; 1898 | temp = head; 1899 | for(int j = len - k; j > 0; -- j) temp = temp -> next; 1900 | head = temp; 1901 | for(int m = len - 1; m > 0; -- m) temp = temp -> next; 1902 | temp -> next = nullptr; 1903 | return head; 1904 | } 1905 | }; 1906 | ``` 1907 | - 取得链表长度len 1908 | - 让它成环(即tail -> next = head) 1909 | - 向右移动k步相当于head顺着指针路线走len-k步 1910 | - 然后向右移动len-1步找到tail节点,让他指向nullptr 1911 | ### [705. 设计哈希集合](https://leetcode.com/problems/design-hashset/) 1912 | ``` 1913 | struct Node{ 1914 | int val; 1915 | Node *next; 1916 | Node(int val): val(val),next(nullptr){} 1917 | }; 1918 | const int len = 100; 1919 | class MyHashSet { 1920 | 1921 | public: 1922 | vector arr; //本题题点 1923 | /** Initialize your data structure here. */ 1924 | MyHashSet() { 1925 | arr = vector(len, new Node(-1)); 1926 | } 1927 | 1928 | void add(int key) { 1929 | int haval = key % len; 1930 | Node* temp = arr[haval]; 1931 | if(temp -> val != -1){ 1932 | while(temp){ 1933 | if(temp -> val == key) return; 1934 | if(!(temp -> next)){ 1935 | Node *node = new Node(key); 1936 | temp -> next = node; 1937 | return; 1938 | } 1939 | temp = temp -> next; 1940 | } 1941 | } 1942 | else{ 1943 | temp -> val = key; 1944 | return; 1945 | } 1946 | } 1947 | 1948 | void remove(int key) { 1949 | int haval = key % len; 1950 | Node* temp = arr[haval]; 1951 | if(temp -> val != -1){ 1952 | while(temp){ 1953 | if(temp -> val == key){ 1954 | temp -> val = -1; 1955 | return; 1956 | } 1957 | temp = temp -> next; 1958 | } 1959 | } 1960 | } 1961 | 1962 | /** Returns true if this set contains the specified element */ 1963 | bool contains(int key) { 1964 | int haval = key % len; 1965 | Node* temp = arr[haval]; 1966 | while(temp){ 1967 | if(temp -> val == key) return true; 1968 | temp = temp -> next; 1969 | } 1970 | return false; 1971 | } 1972 | }; 1973 | ``` 1974 | - 这里考察的是HashMap的底层实现,所以完全用数组实现是不合理的,而直接用hashmap的内置函数是更不合理的。 引用我小刀哥的话,"这题要用数组做,但是不能完全用数组做" 本题应该通过其它方式实现hashmap。要有Key,也要有value,还有相应的哈希函数 1975 | - 本题解的实现方法是用一段有限数组作为容器,使用哈希函数(这里为key%len,len为数组的长度)算出该数字该放的位置(键值)。若已有数字在内(即发生冲突),利用链表在已有数据的后面插入新数据,解决冲突。这种方法为`链地址法` 1976 | ### [706. 设计哈希映射](https://leetcode.com/problems/design-hashmap/) 1977 | ``` 1978 | struct Node{ 1979 | int nkey; 1980 | int nval; 1981 | Node* next; 1982 | Node(int key, int val): nkey(key), nval(val), next(nullptr){} 1983 | }; 1984 | int len = 1000; 1985 | class MyHashMap { 1986 | public: 1987 | vector arr; 1988 | /** Initialize your data structure here. */ 1989 | MyHashMap() { 1990 | arr = vector (len, new Node(-1,-1)); 1991 | } 1992 | 1993 | /** value will always be non-negative. */ 1994 | void put(int key, int value) { 1995 | int temp = key % len; 1996 | Node* h = arr[temp]; 1997 | Node* prev; 1998 | while(h){ 1999 | if(h -> nkey == key){ 2000 | h -> nval = value; 2001 | return; 2002 | } 2003 | prev = h; 2004 | h = h -> next; 2005 | } 2006 | Node* node = new Node(key,value); 2007 | prev -> next = node; 2008 | } 2009 | 2010 | /** Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key */ 2011 | int get(int key) { 2012 | int temp = key % len; 2013 | Node* h = arr[temp]; 2014 | while(h){ 2015 | if(h -> nkey == key) return h -> nval; 2016 | h = h -> next; 2017 | } 2018 | return -1; 2019 | } 2020 | 2021 | /** Removes the mapping of the specified value key if this map contains a mapping for the key */ 2022 | void remove(int key) { 2023 | int temp = key % len; 2024 | Node* h = arr[temp]; 2025 | while(h){ 2026 | if(h -> nkey == key){ 2027 | h -> nval = -1; 2028 | } 2029 | h = h -> next; 2030 | } 2031 | } 2032 | }; 2033 | ``` 2034 | - 与上一题的唯一一个差别在于,本题一个key只能有一个value,所以我们用一个节点同时储存key和value,计算哈希值,然后进行操作 2035 | ### [217. 存在重复元素](https://leetcode.com/problems/contains-duplicate/) 2036 | ```cpp 2037 | class Solution { 2038 | public: 2039 | bool containsDuplicate(vector& nums) { 2040 | unordered_set hashset; 2041 | for(auto i : nums){ 2042 | if(hashset.count(i) > 0){ 2043 | return true; 2044 | } 2045 | else{ 2046 | hashset.insert(i); 2047 | } 2048 | } 2049 | return false; 2050 | } 2051 | }; 2052 | ``` 2053 | - 用内置函数`unordered_set`,若还不存在该值就插入到set内, 2054 | - 若count>0 返回true 2055 | ### [136. 只出现一次的数字](https://leetcode.com/problems/single-number/) 2056 | ``` 2057 | class Solution { 2058 | public: 2059 | int singleNumber(vector& nums) { 2060 | unordered_set bobo; 2061 | int ans; 2062 | for(auto i : nums){ 2063 | if(bobo.count(i)) bobo.erase(i); 2064 | else bobo.insert(i); 2065 | } 2066 | for(auto j : bobo) ans = j; 2067 | return ans; 2068 | } 2069 | }; 2070 | ``` 2071 | 哈希集 2072 | - 若第一次出现,插入哈希集 2073 | - 第二次出现,冲哈希集内删除 2074 | - 最后剩下的就是那个只出现一次的数字 2075 | ``` 2076 | class Solution { 2077 | public: 2078 | int singleNumber(vector& nums) { 2079 | sort(nums.begin(), nums.end()); 2080 | for(int i = 0, j = 1; j < nums.size(); i += 2, j += 2){ 2081 | if(nums[i] != nums[j]) return nums[i]; 2082 | } 2083 | return nums[nums.size() - 1]; 2084 | } 2085 | }; 2086 | ``` 2087 | 先排序,再用双指针对比。 2088 | ``` 2089 | class Solution { 2090 | public: 2091 | int singleNumber(vector& nums) { 2092 | int ans = nums[0]; 2093 | for(int i = 1; i < nums.size(); ++ i){ 2094 | ans = ans ^ nums[i]; 2095 | } 2096 | return ans; 2097 | } 2098 | }; 2099 | ``` 2100 | 异或 2101 | - 任何一个数字异或它自己都等于0。也就是说,如果我们从头到尾依次异或数组中的每一个数字,那么最终的结果刚好是那个只出现一次的数字,因为那些出现两次的数字全部在异或中抵消掉了。 2102 | ``` 2103 | class Solution { 2104 | public: 2105 | int singleNumber(vector& nums) { 2106 | map n; 2107 | int ans = 0; 2108 | for(int i = 0; i < nums.size(); ++ i){ 2109 | n[nums[i]] == 1? n[nums[i]] = 2: n[nums[i]] = 1; 2110 | } 2111 | for(int j = 0; j < nums.size(); ++ j){ 2112 | if(n[nums[j]] == 1) ans = nums[j]; 2113 | } 2114 | return ans; 2115 | } 2116 | }; 2117 | ``` 2118 | map 2119 | - 实现效果是最差的,就不说了 2120 | ### [202. 快乐数](https://leetcode.com/problems/happy-number/) 2121 | ```cpp 2122 | class Solution { 2123 | public: 2124 | bool isHappy(int n) { 2125 | unordered_set bobo; 2126 | while(!bobo.count(n)){ 2127 | int sum = 0; 2128 | bobo.insert(n); 2129 | while(n != 0){ 2130 | sum = sum + (n%10) * (n%10); 2131 | n /= 10; 2132 | } 2133 | n = sum; 2134 | } 2135 | return n == 1; 2136 | } 2137 | }; 2138 | ``` 2139 | ```cpp 2140 | //递归 2141 | class Solution { 2142 | public: 2143 | unordered_set bobo; 2144 | bool isHappy(int n) { 2145 | int sum = 0; 2146 | if(n == 1) return true; 2147 | else if(bobo.count(n)) return false; 2148 | else{ 2149 | bobo.insert(n); 2150 | while(n != 0){ 2151 | sum = sum + (n%10) * (n%10); 2152 | n /= 10; 2153 | } 2154 | n = sum; 2155 | } 2156 | return isHappy(n); 2157 | } 2158 | }; 2159 | ``` 2160 | 本题计算的结果就分为两种, 2161 | 1. 到1的时候进入循环(即1,1,1……循环),返回true 2162 | 2. 到非1的时候进入循环,返回false 2163 | - 因为都会产生循环,直到开始进入循环的点跳出循环,检查开始进入循环的点是否是1,若是1,n就是快乐数,若不是,n就不是快乐数 2164 | ### [1. 两数之和--hashmap的练习](https://leetcode.com/problems/two-sum/) 2165 | ```cpp 2166 | class Solution { 2167 | public: 2168 | vector twoSum(vector& nums, int target) { 2169 | unordered_map hashmap; 2170 | int i = 0; 2171 | for(auto key : nums){ 2172 | if(hashmap.count(target - key)){ 2173 | return {hashmap[target - key],i}; 2174 | } 2175 | else{ 2176 | hashmap[key] = i; 2177 | ++ i; 2178 | } 2179 | } 2180 | return {}; 2181 | } 2182 | }; 2183 | ``` 2184 | ### [205. 同构字符串 双解](https://leetcode.com/problems/isomorphic-strings/) 2185 | ```cpp 2186 | class Solution { 2187 | public: 2188 | bool isIsomorphic(string s, string t) { 2189 | unordered_map smap; 2190 | unordered_map tmap; 2191 | for(int i = 0; s[i] != '\0'; ++ i){ 2192 | char ss = s[i]; 2193 | char tt = t[i]; 2194 | if(smap.count(ss)){ 2195 | if(smap[ss] != tt) return false; 2196 | } 2197 | else if(tmap.count(tt)){ 2198 | if(tmap[tt] != ss) return false; 2199 | } 2200 | else{ 2201 | smap[ss] = tt; 2202 | tmap[tt] = ss; 2203 | } 2204 | } 2205 | return true; 2206 | } 2207 | }; 2208 | ``` 2209 | - 常规解法,使用哈希映射,两个字符串相互映射。 2210 | ```cpp 2211 | class Solution { 2212 | public: 2213 | bool isIsomorphic(string s, string t) { 2214 | for(int i=0;i findRestaurant(vector& list1, vector& list2) { 2228 | vector ans; 2229 | vector> No; 2230 | unordered_map l1; 2231 | unordered_map l2; 2232 | int i = 0; 2233 | int j = 0; 2234 | int count; 2235 | for(auto re1 : list1){ //将餐厅名称与索引映射 2236 | l1[re1] = i; 2237 | ++ i; 2238 | } 2239 | for(auto re2 : list2){ 2240 | l2[re2] = j; 2241 | ++ j; 2242 | } 2243 | for(auto name : list2){ //找到两个列表内都出现的餐厅名称,并计算索引和 2244 | int sum = 0; 2245 | if(l1.count(name)){ 2246 | sum = l1[name] + l2[name]; 2247 | No.push_back({sum,name}); 2248 | } 2249 | } 2250 | int target = INT_MAX; 2251 | for(int p = 0; p < No.size(); ++ p){ //找到最小索引和的大小 2252 | target = No[p].first < target ? No[p].first : target; 2253 | } 2254 | for(int f = 0; f < No.size(); ++ f){ //将等于最小索引和的餐厅名放入答案列表 2255 | if(No[f].first == target){ 2256 | ans.push_back(No[f].second); 2257 | } 2258 | } 2259 | return ans; 2260 | } 2261 | }; 2262 | ``` 2263 | - 思路都在代码注释里 2264 | ### [387. 字符串中的第一个唯一字符](https://leetcode.com/problems/first-unique-character-in-a-string/) 2265 | ```cpp 2266 | class Solution { 2267 | public: 2268 | int firstUniqChar(string s) { 2269 | unordered_map hashmap; 2270 | for(auto i : s){ 2271 | if(hashmap.count(i)) hashmap[i] += 1; 2272 | else hashmap[i] = 1; 2273 | } 2274 | for(int j = 0; s[j] != '\0'; ++ j) if(hashmap[s[j]] == 1) return j; 2275 | return -1; 2276 | } 2277 | }; 2278 | ``` 2279 | 根据提示使用哈希映射 2280 | 1. 遍历一遍字符串记录每个字母出现的次数 2281 | 2. 遍历hashmap,找出第一个出现次数只有一次的字符 2282 | ### [350. 两个数组的交集 II](https://leetcode.com/problems/intersection-of-two-arrays-ii/) 2283 | ```cpp 2284 | class Solution { 2285 | public: 2286 | vector intersect(vector& nums1, vector& nums2) { 2287 | unordered_map n1; 2288 | unordered_map n2; 2289 | vector ans = {}; 2290 | for(auto i : nums1){ 2291 | if(n1.count(i)) n1[i] += 1; 2292 | else n1[i] = 1; 2293 | } 2294 | for(auto i : nums2){ 2295 | if(n2.count(i)) n2[i] += 1; 2296 | else n2[i] = 1; 2297 | } 2298 | for(auto i : n1){ 2299 | while(n1[i.first] >= 1 && n2[i.first] >= 1){ 2300 | ans.push_back(i.first); 2301 | -- n1[i.first]; 2302 | -- n2[i.first]; 2303 | } 2304 | } 2305 | return ans; 2306 | } 2307 | }; 2308 | ``` 2309 | - 用两个`unordered_map`记录两个数组内每个数字出现的次数 2310 | - 若两个映射都存在某个数字,将该数字压入数组,该数字所在关键字的value减一 2311 | ### [219. 存在重复元素 II](https://leetcode.com/problems/contains-duplicate-ii/) 2312 | ```cpp 2313 | class Solution { 2314 | public: 2315 | bool containsNearbyDuplicate(vector& nums, int k) { 2316 | unordered_map hashmap; 2317 | unordered_map temp; //用来记录当前元素的上一次映射 2318 | for(int i = 0; i < nums.size(); ++ i){ 2319 | if(hashmap.count(nums[i])){ 2320 | if(!temp.count(nums[i])) temp[nums[i]] = i; 2321 | else{ 2322 | hashmap[nums[i]] = temp[nums[i]]; 2323 | temp.erase(nums[i]); 2324 | } 2325 | if(i - hashmap[nums[i]] <= k) return true; 2326 | } 2327 | else hashmap[nums[i]] = i; 2328 | } 2329 | return false; 2330 | } 2331 | }; 2332 | ``` 2333 | - 使用一个哈希映射temp来记录当前元素的上一次映射,当元素重复两次以上,hashmap可以用temp来更新为当前元素的上一次映射的索引。 2334 | ### [49. 字母异位词分组](https://leetcode.com/problems/group-anagrams/submissions/) 2335 | ```cpp 2336 | class Solution { 2337 | public: 2338 | vector> groupAnagrams(vector& strs) { 2339 | unordered_map> hashmap; 2340 | for(auto s : strs){ 2341 | string temp = s; 2342 | sort(temp.begin(), temp.end()); 2343 | hashmap[temp].push_back(s); 2344 | } 2345 | int len = hashmap.size(); 2346 | vector> ans(len); 2347 | int index = 0; 2348 | for(auto i : hashmap){ 2349 | ans[index] = i.second; 2350 | ++ index; 2351 | } 2352 | return ans; 2353 | } 2354 | }; 2355 | ``` 2356 | - 在原始信息和哈希映射使用的实际键之间建立映射关系。 在这里体现为,将单词字母按字母表顺序排列,若排列结果相同,则为字母异位词 2357 | ### [36. 有效的数独](https://leetcode.com/problems/valid-sudoku/) 2358 | ```cpp 2359 | class Solution { 2360 | public: 2361 | bool isValidSudoku(vector>& board) { 2362 | vector> row(9); 2363 | vector> col(9); 2364 | vector> block(9); 2365 | for(int i = 0; i < 9; ++ i){ 2366 | for(int j = 0; j < 9; ++ j){ 2367 | int bindex = (i / 3)* 3 + j / 3; 2368 | char cur = board[i][j]; 2369 | if(cur == '.') continue; 2370 | if(row[i].count(cur) || col[j].count(cur) || block[bindex].count(cur)) return false; 2371 | row[i][cur] = 1; 2372 | col[j][cur] = 1; 2373 | block[bindex][cur] = 1; 2374 | } 2375 | } 2376 | return true; 2377 | } 2378 | }; 2379 | ``` 2380 | - 使用数组搭配`unordered_map`,遍历数独,判断是否已经存在,若存在返回false,若不存在,将数字作为关键字插入对应行列设值为一。 2381 | - 用hashset也一样。 2382 | ### [寻找重复的子树] 2383 | 2384 | ### [771. 宝石与石头](https://leetcode.com/problems/jewels-and-stones/) 2385 | ```cpp 2386 | class Solution { 2387 | public: 2388 | int numJewelsInStones(string J, string S) { 2389 | int ans = 0; 2390 | unordered_set jew; 2391 | for(auto i : J) jew.insert(i); //记录宝石类型 2392 | for(auto s : S) if(jew.count(s)) ++ ans; //若拥有的石头里有宝石,答案加一 2393 | return ans; 2394 | } 2395 | }; 2396 | ``` 2397 | - 把宝石类型J记录进set中,用count函数一块块鉴定所拥有的石头是否属于set内的任意一个。若是,ans加一。 2398 | ### [3. 无重复字符的最长子串](https://leetcode.com/problems/longest-substring-without-repeating-characters/) 2399 | ```cpp 2400 | class Solution { 2401 | public: 2402 | int lengthOfLongestSubstring(string s) { 2403 | int ans = 0; 2404 | for(int i = 0; s[i] != '\0'; ++ i){ 2405 | unordered_set str; 2406 | int len = 0; 2407 | for(int j = i; s[j] != '\0'; ++ j){ 2408 | if(str.count(s[j])) break; 2409 | str.insert(s[j]); 2410 | ++ len; 2411 | } 2412 | len > ans? ans = len : ans = ans; 2413 | } 2414 | return ans; 2415 | } 2416 | }; 2417 | ``` 2418 | - 思路很简单很暴力,记录每次遇到重复之前最长的子串len,并与答案候选ans对比,若大于ans就赋值给ans 2419 | ### [454. 四数相加 II](https://leetcode.com/problems/4sum-ii/) 2420 | ```cpp 2421 | class Solution { 2422 | public: 2423 | int fourSumCount(vector& A, vector& B, vector& C, vector& D) { 2424 | int ans = 0; 2425 | unordered_map ab; 2426 | for(auto a : A){ 2427 | for(auto b : B){ 2428 | int sum = a + b; 2429 | if(!ab.count(sum)) ab[sum] = 1; 2430 | else ab[sum] += 1; 2431 | } 2432 | } 2433 | for(auto c : C){ 2434 | for(auto d : D){ 2435 | int need = -(c + d); 2436 | if(ab.count(need)) ans = ans + ab[need]; 2437 | } 2438 | } 2439 | return ans; 2440 | } 2441 | }; 2442 | ``` 2443 | - 建立一个哈希映射,一个记录AB数组的组合和,和为key,出现的次数为value 2444 | - 计算CD数组的组合和,得到相反数,若该数存在于key中,即符合要求,将答案加上AB组合和中该数出现的次数(value) 2445 | ### [347. 前K个高频元素](https://leetcode.com/problems/top-k-frequent-elements/) 2446 | ```cpp 2447 | class Solution { 2448 | public: 2449 | vector topKFrequent(vector& nums, int k) { 2450 | int max = 0; 2451 | int mf = 0; 2452 | unordered_map c; 2453 | vector ans = {}; 2454 | for(auto i : nums){ 2455 | if(!c.count(i)) c[i] = 1; 2456 | else c[i] += 1; 2457 | } 2458 | if(c.size() == k){ 2459 | for(auto key : c){ 2460 | ans.push_back(key.first); 2461 | } 2462 | sort(ans.begin(),ans.end()); 2463 | return ans; 2464 | } 2465 | for(int j = 0; j < k; ++ j){ 2466 | int val = 0; 2467 | int flag = 0; 2468 | for(auto n : c){ 2469 | if(c[n.first] > val){ 2470 | val = c[n.first]; 2471 | flag = n.first; 2472 | } 2473 | } 2474 | ans.push_back(flag); 2475 | c.erase(flag); 2476 | } 2477 | sort(ans.begin(),ans.end()); 2478 | return ans; 2479 | } 2480 | }; 2481 | ``` 2482 | ### [704. 二分查找](https://leetcode.com/problems/binary-search/) 2483 | ```cpp 2484 | class Solution { 2485 | public: 2486 | int search(vector& nums, int target) { 2487 | int ans = -1; 2488 | int l = 0; 2489 | int r = nums.size() - 1; 2490 | int mid = (l + r) / 2; 2491 | if(nums[0] == target) return 0; //数组仅有一位的情况或刚好第零个为目标值的情况 2492 | if(nums[mid] == target) return mid; //初始mid为目标值的情况 2493 | while(nums[mid] != target){ 2494 | if(mid == l){ //当左右边界相邻时,mid的结果总是等于左边界 2495 | if(nums[mid] == target) return mid; 2496 | else if(nums[r] == target) return r; 2497 | else return -1; 2498 | } 2499 | if(nums[mid] > target){ 2500 | r = mid; 2501 | } 2502 | else{ 2503 | l = mid; 2504 | } 2505 | mid = (l + r) / 2; 2506 | ans = mid; 2507 | } 2508 | return ans; 2509 | } 2510 | }; 2511 | ``` 2512 | - 设定数组的开头和尾端为左右边界,mid为(l + r)/2 2513 | - 若target大于mid 将l赋值为mid,重新计算mid值 2514 | - 若target小于mid 将r赋值为mid,重新计算mid值 2515 | ### [69. x 的平方根](https://leetcode.com/problems/sqrtx/) 2516 | ```cpp 2517 | class Solution { 2518 | public: 2519 | int mySqrt(int x) { 2520 | if(x == 0 || x == 1) return x; 2521 | int l = 0; 2522 | int r = x; 2523 | while(l <= r){ 2524 | int mid = l + (r - l) /2; 2525 | int s = x / mid; //用来判断mid大于目标还是小于目标,或等于目标 2526 | int ss = x / (mid + 1); 2527 | if(x / s == s) return s; //刚好是他的算术平方根 2528 | if(s > mid && ss < mid + 1) return mid; //例如6 在2的平方以及 3的平方之间 答案为2 2529 | if(s > mid) l = mid + 1; //调整边界 2530 | if(s < mid) r = mid - 1; 2531 | } 2532 | return 0; 2533 | } 2534 | }; 2535 | ``` 2536 | - 使用二分法,通过对x和mid的商的比较,得到答案 2537 | - 注意:不能通过mid*mid来与x进行比较,会溢出 2538 | ### [374. 猜数字大小](https://leetcode.com/problems/guess-number-higher-or-lower/) 2539 | ```cpp 2540 | class Solution { 2541 | public: 2542 | int guessNumber(int n) { 2543 | int l = 1; 2544 | int r = n; 2545 | while(l <= r){ 2546 | int mid = l + (r -l) / 2; //相当于(l+r)/2,但用这种写法能防止溢出 2547 | int g = guess(mid); 2548 | if(g == 0) return mid; 2549 | else if(g == -1) r = mid - 1; 2550 | else if(g == 1) l = mid + 1; 2551 | } 2552 | return 0; 2553 | } 2554 | }; 2555 | ``` 2556 | 根据反馈进行调整,使用二分查找 2557 | ### [33. 搜索旋转排序数组](https://leetcode.com/problems/search-in-rotated-sorted-array/) 2558 | ```cpp 2559 | class Solution { 2560 | public: 2561 | int search(vector& nums, int target) { 2562 | int ans = -1; 2563 | if(nums.empty()) return ans; 2564 | int l = 0; 2565 | int r = nums.size() - 1; 2566 | int minlo = l; //储存最小值的索引 2567 | int maxlo = r; //储存最大值的索引 2568 | if(nums.size() == 1){ //如果只有一个数字,直接判断 2569 | if(nums[0] == target) return 0; 2570 | else return ans; 2571 | } 2572 | for(int i = 0, j = 1; j< nums.size(); ++ i, ++ j){ //找到数组旋转的位置 2573 | if(nums[i] > nums[j]){ 2574 | minlo = j; 2575 | maxlo = i; 2576 | } 2577 | } 2578 | if(target > nums[maxlo] || target < nums[minlo]) return ans; //如果在数字范围之内 2579 | if(target >= nums[0]) r = maxlo; //重新设定边界----在左半段的情况 修改r值 2580 | else if(target <= nums[r]) l = minlo; //在右半段的情况 修改l值 2581 | else return -1; 2582 | while(l <= r){ //二分法常规模板 2583 | int mid = l + (r - l) / 2; 2584 | if(nums[mid] == target) return mid; 2585 | if(nums[mid] > target) r = mid - 1; 2586 | else l = mid + 1; 2587 | } 2588 | return ans; 2589 | } 2590 | }; 2591 | ``` 2592 | - 由于算法时间复杂度必须是 O(log n) 级别,暗示要用二分法 2593 | - 因为旋转前数组为升序,旋转后旋转点两侧仍是升序,我们只需先找到旋转点的位置,然后判断target在前段还是后段,之后再用二分法进行查找即可 2594 | ### [278. 第一个错误的版本](https://leetcode.com/problems/first-bad-version/) 2595 | ```cpp 2596 | class Solution { 2597 | public: 2598 | int firstBadVersion(int n) { 2599 | int left = 1, right = n; 2600 | if(isBadVersion(1)) return 1; 2601 | while(left < right){ 2602 | // Prevent (left + right) overflow 2603 | int mid = left + (right - left) / 2; 2604 | if(!isBadVersion(mid - 1) && isBadVersion(mid)) return mid; 2605 | else if(!isBadVersion(mid)) left = mid + 1; 2606 | else right = mid; 2607 | } 2608 | // Post-processing: 2609 | // End Condition: left == right 2610 | if(isBadVersion(left) && !isBadVersion(left - 1)) return left; 2611 | return -1; 2612 | } 2613 | }; 2614 | ``` 2615 | - 这里要查找的是第一个错误版本,因此我们在查找到错误版本时,还需要判断该版本的前一个版本是不是正确版本。 2616 | ### [162. 寻找峰值](https://leetcode.com/problems/find-peak-element/) 2617 | ```cpp 2618 | class Solution { 2619 | public: 2620 | int findPeakElement(vector& nums) { 2621 | int l = 0, r = nums.size()-1; 2622 | if(nums.size() == 1) return 0; 2623 | if(nums.size() == 2) return nums[0] > nums[1] ? 0 : 1; 2624 | while(l <= r){ 2625 | int mid = l + (r - l) / 2; 2626 | cout << mid<< endl; 2627 | if((mid == 0 && nums[mid] > nums[mid + 1]) || (mid == nums.size() - 1 && nums[mid] > nums[mid - 1]) ||(mid != 0 && mid != nums.size()-1 && nums[mid] > nums[mid + 1] && nums[mid] > nums[mid - 1])) return mid; 2628 | if(mid == 0 || nums[mid + 1] > nums[mid - 1]) l = mid + 1; 2629 | else r = mid - 1; 2630 | } 2631 | return -1; 2632 | } 2633 | }; 2634 | ``` 2635 | - 当数组大小为一时,返回0(题:你可以假设 nums[-1] = nums[n] = -∞。) 2636 | - 当数组大小为二时,对比返回较大值的索引 2637 | - 当数组大于二时,峰值可能出现在数组的中间某处或左右边界,因此注意条件 2638 | - 将范围往斜率上升的方向缩 2639 | ### [153. 寻找旋转排序数组中的最小值](https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/) 2640 | ```cpp 2641 | class Solution { 2642 | public: 2643 | int findMin(vector& nums) { 2644 | int l = 0, r = nums.size() - 1; 2645 | if(nums.size() == 1) return nums[0]; 2646 | while(l <= r){ 2647 | int mid = l + (r - l) / 2; 2648 | cout << mid << endl; 2649 | if((mid == 0 && nums[mid] < nums[mid + 1]) || (mid == nums.size()-1 && nums[mid] < nums[mid - 1]) || (mid != 0 && mid != nums.size()-1 && nums[mid] < nums[mid-1] && nums[mid] < nums[mid+1])) return nums[mid]; 2650 | if(nums[r] nums[l]) r = l; 2652 | else if(nums[mid] > nums[r]) l = mid + 1; 2653 | } 2654 | return -1; 2655 | } 2656 | }; 2657 | ``` 2658 | 通过二分查找不断缩小范围,目标值的要求是小于左右相邻的值 2659 | 三个重新界定左右边界的条件 2660 | 1. 右边界小于左边界,且mid位置的值小于右边界,说明最小值在旋转后的数组的右半段。 2661 | 2. 右边界大于左边界,说明范围内数组由小到大排列,直接收敛r=l。 2662 | 3. mid位置的值大于右边界,说明最小值在mid值的右边。 2663 | ### [34. 在排序数组中查找元素的第一个和最后一个位置](https://leetcode.com/problems/find-first-and-last-position-of-element-in-sorted-array/) 2664 | ```cpp 2665 | class Solution { 2666 | public: 2667 | vector searchRange(vector& nums, int target) { 2668 | vector ans = {-1, -1}; 2669 | if(nums.empty()) return ans; // 数组为空的情况 2670 | int l = 0, r = nums.size()-1; 2671 | if(nums[l] > target) return ans; // 若target不在数组范围内 2672 | if(nums[r] < target) return ans; 2673 | 2674 | while(l < r){ // 先查找元素的第一个位置 2675 | int mid = l + (r - l)/2; 2676 | if(nums[mid] >= target) r = mid; 2677 | else l = mid + 1; 2678 | } // 到出循环时,索引 l 和 r 在同一个位置,即查找元素的第一个位置 2679 | if(nums[l] == target) ans[0] = l; // 防止查找元素在数组位置内 但 数组内没有目标元素 2680 | r = nums.size(); // 不设成 nums.size() - 1 的原因是,应对数组大小为一的情况,后面操作会超出索引。 2681 | while(l < r){ // 查找元素的最后一个位置 2682 | int mid = l + (r - l)/2; 2683 | if(nums[mid] > target) r = mid; 2684 | else l = mid + 1; 2685 | } 2686 | // 到处循环时,l和r 在同一个位置,即 查找元素的最后一个位置的下一位 2687 | if(nums[l - 1] == target) ans[1] = l - 1; 2688 | return ans; 2689 | } 2690 | }; 2691 | ``` 2692 | - 先查找元素第一个位置后查找元素最后一个位置 2693 | ### [658. 找到 K 个最接近的元素](https://leetcode.com/problems/find-k-closest-elements/) 2694 | - 方法一:根据题意的常规做法 2695 | ```cpp 2696 | class Solution { 2697 | public: 2698 | vector findClosestElements(vector& arr, int k, int x) { 2699 | vector ans(k); 2700 | int l = 0, r = arr.size() - 1; 2701 | int i = 0; 2702 | while(l + 1 < r){ //找到最靠近x的两个数 2703 | int mid = l + (r - l) / 2; 2704 | if(arr[mid] <= x) l = mid; 2705 | else r = mid; 2706 | } 2707 | while(i < k){ // 两个数分别与x 相减,对比两个差,放进数组。 2708 | int subl,subr; 2709 | if(l < r){ // 用来处理超出边界的情况 2710 | subl = x - arr[l]; 2711 | subr = arr[r] - x; 2712 | } 2713 | else{ 2714 | subl = arr[l] - x; 2715 | subr = x - arr[r]; 2716 | } 2717 | if(subl - subr <= 0){ 2718 | ans[i] = arr[l]; 2719 | -- l; 2720 | if(l == -1) l = arr.size() -1; 2721 | } 2722 | else{ 2723 | ans[i] = arr[r]; 2724 | ++ r; 2725 | if(r == arr.size()) r = 0; 2726 | } 2727 | ++ i; 2728 | } 2729 | sort(ans.begin(),ans.end()); //排序 2730 | return ans; 2731 | } 2732 | }; 2733 | ``` 2734 | - 先找到最靠近x得两个数 2735 | - 求与x得差,对比,小的放入数组 2736 | - 处理边界方法, 若l超出左边界,将索引l的值移到最右边,接下来会把索引r的值依次放入ans数组。 2737 | - 最后排序 2738 | - =============== 2739 | - 方法二:来自评论区的大佬。二分法加滑动窗口 2740 | ```cpp 2741 | class Solution { 2742 | public: 2743 | vector findClosestElements(vector& arr, int k, int x) { 2744 | int left = 0; 2745 | int right = arr.size() - k; 2746 | while(left < right) 2747 | { 2748 | int mid = (left + right) / 2; 2749 | if(x - arr[mid] > arr[mid + k] - x) 2750 | { 2751 | left = mid + 1; 2752 | } 2753 | else 2754 | { 2755 | right = mid; 2756 | } 2757 | } 2758 | return vector(arr.begin() + left, arr.begin() + k + left); // 返回左边界到距离左边界k个值得一段数组 2759 | } 2760 | }; 2761 | ``` 2762 | - 题目所给的数组是排序好的数组,我们要的答案就是数组内长度为k的一段连续的部分数组。 2763 | - 寻找左端点,当l == r时,出循环,此时l是目标数组的第一个端点,只需将该端点及该端点右边的k个值(包括第一个端点)返回即可 2764 | 2765 | ### [162. 寻找峰值](https://leetcode.com/problems/find-peak-element/) 2766 | ```cpp 2767 | class Solution { 2768 | public: 2769 | int findPeakElement(vector& nums) { 2770 | int l = 0, r = nums.size()-1; 2771 | if(nums.size() == 1) return 0; 2772 | if(nums.size() == 2) return nums[0] > nums[1] ? 0 : 1; 2773 | while(l <= r){ 2774 | int mid = l + (r - l) / 2; 2775 | if((mid == 0 && nums[mid] > nums[mid + 1]) || (mid == nums.size() - 1 && nums[mid] > nums[mid - 1]) ||(mid != 0 && mid != nums.size()-1 && nums[mid] > nums[mid + 1] && nums[mid] > nums[mid - 1])) return mid; 2776 | if(mid == 0 || nums[mid + 1] > nums[mid - 1]) l = mid + 1; 2777 | else r = mid - 1; 2778 | } 2779 | return -1; 2780 | } 2781 | }; 2782 | ``` 2783 | - 当数组大小为一时,返回0(题:你可以假设 nums[-1] = nums[n] = -∞。) 2784 | - 当数组大小为二时,对比返回较大值的索引 2785 | - 当数组大于二时,峰值可能出现在数组的中间某处或左右边界,因此注意条件 2786 | - 将范围往斜率上升的方向缩 2787 | 2788 | ### [50. Pow(x, n)](https://leetcode.com/problems/powx-n/) 2789 | ```cpp 2790 | class Solution { 2791 | public: 2792 | //二分法,不断将指数减半 2793 | double basicPow(double x, long n){ 2794 | if(n == 0) return 1.0; // 顶 2795 | double half = basicPow(x, n / 2); 2796 | if(n % 2 == 0){ //根据奇偶性分 2797 | return half * half; 2798 | } 2799 | else{ 2800 | return half * half * x; 2801 | } 2802 | } 2803 | double myPow(double x, int n) { 2804 | long N = n; 2805 | if(N == 0) return 1.0; 2806 | if(N < 0){ //处理指数为负数的情况 2807 | x = 1 / x; 2808 | N = - N; 2809 | } 2810 | return basicPow(x, N); 2811 | } 2812 | }; 2813 | ``` 2814 | - 需要用long来存储指数 2815 | 2816 | ### [367. 有效的完全平方数](https://leetcode.com/problems/valid-perfect-square/) 2817 | ```cpp 2818 | class Solution { 2819 | public: 2820 | bool isPerfectSquare(int num) { 2821 | int l = 0, r = 46340; 2822 | while(l <= r){ // 二分法找根 2823 | int mid = l + (r - l) / 2; 2824 | long power = mid * mid; 2825 | if(power > num){ 2826 | r = mid -1; 2827 | } 2828 | else if(power < num){ 2829 | l = mid +1; 2830 | } 2831 | else{ 2832 | return true; 2833 | } 2834 | } 2835 | return false; 2836 | } 2837 | }; 2838 | ``` 2839 | 方法一:二分法 2840 | - 首先要知道一个前提,整型底数上限为46340 即 整数最大值为 2147483647 而其中最大的有效的完全平方数为 46340 *46340 = 2147395600 2841 | - 使用二分法查找num的根 2842 | 2843 | ```cpp 2844 | class Solution { 2845 | public: 2846 | bool isPerfectSquare(int num) { 2847 | long odd = 1, power = 0; 2848 | while(true){ 2849 | power += odd; 2850 | odd += 2; 2851 | if(power == num) return true; 2852 | if(power > num) return false; 2853 | } 2854 | return true; 2855 | } 2856 | }; 2857 | ``` 2858 | 方法二:数学法 2859 | - 根据公式 1 + 3 + 5 + 7 +... +(2n+1) = n^2 即完全平方数肯定是前n个连续奇数的和 2860 | 2861 | ### [744. 寻找比目标字母大的最小字母](https://leetcode.com/problems/find-smallest-letter-greater-than-target/) 2862 | ```cpp 2863 | class Solution { 2864 | public: 2865 | char nextGreatestLetter(vector& letters, char target) { 2866 | int l = 0, r = letters.size() - 1; 2867 | if(target >= letters[r] || target < letters[l]) return letters[l]; //因为是循环数组,如果target不在数组范围内,直接返回数组第一个字符 2868 | while(l + 1 < r){ // 二分法模板③,l始终在目标字符或者目标字符的左边,r 始终再目标字符的右边,当两者相遇跳出循环时,r刚好在目标字符位置的右边 2869 | int mid = l + (r - l)/2; 2870 | if(letters[mid] > target) r = mid; 2871 | else l = mid; 2872 | } 2873 | return letters[r]; 2874 | } 2875 | }; 2876 | ``` 2877 | - 在ASCII码中,字符可以直接比较大小,即内置数值进行比较,小写子母中,从a到z字符逐渐增大; 2878 | - 二分法模板③ 2879 | 2880 | ### [154. 寻找旋转排序数组中的最小值 II](https://leetcode.com/problems/find-minimum-in-rotated-sorted-array-ii/) 2881 | ```cpp 2882 | class Solution { 2883 | public: 2884 | int findMin(vector& nums) { 2885 | int l = 0, r = nums.size()-1; 2886 | if(nums[0] < nums[r]) return nums[0]; 2887 | while(l + 1 < r){ 2888 | int mid = l + (r - l)/2; 2889 | if(nums[mid] < nums[r]) r = mid; 2890 | else if(nums[mid] > nums[r]) l = mid; 2891 | else{ 2892 | -- r; 2893 | } 2894 | } 2895 | return nums[r]; 2896 | } 2897 | }; 2898 | ``` 2899 | - 基本跟153差不多 2900 | - 去重即可 即代码中`--r` 2901 | 2902 | ### [287. 寻找重复数](https://leetcode.com/problems/find-the-duplicate-number/) 2903 | ```cpp 2904 | class Solution { 2905 | public: 2906 | int findDuplicate(vector& nums) { 2907 | int l = 1, r = nums.size(); 2908 | while(l < r){ 2909 | int mid = l + (r - l) / 2; 2910 | int count = 0; 2911 | for(int i : nums){ 2912 | if(i < mid) ++ count; 2913 | } 2914 | if(count < mid){ 2915 | l = mid + 1; 2916 | } 2917 | else{ 2918 | r = mid; 2919 | } 2920 | } 2921 | return l-1; 2922 | } 2923 | }; 2924 | ``` 2925 | ## 二叉树 2926 | ### [144. Binary Tree Preorder Traversa二叉树的前序遍历](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/) 2927 | - 先明白二叉树前序遍历的概念:前序遍历首先访问**根节点**,然后遍历**左子树**,最后遍历**右子树**。 2928 | 以下各个方法细讲 2929 | #### 方法一: 递归法 2930 | ```cpp 2931 | class Solution { 2932 | public: 2933 | vector ans; 2934 | vector preorderTraversal(TreeNode* root) { 2935 | if(root != NULL){ 2936 | ans.push_back(root -> val); 2937 | preorderTraversal(root -> left); 2938 | preorderTraversal(root -> right); 2939 | } 2940 | return ans; 2941 | } 2942 | }; 2943 | ``` 2944 | - 因为先访问根节点,所以直接将`root`的`val`放入答案(ans)容器内。 2945 | - 然后遍历左子树,现在以`root`的左子树为`root`进入递归。 2946 | 2947 | #### 方法二: 迭代法(使用stack 2948 | ```cpp 2949 | class Solution { 2950 | public: 2951 | vector preorderTraversal(TreeNode* root) { 2952 | stack stk; 2953 | vector ans; 2954 | TreeNode* temp = root; 2955 | while(temp != NULL || !stk.empty()){ 2956 | while(temp != NULL) { 2957 | ans.push_back(temp -> val); 2958 | stk.push(temp); 2959 | temp = temp -> left; 2960 | 2961 | } 2962 | temp = stk.top(); 2963 | stk.pop(); 2964 | temp = temp -> right; 2965 | } 2966 | return ans; 2967 | } 2968 | }; 2969 | ``` 2970 | - 想法跟上一种方法差不多,差别在于使用`stack`来储存`root`。 2971 | - 当左子树遍历完后,取出`root`接着遍历右子树。 2972 | 2973 | ### [94. 二叉树的中序遍历](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) 2974 | - 中序遍历的概念:中序遍历是先遍历左子树,然后访问根节点,然后遍历右子树。(如果还不清楚,可以看这个 [链接](https://leetcode-cn.com/explore/learn/card/data-structure-binary-tree/2/traverse-a-tree/7/) 是力扣关于中序遍历的动态总结) 2975 | #### 方法一: 递归法 2976 | ```cpp 2977 | class Solution { 2978 | public: 2979 | vector ans; 2980 | vector inorderTraversal(TreeNode* root) { 2981 | if(root != NULL) { 2982 | inorderTraversal(root -> left); 2983 | ans.push_back(root -> val); 2984 | inorderTraversal(root -> right); 2985 | } 2986 | return ans; 2987 | } 2988 | }; 2989 | ``` 2990 | - 递归法,先左子树,后root,最后;右子树。 2991 | #### 方法二: 迭代法 2992 | ```cpp 2993 | class Solution { 2994 | public: 2995 | vector inorderTraversal(TreeNode* root) { 2996 | vector ans; 2997 | stack stk; 2998 | TreeNode* temp = root; 2999 | while(temp || !stk.empty()){ 3000 | while(temp){ 3001 | stk.push(temp); 3002 | temp = temp -> left; 3003 | } 3004 | temp = stk.top(); 3005 | stk.pop(); 3006 | ans.push_back(temp -> val); 3007 | temp = temp -> right; 3008 | } 3009 | return ans; 3010 | } 3011 | }; 3012 | ``` 3013 | - 个人认为难点在于找到哪里到哪里为一个循环 3014 | - 首先,我们知道从root开始。然后遍历左子树,直到NULL。然后指向最后一个root的右子树。从这里进入循环,以新的点为root。 3015 | 3016 | ### [145. 二叉树的后序遍历](https://leetcode-cn.com/problems/binary-tree-postorder-traversal/) 3017 | #### 基本思想--迭代法 3018 | - 先遍历左节点直到左节点为null。 3019 | - 开始遍历右节点,若该右节点有左节点,优先遍历左节点。 3020 | - 使用`rightchild`来记录右节点是否已被遍历过。若是:则说明以该点为根的子树已被遍历,输出根节点。若否:就开始遍历右节点,回到第二步。 3021 | ```cpp 3022 | class Solution { 3023 | public: 3024 | vector postorderTraversal(TreeNode* root) { 3025 | vector ans; 3026 | stack stk; 3027 | TreeNode* cur = root; 3028 | TreeNode* rightchild = NULL; 3029 | while(cur || !stk.empty()){ 3030 | while(cur != NULL){ 3031 | stk.push(cur); 3032 | cur = cur -> left; 3033 | } 3034 | cur = stk.top(); 3035 | if(!cur -> right|| rightchild == cur -> right){ 3036 | ans.push_back(cur -> val); 3037 | stk.pop(); 3038 | rightchild = cur; 3039 | cur = NULL; 3040 | } 3041 | else{ 3042 | rightchild = NULL; 3043 | cur = cur -> right; 3044 | } 3045 | } 3046 | return ans; 3047 | } 3048 | }; 3049 | ``` 3050 | #### 基本思想--递归法 3051 | - 若该点为null,则返回。 3052 | - 遍历左子树 3053 | - 遍历右子树 3054 | - 记录根节点的值。 3055 | ```cpp 3056 | class Solution { 3057 | public: 3058 | vector ans; 3059 | vector postorderTraversal(TreeNode* root) { 3060 | if(!root) return ans; 3061 | postorderTraversal(root -> left); 3062 | postorderTraversal(root -> right); 3063 | ans.push_back(root -> val); 3064 | return ans; 3065 | } 3066 | }; 3067 | ``` 3068 | ### [102. 二叉树的层序遍历](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) 3069 | #### 基本思路1--使用单层循环 3070 | - 遍历每一层,然后把`flag`放在每层最后,用来分割上下两层。将每一层存入`ans`容器内 3071 | ```cpp 3072 | class Solution { 3073 | public: 3074 | vector> levelOrder(TreeNode* root) { 3075 | vector> ans; 3076 | if(!root) return ans; 3077 | queue q; 3078 | TreeNode* ptr = root; 3079 | TreeNode* flag = new TreeNode(-1); 3080 | int lvl = 0, ele = 0; 3081 | q.push(root); 3082 | q.push(flag); 3083 | vector row; 3084 | while(!q.empty()){ 3085 | if(q.front() == flag){ 3086 | ans.push_back(row); 3087 | row.clear(); 3088 | q.pop(); 3089 | q.push(flag); 3090 | continue; 3091 | } 3092 | TreeNode* temp = q.front(); 3093 | q.pop(); 3094 | row.push_back(temp -> val); 3095 | if(temp -> left) q.push(temp -> left); 3096 | if(temp -> right) q.push(temp -> right); 3097 | if(q.size() == 1){ 3098 | ans.push_back(row); 3099 | break; 3100 | } 3101 | } 3102 | return ans; 3103 | } 3104 | }; 3105 | ``` 3106 | #### 基本思路2--使用双层循环 3107 | - 这个方法比较好。 3108 | - 也是遍历每一层,无需flag分离。 3109 | - 以每一层为整体。总共两个while循环,外部while循环遍历层(由上到下),内部while循环遍历每一层内的节点(由左到右)。 3110 | 3111 | ```cpp 3112 | class Solution { 3113 | public: 3114 | vector> levelOrder(TreeNode* root) { 3115 | vector> ans; 3116 | if(!root) return ans; 3117 | queue q; 3118 | q.push(root); 3119 | while(!q.empty()){ 3120 | int count = q.size(); 3121 | vector row; 3122 | while(count--){ 3123 | TreeNode* temp = q.front(); 3124 | q.pop(); 3125 | row.push_back(temp -> val); 3126 | if(temp -> left) q.push(temp -> left); 3127 | if(temp -> right) q.push(temp -> right); 3128 | } 3129 | ans.push_back(row); 3130 | } 3131 | return ans; 3132 | } 3133 | }; 3134 | ``` 3135 | ### [101. 对称二叉树](https://leetcode-cn.com/problems/symmetric-tree/) 3136 | 想说的都在注释里 3137 | ```cpp 3138 | class Solution { 3139 | public: 3140 | bool isSymmetric(TreeNode* root) { 3141 | if(!root) return true; //常规 3142 | return box(root -> left, root -> right); 3143 | } 3144 | bool box(TreeNode* l, TreeNode* r){ 3145 | if(l == NULL && r == NULL) return true; 3146 | if(l == NULL || r == NULL) return false; 3147 | return (l -> val == r -> val) //对应node的值要相同 3148 | && (box(l -> left, r ->right)) //注意这两句,为了对称,对比左节点的左子节点和右节点的右子节点 3149 | && (box(r -> left, l -> right)); //右节点的左子节点和左节点的右子节点 3150 | } 3151 | }; 3152 | ``` 3153 | ### [112. 路径总和](https://leetcode-cn.com/problems/path-sum/) 3154 | - 通过用sum减去一条路径的每个节点的值,到达叶节点时,判断sum是否为0。 3155 | ```cpp 3156 | class Solution { 3157 | public: 3158 | bool hasPathSum(TreeNode* root, int sum) { 3159 | if(!root) return false; 3160 | sum -= root -> val; 3161 | if((!root -> left) && (!root -> right)) return sum == 0; 3162 | return hasPathSum(root -> left, sum) || hasPathSum(root -> right, sum); 3163 | } 3164 | }; 3165 | ``` 3166 | ### [105. 从前序与中序遍历序列构造二叉树](https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) 3167 | - 前序遍历数组的第一个数字是根节点 3168 | - 找出根节点,得到根节点的val 3169 | - 根据对比根节点的值找到根节点在中序遍历数组内的位置 3170 | - 递归以上步骤,直到达到递归终止条件。 3171 | - (中序遍历左子树不一定从[0]位开始。) 3172 | ```cpp 3173 | class Solution { 3174 | public: 3175 | TreeNode* buildTree(vector& preorder, vector& inorder) { 3176 | if(preorder.empty()) return NULL; 3177 | return box(preorder, 0, preorder.size() - 1, inorder, 0, inorder.size() - 1); 3178 | } 3179 | TreeNode* box(vector& preorder,int ps, int pe,vector& inorder, int is, int ie){ 3180 | if(ps > pe || is > ie) return NULL; 3181 | int flag = preorder[ps]; 3182 | TreeNode* root = new TreeNode(flag); 3183 | int id = 0; 3184 | while(inorder[is + id] != flag) ++ id; 3185 | root -> left = box(preorder, ps + 1, ps + id, inorder, is, is+id -1); 3186 | root -> right = box(preorder,ps + id + 1, pe, inorder, is + id + 1, ie); 3187 | return root; 3188 | } 3189 | }; 3190 | ``` 3191 | 3192 | ### [106. 从中序与后序遍历序列构造二叉树](https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/) 3193 | - 后续遍历的根节点在postorder数组的最后一个 3194 | - 根据根节点的值找到根节点在中序遍历数组中的位置。 3195 | - 此时,中序遍历根节点的左边为左子树,右边为右子树。 3196 | - 将以上三步进入递归。生成二叉树 3197 | #### 难点在于:正确找到左右子树在inorder数组内的范围! 3198 | ```cpp 3199 | class Solution { 3200 | public: 3201 | TreeNode* buildTree(vector& inorder, vector& postorder) { 3202 | if(inorder.empty()) return NULL; 3203 | return box(inorder, 0, inorder.size() - 1, postorder, 0, postorder.size() - 1); 3204 | } 3205 | TreeNode* box(vector& inorder, int iS, int iE, vector& postorder, int pS, int pE){ 3206 | if(iS > iE || pS > pE) return NULL; 3207 | int rooot = postorder[pE]; 3208 | int id = 0; 3209 | while(rooot != inorder[id]) ++ id; 3210 | TreeNode* root = new TreeNode(rooot); 3211 | root -> left = box(inorder, iS, id - 1, postorder, pS, pS + id -iS -1); 3212 | root -> right = box(inorder,id + 1, iE, postorder, pS + id - iS, pE - 1); 3213 | return root; 3214 | } 3215 | }; 3216 | ``` 3217 | ### [116. 填充每个节点的下一个右侧节点指针](https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/) 3218 | #### 思路一 3219 | - 使用队列遍历整个树。 3220 | - 使用flag作为每一层的分界标 3221 | - 使用last变量记录上一个节点,用来连接next。 3222 | ```cpp 3223 | class Solution { 3224 | public: 3225 | Node* connect(Node* root) { 3226 | if(!root) return root; 3227 | queue q; 3228 | Node* flag = new Node; // 用于记录每层树的结束。 3229 | Node* last = new Node; // 用于记录上一个节点,将上一个节点的next连接当前节点。 3230 | 3231 | q.push(root); // 根推入队列 3232 | q.push(flag); // 第一层结束,用flag标记位置 3233 | while(q.size() >= 2){ //队列内到最后存在一个flag,因此 >2 3234 | Node* now = q.front(); // 取出当前节点。 3235 | q.pop(); 3236 | if(now == flag){ // 如果到达每层末尾,last清空,再次推入flag 3237 | //last -> next = NULL; 3238 | last = NULL; 3239 | q.push(flag); 3240 | continue; 3241 | } 3242 | if(last == NULL){ // 说明是新的一层 3243 | last = now; 3244 | } 3245 | else{ // 否则一定存在last节点 3246 | last -> next = now; 3247 | last = now; // 更新last节点 3248 | } 3249 | if(now -> left){ //压入新的节点。 3250 | q.push(now -> left); 3251 | q.push(now -> right); 3252 | } 3253 | } 3254 | return root; 3255 | } 3256 | }; 3257 | ``` 3258 | 3259 | #### 思路二 ----符合要求 3260 | - 为了找到当前节点的右节点的next,可以使用当前节点已连接的next,即 3261 | - `now -> right -> next = now -> next -> left;` 3262 | ```cpp 3263 | class Solution { 3264 | public: 3265 | Node* connect(Node* root) { 3266 | if(!root) return NULL; 3267 | Node* now = new Node; 3268 | Node* leftmost = new Node; 3269 | now = root; 3270 | leftmost = root; 3271 | while(now -> left){ 3272 | now -> left -> next = now -> right; // 当前节点的左节点的next指向右节点 3273 | if(now -> next){ //将当前节点的右节点的next指向 下一节点的左节点 3274 | now -> right -> next = now -> next -> left; 3275 | now = now -> next; // 更新当前节点 3276 | } 3277 | else{ 3278 | now = leftmost -> left; // 若当前节点没有next,更新当前节点为当前层的最左节点的左节点。 3279 | leftmost = now; 3280 | } 3281 | } 3282 | return root; 3283 | } 3284 | }; 3285 | ``` 3286 | #### 思路三 ------ 拉链法,递归写法,符合要求 3287 | - 也就是拉链法,先将以root 为中心的左右两部分连接起来,然后进入递归。 3288 | ```cpp 3289 | class Solution { 3290 | public: 3291 | Node* connect(Node* root) { 3292 | if(!root || !root -> left) return root; 3293 | root -> left -> next = root -> right; 3294 | Node* now = new Node; 3295 | now = root -> left; 3296 | while(now -> left){ 3297 | now -> right -> next = now -> next -> left; 3298 | now = now -> right; 3299 | } 3300 | root -> left = connect(root -> left); 3301 | root -> right = connect(root -> right); 3302 | return root; 3303 | } 3304 | }; 3305 | ``` 3306 | ### [117. 填充每个节点的下一个右侧节点指针 II](https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node-ii/) 3307 | - 代码虽不优雅,但好歹思路还算清晰,且符合常量额外空间要求 3308 | - 首先介绍参数 3309 | - `now` :记录当前处理的节点 3310 | - `leftmost` :记录当前层最左拥有child的节点 3311 | - `flat` :用来记录now 的next 之后的节点含有child的第一个节点 3312 | - `temp` :使用temp遍历now 的next,直到含有child的节点。 3313 | - 1. 若now节点有左child,寻找now节点的右child,若没有右child,接着从now节点往next方向寻找第一个含有child的节点。 3314 | - 2. 若now没有左chuild,只有右child,直接从now节点往next方向寻找第一个含有child的节点,并与它的child连接。 3315 | - 3. 更细节的都在代码注释里,祝好! 3316 | ```cpp 3317 | class Solution { 3318 | public: 3319 | Node* connect(Node* root) { 3320 | if(!root || (!root -> left && !root -> right)) return root; //如果root为空或root没有没有左子树和右子树,直接返回 3321 | Node* now = new Node; // 记录当前处理的节点 3322 | Node* leftmost = new Node; // 记录当前层最左拥有child的节点 3323 | now = root; 3324 | leftmost = now -> left != NULL ? now -> left : now -> right; 3325 | while(now -> left || now -> right || now ){ 3326 | Node* flag = new Node; 3327 | flag = NULL; // 用来记录now 的next 之后的节点含有child的第一个节点 3328 | if(now -> left){ //当前节点有左子树,寻找右子树或者更右边的节点。 3329 | if(now -> right){ //如果有右子树,左右相连 3330 | now -> left -> next = now -> right; 3331 | } 3332 | else{ // 若没有右子树,寻找最近的👉节点 3333 | Node* temp = new Node; // 使用temp遍历now 的next,直到含有child的节点。 3334 | temp = now -> next; 3335 | while(temp){ 3336 | if(temp-> left){ //有左节点就连上 3337 | now -> left -> next = temp -> left; 3338 | flag = temp; 3339 | break; 3340 | } 3341 | else if(temp-> right){ // 若没有左节点就连右节点。 3342 | now -> left -> next = temp -> right; 3343 | flag = temp; 3344 | break; 3345 | } 3346 | else temp = temp -> next; // 若都没有,接着往next方向找 3347 | } 3348 | } 3349 | } 3350 | if(now -> right){ // 右子树 3351 | Node* temp = new Node; 3352 | temp = now -> next; 3353 | while(temp){ // 直接寻找最近的👉子树 3354 | if(temp -> left){ //与上面相同,有左连做 3355 | now -> right -> next = temp -> left; 3356 | flag = temp; 3357 | break; 3358 | } 3359 | else if(temp -> right){ // 无左连右 3360 | now -> right -> next = temp -> right; 3361 | flag = temp; 3362 | break; 3363 | } 3364 | else temp = temp -> next; // 若无左右,接着往next方向找 3365 | } 3366 | } 3367 | if(flag){ // 更新now(当前处理的节点)到flag 3368 | now = flag; 3369 | } 3370 | else{ // 如果不存在flag,说明需要开始处理下一层 3371 | while(leftmost && (!leftmost -> left && !leftmost -> right)){ //找到下一层至少含有一个子节点的最左的节点 3372 | leftmost = leftmost -> next; 3373 | } 3374 | if(leftmost){ // 更新now 3375 | now = leftmost; 3376 | if(now -> left){ // 更新预备leftmost 3377 | leftmost = now -> left; 3378 | } 3379 | else leftmost = now -> right; 3380 | } 3381 | else break; //若不存在下一层的leftmost,说明遍历完成,跳出循环。 3382 | } 3383 | } 3384 | return root; 3385 | } 3386 | }; 3387 | ``` 3388 | ### [236. 二叉树的最近公共祖先](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) 3389 | #### 思路: 全盘搜索 3390 | - 遇到指定节点,直接返回root,无需往下搜寻。 3391 | - 向左右子树遍历,返回包含指定节点的子树。 3392 | - 若左右子树都包含指定节点,则当前root为最近公共祖先。 3393 | ```cpp 3394 | class Solution { 3395 | public: 3396 | TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { 3397 | if(!root || root == p || root == q) return root; 3398 | TreeNode* l = lowestCommonAncestor(root -> left, p, q); 3399 | TreeNode* r = lowestCommonAncestor(root -> right, p, q); 3400 | if(l && r) return root; 3401 | return l ? l : r; 3402 | } 3403 | }; 3404 | ``` 3405 | ### [297. 二叉树的序列化与反序列化](https://leetcode-cn.com/problems/serialize-and-deserialize-binary-tree/) 3406 | ```cpp 3407 | class Codec { 3408 | public: 3409 | 3410 | // Encodes a tree to a single string. 3411 | string serialize(TreeNode* root) { //把树转化为字符串 3412 | if(!root) return ""; 3413 | string ans = ""; 3414 | queue q; 3415 | q.push(root); 3416 | while(!q.empty()){ 遍历二叉树,BFS 3417 | TreeNode* temp = q.front(); 3418 | q.pop(); 3419 | if(temp){ 3420 | ans += to_string(temp -> val); 3421 | ans += ","; // 分割每个node 3422 | q.push(temp -> left); 3423 | q.push(temp -> right); 3424 | }else{ 3425 | ans += "null,"; // 即使是空节点也要转换成字符串,因为要将字符串还原为树,null作为占位符 3426 | } 3427 | } 3428 | return ans; 3429 | } 3430 | 3431 | // Decodes your encoded data to tree. 3432 | TreeNode* deserialize(string data) { // 把字符串转换为树 3433 | TreeNode* root = new TreeNode; // 创建根节点 3434 | if(data.empty()) return NULL; // 传统艺能 3435 | int j = 0; 3436 | string sub = ""; 3437 | for(; data[j] >= '0' && data[j] <= '9' || data[j] == '-'; ++ j){} // 找到一个完整的数字在字符串的位置 3438 | if(data[0] == '-'){ // 处理负数情况 3439 | sub = data.substr(1, j-1); 3440 | data.erase(0, j+1); 3441 | int number = stoi(sub); 3442 | root -> val = -number; 3443 | }else{ 3444 | sub = data.substr(0, j); 3445 | data.erase(0, j+1); 3446 | int number = stoi(sub); 3447 | root -> val = number; 3448 | } 3449 | queue q; 3450 | q.push(root); //root处理完毕,压入队列 3451 | int a = 1; 3452 | while(!data.empty()){ //开始处理左右节点 3453 | cout << q.front() -> val << endl; 3454 | TreeNode* l = new TreeNode; 3455 | TreeNode* r = new TreeNode; 3456 | if(data[0] == 'n'){ //左节点 3457 | l = NULL; 3458 | data.erase(0, 5); 3459 | } 3460 | else{ 3461 | int i = 0; 3462 | for(i; (data[i] >= '0' && data[i] <= '9') || data[i] == '-'; ++ i){} 3463 | if(data[0] == '-'){ 3464 | sub = data.substr(1, i-1); 3465 | data.erase(0, i+1); 3466 | int num = stoi(sub); 3467 | l -> val = - num; 3468 | q.push(l); 3469 | } 3470 | else{ 3471 | sub = data.substr(0, i); 3472 | data.erase(0, i+1); 3473 | int num = stoi(sub); 3474 | l -> val = num; 3475 | q.push(l); 3476 | } 3477 | } 3478 | if(data[0] == 'n'){ // 右节点 3479 | r = NULL; 3480 | data.erase(0, 5); 3481 | }else{ 3482 | int i = 0; 3483 | for(i; (data[i] >= '0' && data[i] <= '9') || data[i] == '-'; ++ i){} 3484 | if(data[0] == '-'){ 3485 | sub = data.substr(1, i-1); 3486 | data.erase(0, i + 1); 3487 | int num = stoi(sub); 3488 | r -> val = - num; 3489 | q.push(r); 3490 | } 3491 | else{ 3492 | sub = data.substr(0, i); 3493 | data.erase(0, i + 1); 3494 | int num = stoi(sub); 3495 | r -> val = num; 3496 | q.push(r); 3497 | } 3498 | } 3499 | TreeNode* n = q.front(); 3500 | n -> left = l; //连接root 的 左右节点 3501 | n -> right = r; 3502 | q.pop(); 3503 | } 3504 | return root; 3505 | } 3506 | }; 3507 | ``` 3508 | 3509 | ### [98. 验证二叉搜索树](https://leetcode-cn.com/problems/validate-binary-search-tree/) 3510 | - 对于二叉搜索树,我们可以通过中序遍历得到一个递增的有序序列。因此,中序遍历是二叉搜索树中最常用的遍历方法。 3511 | ```cpp 3512 | class Solution { 3513 | public: 3514 | long pre = LONG_MIN; 3515 | bool isValidBST(TreeNode* root) { 3516 | if(!root) return true; //传统艺能 3517 | if(!isValidBST(root -> left)) return false; // 向左遍历,直到叶节点 3518 | if(pre >= root -> val) return false; // 判断节点值是否大于上一个 3519 | pre = root -> val; // 更新pre 3520 | return isValidBST(root -> right); // 向右遍历 3521 | } 3522 | }; 3523 | ``` 3524 | ### [173. 二叉搜索树迭代器](https://leetcode-cn.com/problems/binary-search-tree-iterator/) 3525 | - 每个节点中的值必须大于(或等于)存储在其左侧子树中的任何值。 3526 | - 每个节点中的值必须小于(或等于)存储在其右子树中的任何值。 3527 | - 对于二叉搜索树,我们可以通过中序遍历得到一个递增的有序序列。因此,中序遍历是二叉搜索树中最常用的遍历方法。 3528 | ```cpp 3529 | class BSTIterator { 3530 | public: 3531 | stack stk; 3532 | BSTIterator(TreeNode* root) { 3533 | box(root); 3534 | } 3535 | void box(TreeNode* root){ //压栈函数 3536 | if(!root) return; 3537 | while(root){ 3538 | stk.push(root); 3539 | root = root -> left; 3540 | } 3541 | } 3542 | 3543 | /** @return the next smallest number */ 3544 | int next() { //获取当前值 3545 | TreeNode* now = stk.top(); 3546 | stk.pop(); 3547 | int ans = now -> val; // 若有右子树,压入栈内 3548 | if(now -> right) box(now -> right); 3549 | return ans; 3550 | } 3551 | 3552 | /** @return whether we have a next smallest number */ 3553 | bool hasNext() { 3554 | return !stk.empty(); 3555 | } 3556 | }; 3557 | ``` 3558 | # 题库解析 3559 | 默认已经看过题目 🤡 点击标题可跳转对应题目网址。 3560 | ## 数组 3561 | ### [238. Product of Array Except Self 双指针](https://leetcode.com/problems/product-of-array-except-self/) 3562 | ```cpp 3563 | class Solution { 3564 | public: 3565 | vector productExceptSelf(vector& nums) { 3566 | int n = nums.size(); 3567 | vector res(n, 1); 3568 | 3569 | int l = 1; 3570 | for(int i=0; i=0; --j){ 3577 | res[j] *= r; 3578 | r *= nums[j]; 3579 | } 3580 | 3581 | return res; 3582 | } 3583 | }; 3584 | ``` 3585 | - 本题利用双指针,新数组每个位置上的值应该等于数组左边所有数字的乘积 × 数组右边所有数字的乘积 3586 | - 1.初始化一个新的数组res(result),包含n个1 3587 | 3588 | 2.初始化变量l(left)代表左边的乘积,从左到右遍历数组,每次都让新数组的值乘以它左边数字的乘积l,然后更新l。此时新数组里的所有数字就代表了nums数组中对应位置左边所有数字的乘积 3589 | 3590 | 3.再从右往左做一遍同样的操作,最终`res[i] = 1 * nums中i左边所有数字的乘积 * nums中i右边所有数字的乘积` 3591 | 3592 | ### [448. Find All Numbers Disappeared in an Array 伪哈希](https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/) 3593 | ```cpp 3594 | class Solution { 3595 | public: 3596 | vector findDisappearedNumbers(vector& nums) { 3597 | for(int i=0; i r; 3602 | for(int i=0; i 0){ 3604 | r.push_back(i+1); 3605 | } 3606 | } 3607 | return r; 3608 | } 3609 | }; 3610 | ``` 3611 | - 应题目进阶要求,O(N)时间,无额外空间,此解实际上是利用索引把数组自身当作哈希表处理 3612 | - 将 nums 中所有正数作为索引i,置 nums[i] 为负值。那么,仍为正数的位置即为(未出现过)消失的数字 3613 | - 原始数组:[4,3,2,7,8,2,3,1] 3614 | - 重置后为:[-4,-3,-2,-7,`8`,`2`,-3,-1] 3615 | - 结论:[8,2] 分别对应的index为[5,6](消失的数字) 3616 | ## 哈希表 3617 | ### [36. Valid Sudoku 哈希表]() 3618 | ```cpp 3619 | class Solution { 3620 | public: 3621 | bool isValidSudoku(vector>& board) { 3622 | int n = board.size(); 3623 | vector> row(n), col(n); 3624 | vector>> sub(n/3, vector>(n/3)); 3625 | 3626 | for(int i=0; i 0 || col[j][c]++ > 0 || sub[i/3][j/3][c]++ > 0) return false; 3631 | } 3632 | } 3633 | return true; 3634 | } 3635 | }; 3636 | ``` 3637 | - O(N)时间复杂度,为每个分区建立一张映射表,每遍历一个元素就在所属的所有对应分区中记录该值,若发现值已经被更改则证明数独表无效 3638 | ## 链表 3639 | ### [206. Reverse Linked List 前后指针](https://leetcode.com/problems/reverse-linked-list/) 3640 | ```cpp 3641 | /** 3642 | * Definition for singly-linked list. 3643 | * struct ListNode { 3644 | * int val; 3645 | * ListNode *next; 3646 | * ListNode(int x) : val(x), next(NULL) {} 3647 | * }; 3648 | */ 3649 | class Solution { 3650 | public: 3651 | ListNode* reverseList(ListNode* head) { 3652 | ListNode *pre = NULL; 3653 | while(head){ 3654 | ListNode *next = head -> next; 3655 | head -> next = pre; 3656 | pre = head; 3657 | head = next; 3658 | } 3659 | return pre; 3660 | } 3661 | }; 3662 | ``` 3663 | ## 数学 3664 | ### [268. Missing Number 等差数列](https://leetcode.com/problems/missing-number/) 3665 | ```cpp 3666 | class Solution { 3667 | public: 3668 | int missingNumber(vector& nums) { 3669 | int n = nums.size(); 3670 | 3671 | int sum = 0; 3672 | for(int i=0; i& s) { 3690 | int i = -1, j = s.size(); 3691 | while(++i < --j){ 3692 | swap(s[i], s[j]); 3693 | } 3694 | } 3695 | }; 3696 | ``` 3697 | ## 字符串 3698 | ### [13. Roman to Integer 哈希表](https://leetcode.com/problems/roman-to-integer/) 3699 | ```cpp 3700 | class Solution { 3701 | public: 3702 | int romanToInt(string s) { 3703 | unordered_map m = {{"I", 1}, {"IV", 3}, {"IX", 8}, {"V", 5}, {"X", 10}, {"XL", 30}, {"XC", 80}, {"L", 50}, {"C", 100}, {"CD", 300}, {"CM", 800}, {"D", 500}, {"M", 1000}}; 3704 | 3705 | int r = m[s.substr(0, 1)]; 3706 | for(int i=1; i& nums) { 3726 | int l = 0; 3727 | int r = nums.size() - 1; 3728 | 3729 | while(l < r){ 3730 | int m = (l + r) / 2; 3731 | 3732 | if(m > 0 and nums[m-1] > nums[m]){ 3733 | r = m - 1; 3734 | }else if(m < nums.size() and nums[m+1] > nums[m]){ 3735 | l = m + 1; 3736 | }else{ 3737 | return m; 3738 | } 3739 | } 3740 | return l; 3741 | } 3742 | }; 3743 | ``` 3744 | - 初始化搜索范围为[0, len(nums)-1],初始搜索位置为中间位置 m,如果 m 左边存在值比 nums[m] 大,说明[0, m-1]一定存在峰值,我们缩小搜索范围;否则如果 m 右边存在值比 nums[m] 大,说明[m+1, len(nums)-1]一定存在峰值,我们缩小范围;否则 m 就是峰值 3745 | ## 分治算法 3746 | ### [973. K Closest Points to Origin 快速选择](https://leetcode.com/problems/k-closest-points-to-origin/) 3747 | ```cpp 3748 | class Solution { 3749 | public: 3750 | vector> kClosest(vector>& points, int K) { 3751 | int p = pow(points[0][0], 2) + pow(points[0][1], 2); 3752 | vector> l, m, r; 3753 | for(int i=0; i(x ^ y).count(); 3786 | } 3787 | }; 3788 | ``` 3789 | ## 队列 3790 | ### [933. Number of Recent Calls 队列](https://leetcode-cn.com/problems/number-of-recent-calls/) 3791 | ```cpp 3792 | class RecentCounter { 3793 | queue tco; 3794 | public: 3795 | RecentCounter() { 3796 | } 3797 | int ping(int t) { 3798 | while(!tco.empty() && tco.front() >&grid) {//本题地图上的'0','1'被当作字符录入 3815 | if (grid.empty()) return 0; 3816 | int m=grid.size(); 3817 | int n=grid[0].size(); //以上两行求得地图的边界 3818 | int ans=0; //答案初始化为零 3819 | for (int y=0;y>&grid,int x,int y,int m,int n) 3829 | { 3830 | if(x<0||y<0||y>=m||x>=n||grid[y][x]=='0') //遍历的点不能超出地图边界 3831 | return; 3832 | grid[y][x]='0'; //将岛的一部分'1',转换为0 3833 | dfs(grid,x+1,y,m,n); //遍历该点的上下左右 3834 | dfs(grid,x-1,y,m,n); 3835 | dfs(grid,x,y+1,m,n); 3836 | dfs(grid,x,y-1,m,n); 3837 | 3838 | 3839 | } 3840 | }; 3841 | ``` 3842 | - 本题用深度搜索算法来解答,当遭遇一座岛‘1’时,我们通过遍历这座岛每个部分(其它与这个点横竖相连的1)并将它们同化为‘0’,以防止这座岛被重复遍历 3843 | - 这里有一个关键点,本题地图上的点‘1’,‘0’作为字符录入计算机,在计算机中这两个字符会被转化为ASCII码,‘1’的ASCII码比‘0’的数值多1(分别是49,48)。 3844 | ## 栈(stack) 3845 | 3846 | ## 暴力破解 3847 | ### [1. Two Sum](https://leetcode.com/problems/two-sum/) 3848 | ```cpp 3849 | class Solution { 3850 | public: 3851 | vector twoSum(vector& nums, int target) { 3852 | int len = nums.size(); 3853 | int i,j; 3854 | for(i = 0; i < len; ++i) 3855 | { 3856 | for(j = i+1; j < len; ++j) 3857 | { 3858 | int plus = nums[i] + nums[j]; 3859 | if(plus == target) 3860 | return {i,j}; 3861 | } 3862 | } 3863 | return{}; 3864 | } 3865 | }; 3866 | ``` 3867 | - 暴力破解,注意:你不能重复利用这个数组中同样的元素。 3868 | - 其次,若没有答案,需要返回空的数组。 3869 | # 解法汇总贡献者 3870 | 注:此处贡献名单仅代表汇总搜集贡献,不代表全部原创,欢迎所有更短的解法🤓 3871 | - [YouLookDeliciousC](https://github.com/YouLookDeliciousC) [QQ210021997 微信Ccxj_1013] 3872 | - [Knife丶](https://github.com/cy69855522) [QQ1272068154 微信ly18597591102] 3873 | -------------------------------------------------------------------------------- /pics/README.md: -------------------------------------------------------------------------------- 1 | 这里存放一些解析用到的图 2 | -------------------------------------------------------------------------------- /pics/完全平方数.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YouLookDeliciousC/Clearest-LeetCode-Cpp-Solutions/15d13b25f50cd4f4aa427f852cb781838f4242d0/pics/完全平方数.png -------------------------------------------------------------------------------- /思维导图.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YouLookDeliciousC/Clearest-LeetCode-Cpp-Solutions/15d13b25f50cd4f4aa427f852cb781838f4242d0/思维导图.jpg --------------------------------------------------------------------------------