├── Leetcode 题解 - 位运算.md ├── Leetcode 题解 - 分治.md ├── Leetcode 题解 - 双指针.md ├── Leetcode 题解 - 哈希表.md ├── Leetcode 题解 - 栈和队列.md ├── Leetcode 题解 - 树&递归.md ├── Leetcode 题解 - 链表.md ├── README.md └── 数据结构与算法python3总目录.md /Leetcode 题解 - 位运算.md: -------------------------------------------------------------------------------- 1 | # Leetcode 题解 - 位运算 2 | 3 | * [Leetcode 题解 - 位运算](#leetcode-题解---位运算) 4 | * [0. 原理](#0-原理) 5 | * [1. 统计两个数的二进制表示有多少位不同](#1-统计两个数的二进制表示有多少位不同) 6 | * [2. 数组中唯一一个不重复的元素](#2-数组中唯一一个不重复的元素) 7 | * [3. 找出数组中缺失的那个数](#3-找出数组中缺失的那个数) 8 | * [4. 数组中不重复的两个元素](#4-数组中不重复的两个元素) 9 | * [5. 翻转一个数的比特位](#5-翻转一个数的比特位) 10 | * [6. 不用额外变量交换两个整数](#6-不用额外变量交换两个整数) 11 | * [7. 判断一个数是不是 2 的 n 次方](#7-判断一个数是不是-2-的-n-次方) 12 | * [8. 判断一个数是不是 4 的 n 次方](#8--判断一个数是不是-4-的-n-次方) 13 | * [9. 判断一个数的位级表示是否不会出现连续的 0 和 1](#9-判断一个数的位级表示是否不会出现连续的-0-和-1) 14 | * [10. 求一个数的补码](#10-求一个数的补码) 15 | * [11. 实现整数的加法](#11-实现整数的加法) 16 | * [12. 字符串数组最大乘积](#12-字符串数组最大乘积) 17 | * [13. 统计从 0 \~ n 每个数的二进制表示中 1 的个数](#13-统计从-0-\~-n-每个数的二进制表示中-1-的个数) 18 | * [14. 数组中数字出现的次数](#14-数组中数字出现的次数) 19 | * [15. 数组中数字出现的次数II](#15-数组中数字出现的次数II) 20 | 21 | 22 | 23 | ## 0. 原理 24 | 25 | ``` 26 | a = 0011 1100 27 | b = 0000 1101 28 | ----------------- 29 | a&b = 0000 1100 30 | a|b = 0011 1101 31 | a^b = 0011 0001 32 | ~a = 1100 0011 33 | ``` 34 | ![image](https://user-images.githubusercontent.com/70521393/116840048-17dc6380-ac07-11eb-96e0-dc4b9fc449f7.png) 35 | 36 | **基本原理** 37 | 38 | 0s 表示一串 0,1s 表示一串 1。 39 | 40 | ``` 41 | x ^ 0s = x x & 0s = 0 x | 0s = x 42 | x ^ 1s = ~x x & 1s = x x | 1s = 1s 43 | x ^ x = 0 x & x = x x | x = x 44 | ``` 45 | 如果a、b两个值不相同,则异或^结果为1。如果a、b两个值相同,异或^结果为0。 46 | 47 | 利用 x ^ 1s = \~x 的特点,可以将一个数的位级表示翻转;利用 x ^ x = 0 的特点,可以将三个数中重复的两个数去除,只留下另一个数。 48 | 49 | ``` 50 | 1^1^2 = 2 51 | ``` 52 | 53 | 利用 x & 0s = 0 和 x & 1s = x 的特点,可以实现掩码操作。一个数 num 与 mask:00111100 进行位与操作,只保留 num 中与 mask 的 1 部分相对应的位。 54 | 55 | ``` 56 | 01011011 & 57 | 00111100 58 | -------- 59 | 00011000 60 | ``` 61 | 62 | 利用 x | 0s = x 和 x | 1s = 1s 的特点,可以实现设值操作。一个数 num 与 mask:00111100 进行位或操作,将 num 中与 mask 的 1 部分相对应的位都设置为 1。 63 | 64 | ``` 65 | 01011011 | 66 | 00111100 67 | -------- 68 | 01111111 69 | ``` 70 | 71 | **位与运算技巧** 72 | 73 | n&(n-1) 去除 n 的位级表示中最低的那一位 1。例如对于二进制表示 01011011,减去 1 得到 01011010,这两个数相与得到 01011010。 74 | 75 | ``` 76 | 01011011 & 77 | 01011010 78 | -------- 79 | 01011010 80 | ``` 81 | 82 | n&(-n) 得到 n 的位级表示中最低的那一位 1。-n 得到 n 的反码加 1,也就是 -n=\~n+1。例如对于二进制表示 10110100,-n 得到 01001100,相与得到 00000100。 83 | 84 | ``` 85 | 10110100 & 86 | 01001100 87 | -------- 88 | 00000100 89 | ``` 90 | 91 | n-(n&(-n)) 则可以去除 n 的位级表示中最低的那一位 1,和 n&(n-1) 效果一样。 92 | 93 | **mask 计算** 94 | 95 | 要获取 111111111,将 0 取反即可,\~0。 96 | 97 | 要得到只有第 i 位为 1 的 mask,将 1 向左移动 i-1 位即可,1\<\<(i-1) 。例如 1\<\<4 得到只有第 5 位为 1 的 mask :00010000。 98 | 99 | 要得到 1 到 i 位为 1 的 mask,(1\<\> 1; 139 | } 140 | return cnt; 141 | } 142 | ``` 143 | 144 | 使用 z&(z-1) 去除 z 位级表示最低的那一位。 145 | 146 | ```java 147 | public int hammingDistance(int x, int y) { 148 | int z = x ^ y; 149 | int cnt = 0; 150 | while (z != 0) { 151 | z &= (z - 1); 152 | cnt++; 153 | } 154 | return cnt; 155 | } 156 | ``` 157 | 158 | 可以使用 Integer.bitcount() 来统计 1 个的个数。 159 | 160 | ```java 161 | public int hammingDistance(int x, int y) { 162 | return Integer.bitCount(x ^ y); 163 | } 164 | ``` 165 | 166 | ## 2. 数组中唯一一个不重复的元素 167 | 168 | 136\. Single Number (Easy) 169 | 170 | [Leetcode](https://leetcode.com/problems/single-number/description/) / [力扣](https://leetcode-cn.com/problems/single-number/description/) 171 | 172 | ```html 173 | Input: [4,1,2,1,2] 174 | Output: 4 175 | ``` 176 | 177 | 两个相同的数异或的结果为 0,对所有数进行异或操作,最后的结果就是单独出现的那个数。 178 | 179 | ```java 180 | public int singleNumber(int[] nums) { 181 | int ret = 0; 182 | for (int n : nums) ret = ret ^ n; 183 | return ret; 184 | } 185 | ``` 186 | 187 | ## 3. 找出数组中缺失的那个数 188 | 189 | 268\. Missing Number (Easy) 190 | 191 | [Leetcode](https://leetcode.com/problems/missing-number/description/) / [力扣](https://leetcode-cn.com/problems/missing-number/description/) 192 | 193 | ```html 194 | Input: [3,0,1] 195 | Output: 2 196 | ``` 197 | 198 | 题目描述:数组元素在 0-n 之间,但是有一个数是缺失的,要求找到这个缺失的数。 199 | 200 | ```java 201 | public int missingNumber(int[] nums) { 202 | int ret = 0; 203 | for (int i = 0; i < nums.length; i++) { 204 | ret = ret ^ i ^ nums[i]; 205 | } 206 | return ret ^ nums.length; 207 | } 208 | ``` 209 | 210 | ## 4. 数组中不重复的两个元素 211 | 212 | 260\. Single Number III (Medium) 213 | 214 | [Leetcode](https://leetcode.com/problems/single-number-iii/description/) / [力扣](https://leetcode-cn.com/problems/single-number-iii/description/) 215 | 216 | 两个不相等的元素在位级表示上必定会有一位存在不同。 217 | 218 | 将数组的所有元素异或得到的结果为不存在重复的两个元素异或的结果。 219 | 220 | diff &= -diff 得到出 diff 最右侧不为 0 的位,也就是不存在重复的两个元素在位级表示上最右侧不同的那一位,利用这一位就可以将两个元素区分开来。 221 | 222 | ```java 223 | public int[] singleNumber(int[] nums) { 224 | int diff = 0; 225 | for (int num : nums) diff ^= num; 226 | diff &= -diff; // 得到最右一位 227 | int[] ret = new int[2]; 228 | for (int num : nums) { 229 | if ((num & diff) == 0) ret[0] ^= num; 230 | else ret[1] ^= num; 231 | } 232 | return ret; 233 | } 234 | ``` 235 | 236 | ## 5. 翻转一个数的比特位 237 | 238 | 190\. Reverse Bits (Easy) 239 | 240 | [Leetcode](https://leetcode.com/problems/reverse-bits/description/) / [力扣](https://leetcode-cn.com/problems/reverse-bits/description/) 241 | 242 | ```java 243 | public int reverseBits(int n) { 244 | int ret = 0; 245 | for (int i = 0; i < 32; i++) { 246 | ret <<= 1; 247 | ret |= (n & 1); 248 | n >>>= 1; 249 | } 250 | return ret; 251 | } 252 | ``` 253 | 254 | 如果该函数需要被调用很多次,可以将 int 拆成 4 个 byte,然后缓存 byte 对应的比特位翻转,最后再拼接起来。 255 | 256 | ```java 257 | private static Map cache = new HashMap<>(); 258 | 259 | public int reverseBits(int n) { 260 | int ret = 0; 261 | for (int i = 0; i < 4; i++) { 262 | ret <<= 8; 263 | ret |= reverseByte((byte) (n & 0b11111111)); 264 | n >>= 8; 265 | } 266 | return ret; 267 | } 268 | 269 | private int reverseByte(byte b) { 270 | if (cache.containsKey(b)) return cache.get(b); 271 | int ret = 0; 272 | byte t = b; 273 | for (int i = 0; i < 8; i++) { 274 | ret <<= 1; 275 | ret |= t & 1; 276 | t >>= 1; 277 | } 278 | cache.put(b, ret); 279 | return ret; 280 | } 281 | ``` 282 | 283 | ## 6. 不用额外变量交换两个整数 284 | 285 | [程序员代码面试指南 :P317](#) 286 | 287 | ```java 288 | a = a ^ b; 289 | b = a ^ b; 290 | a = a ^ b; 291 | ``` 292 | 293 | ## 7. 判断一个数是不是 2 的 n 次方 294 | 295 | 231\. Power of Two (Easy) 296 | 297 | [Leetcode](https://leetcode.com/problems/power-of-two/description/) / [力扣](https://leetcode-cn.com/problems/power-of-two/description/) 298 | 299 | 二进制表示只有一个 1 存在。 300 | 301 | ```java 302 | public boolean isPowerOfTwo(int n) { 303 | return n > 0 && Integer.bitCount(n) == 1; 304 | } 305 | ``` 306 | 307 | 利用 1000 & 0111 == 0 这种性质,得到以下解法: 308 | 309 | ```java 310 | public boolean isPowerOfTwo(int n) { 311 | return n > 0 && (n & (n - 1)) == 0; 312 | } 313 | ``` 314 | 315 | ## 8. 判断一个数是不是 4 的 n 次方 316 | 317 | 342\. Power of Four (Easy) 318 | 319 | [Leetcode](https://leetcode.com/problems/power-of-four/) / [力扣](https://leetcode-cn.com/problems/power-of-four/) 320 | 321 | 这种数在二进制表示中有且只有一个奇数位为 1,例如 16(10000)。 322 | 323 | ```java 324 | public boolean isPowerOfFour(int num) { 325 | return num > 0 && (num & (num - 1)) == 0 && (num & 0b01010101010101010101010101010101) != 0; 326 | } 327 | ``` 328 | 329 | 也可以使用正则表达式进行匹配。 330 | 331 | ```java 332 | public boolean isPowerOfFour(int num) { 333 | return Integer.toString(num, 4).matches("10*"); 334 | } 335 | ``` 336 | 337 | ## 9. 判断一个数的位级表示是否不会出现连续的 0 和 1 338 | 339 | 693\. Binary Number with Alternating Bits (Easy) 340 | 341 | [Leetcode](https://leetcode.com/problems/binary-number-with-alternating-bits/description/) / [力扣](https://leetcode-cn.com/problems/binary-number-with-alternating-bits/description/) 342 | 343 | ```html 344 | Input: 10 345 | Output: True 346 | Explanation: 347 | The binary representation of 10 is: 1010. 348 | 349 | Input: 11 350 | Output: False 351 | Explanation: 352 | The binary representation of 11 is: 1011. 353 | ``` 354 | 355 | 对于 1010 这种位级表示的数,把它向右移动 1 位得到 101,这两个数每个位都不同,因此异或得到的结果为 1111。 356 | 357 | ```java 358 | public boolean hasAlternatingBits(int n) { 359 | int a = (n ^ (n >> 1)); 360 | return (a & (a + 1)) == 0; 361 | } 362 | ``` 363 | 364 | ## 10. 求一个数的补码 365 | 366 | 476\. Number Complement (Easy) 367 | 368 | [Leetcode](https://leetcode.com/problems/number-complement/description/) / [力扣](https://leetcode-cn.com/problems/number-complement/description/) 369 | 370 | ```html 371 | Input: 5 372 | Output: 2 373 | Explanation: The binary representation of 5 is 101 (no leading zero bits), and its complement is 010. So you need to output 2. 374 | ``` 375 | 376 | 题目描述:不考虑二进制表示中的首 0 部分。 377 | 378 | 对于 00000101,要求补码可以将它与 00000111 进行异或操作。那么问题就转换为求掩码 00000111。 379 | 380 | ```java 381 | public int findComplement(int num) { 382 | if (num == 0) return 1; 383 | int mask = 1 << 30; 384 | while ((num & mask) == 0) mask >>= 1; 385 | mask = (mask << 1) - 1; 386 | return num ^ mask; 387 | } 388 | ``` 389 | 390 | 可以利用 Java 的 Integer.highestOneBit() 方法来获得含有首 1 的数。 391 | 392 | ```java 393 | public int findComplement(int num) { 394 | if (num == 0) return 1; 395 | int mask = Integer.highestOneBit(num); 396 | mask = (mask << 1) - 1; 397 | return num ^ mask; 398 | } 399 | ``` 400 | 401 | 对于 10000000 这样的数要扩展成 11111111,可以利用以下方法: 402 | 403 | ```html 404 | mask |= mask >> 1 11000000 405 | mask |= mask >> 2 11110000 406 | mask |= mask >> 4 11111111 407 | ``` 408 | 409 | ```java 410 | public int findComplement(int num) { 411 | int mask = num; 412 | mask |= mask >> 1; 413 | mask |= mask >> 2; 414 | mask |= mask >> 4; 415 | mask |= mask >> 8; 416 | mask |= mask >> 16; 417 | return (mask ^ num); 418 | } 419 | ``` 420 | 421 | ## 11. 实现整数的加法 422 | 423 | 371\. Sum of Two Integers (Easy) 424 | 425 | [Leetcode](https://leetcode.com/problems/sum-of-two-integers/description/) / [力扣](https://leetcode-cn.com/problems/sum-of-two-integers/description/) 426 | 427 | a ^ b 表示没有考虑进位的情况下两数的和,(a & b) \<\< 1 就是进位。 428 | 429 | 递归会终止的原因是 (a & b) \<\< 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。 430 | 431 | ```java 432 | public int getSum(int a, int b) { 433 | return b == 0 ? a : getSum((a ^ b), (a & b) << 1); 434 | } 435 | ``` 436 | 437 | ## 12. 字符串数组最大乘积 438 | 439 | 318\. Maximum Product of Word Lengths (Medium) 440 | 441 | [Leetcode](https://leetcode.com/problems/maximum-product-of-word-lengths/description/) / [力扣](https://leetcode-cn.com/problems/maximum-product-of-word-lengths/description/) 442 | 443 | ```html 444 | Given ["abcw", "baz", "foo", "bar", "xtfn", "abcdef"] 445 | Return 16 446 | The two words can be "abcw", "xtfn". 447 | ``` 448 | 449 | 题目描述:字符串数组的字符串只含有小写字符。求解字符串数组中两个字符串长度的最大乘积,要求这两个字符串不能含有相同字符。 450 | 451 | 本题主要问题是判断两个字符串是否含相同字符,由于字符串只含有小写字符,总共 26 位,因此可以用一个 32 位的整数来存储每个字符是否出现过。 452 | 453 | ```java 454 | public int maxProduct(String[] words) { 455 | int n = words.length; 456 | int[] val = new int[n]; 457 | for (int i = 0; i < n; i++) { 458 | for (char c : words[i].toCharArray()) { 459 | val[i] |= 1 << (c - 'a'); 460 | } 461 | } 462 | int ret = 0; 463 | for (int i = 0; i < n; i++) { 464 | for (int j = i + 1; j < n; j++) { 465 | if ((val[i] & val[j]) == 0) { 466 | ret = Math.max(ret, words[i].length() * words[j].length()); 467 | } 468 | } 469 | } 470 | return ret; 471 | } 472 | ``` 473 | 474 | ## 13. 统计从 0 \~ n 每个数的二进制表示中 1 的个数 475 | 476 | 338\. Counting Bits (Medium) 477 | 478 | [Leetcode](https://leetcode.com/problems/counting-bits/description/) / [力扣](https://leetcode-cn.com/problems/counting-bits/description/) 479 | 480 | 对于数字 6(110),它可以看成是 4(100) 再加一个 2(10),因此 dp[i] = dp[i&(i-1)] + 1; 481 | 482 | ```java 483 | public int[] countBits(int num) { 484 | int[] ret = new int[num + 1]; 485 | for(int i = 1; i <= num; i++){ 486 | ret[i] = ret[i&(i-1)] + 1; 487 | } 488 | return ret; 489 | } 490 | ``` 491 | 492 | ## 14. 数组中数字出现的次数 493 | 494 | 剑指 Offer 56 - I. 数组中数字出现的次数 [力扣](https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/) 495 | 496 | 找出数组中两个没有重复的数字 497 | 498 | 方法一:位运算 499 | reduce() 函数会对参数序列中元素进行累积。 500 | 函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。 501 | 502 | 如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。 503 | 把a和b分到两组,每组里其它的数都是出现两次的。 504 | 505 | 如何分组?随便在**异或结果**中取一位,这位为0的放一组,为1的放另外一组。 506 | 因为a、b两个数在**异或结果**中为1的那位上必然不同。 507 | 508 | ```python 509 | class Solution: 510 | def singleNumbers(self, nums: List[int]) -> List[int]: 511 | ret = functools.reduce(lambda x, y: x ^ y, nums)#因为重复的数字异或为0,所以全部异或的结果就是两个只出现过一次的数字的异或结果 512 | div = 1 513 | while div & ret == 0:#python3中 位与& 优先级高于 等于== ,先div&ret,再判断是否等于0 514 | div <<= 1#找到异或结果中为1的那一位 515 | a, b = 0, 0 516 | for n in nums: 517 | if n & div: 518 | a ^= n#该位为1的为a组,全部异或在一起 519 | else: 520 | b ^= n#该位为0的为b组,全部异或在一起 521 | return [a, b] 522 | ``` 523 | 524 | 方法二:用字典 525 | ```python 526 | class Solution: 527 | def singleNumbers(self, nums: List[int]) -> List[int]: 528 | dic = {} 529 | for num in nums: 530 | if num not in dic: 531 | dic[num] = 1 532 | else: 533 | dic[num] += 1 534 | res = [] 535 | for key,val in dic.items(): 536 | if val == 1: 537 | res.append(key) 538 | return res 539 | ``` 540 | 541 | ## 15. 数组中数字出现的次数II 542 | 543 | 剑指 Offer 56 - II. 数组中数字出现的次数 II [力扣](https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/) 544 | 545 | 解法:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/solution/mian-shi-ti-56-ii-shu-zu-zhong-shu-zi-chu-xian-d-4/ 546 | 547 | 548 | 方法一:字典 549 | 550 | 用字典存数和数出现的次数,然后交换字典键值,输出次数为1的。 551 | 552 | ```python 553 | class Solution: 554 | def singleNumber(self, nums: List[int]) -> int: 555 | dic = {} 556 | for num in nums: 557 | if num not in dic: 558 | dic[num] = 1#一开始没有,所以赋值1(等于0+1) 559 | else: 560 | dic[num] += 1 561 | for key, value in dic.items():#以列表返回可遍历的(键, 值) 元组数组 562 | if value == 1: 563 | return key 564 | ``` 565 | 566 | 567 | 方法二:遍历统计 568 | 569 | ```python 570 | class Solution: 571 | def singleNumber(self, nums: List[int]) -> int: 572 | counts = [0] * 32 573 | for num in nums: 574 | for j in range(32): 575 | counts[j] += num & 1#每一位进行计数 576 | num >>= 1#第一位是在最左边,所以从左向右移位(右移!) 577 | res, m = 0, 3 578 | #利用 左移操作 和 或运算,将 counts 数组中各二进位的值恢复到数字 res 上(循环区间是 i∈[0,31] )。 579 | for i in range(32): 580 | res <<= 1 581 | res |= counts[31 - i] % m#每一位对m求余数,i=0时,counts[31]即在最左边(最高位)的哪一位 582 | if counts[31] % m == 0: 583 | return res 584 | else: 585 | return ~(res ^ 0xffffffff) 586 | ``` 587 | 588 | 由于 Python 的存储负数的特殊性,需要先将 0 - 32 位取反(即 res ^ 0xffffffff ),再将所有位取反(即 ~ )。 589 | 两个组合操作实质上是将数字 32 以上位取反, 0 - 32 位不变。 590 | 591 | 复杂度分析: 592 | 时间复杂度 O(N) : 其中 N 位数组 nums 的长度;遍历数组占用 O(N) ,每轮中的常数个位运算操作占用 O(1) 。 593 | 空间复杂度 O(1) : 数组 counts 长度恒为 32 ,占用常数大小的额外空间。 594 | -------------------------------------------------------------------------------- /Leetcode 题解 - 分治.md: -------------------------------------------------------------------------------- 1 | # Leetcode 题解 - 分治 2 | 3 | * [Leetcode 题解 - 分治](#leetcode-题解---分治) 4 | * [1. 给表达式加括号](#1-给表达式加括号) 5 | * [2. 不同的二叉搜索树](#2-不同的二叉搜索树) 6 | 7 | 8 | 9 | ## 1. 给表达式加括号 10 | 11 | 241\. Different Ways to Add Parentheses (Medium) 12 | 13 | [Leetcode](https://leetcode.com/problems/different-ways-to-add-parentheses/description/) / [力扣](https://leetcode-cn.com/problems/different-ways-to-add-parentheses/description/) 14 | 15 | ```html 16 | Input: "2-1-1". 17 | 18 | ((2-1)-1) = 0 19 | (2-(1-1)) = 2 20 | 21 | Output : [0, 2] 22 | ``` 23 | 24 | 对于一个形如 x op y(op 为运算符,x 和 y 为数) 的算式而言,它的结果组合取决于 x 和 y 的结果组合数,而 x 和 y 又可以写成形如 x op y 的算式。 25 | 26 | 因此,该问题的子问题就是 x op y 中的 x 和 y:以运算符分隔的左右两侧算式解。 27 | 28 | 然后我们来进行 分治算法三步走: 29 | 30 | 1.分解:按运算符分成左右两部分,分别求解 31 | 2.解决:实现一个递归函数,输入算式,返回算式解 32 | 3.合并:根据运算符合并左右两部分的解,得出最终解 33 | 34 | ```python3 35 | class Solution: 36 | def diffWaysToCompute(self, input: str) -> List[int]: 37 | # 如果只有数字,直接返回 38 | if input.isdigit(): 39 | return [int(input)] 40 | 41 | res = [] 42 | for i, char in enumerate(input): 43 | if char in ['+', '-', '*']: 44 | # 1.分解:遇到运算符,计算左右两侧的结果集 45 | # 2.解决:diffWaysToCompute 递归函数求出子问题的解 46 | left = self.diffWaysToCompute(input[:i]) 47 | right = self.diffWaysToCompute(input[i+1:]) 48 | # 3.合并:根据运算符合并子问题的解 49 | for l in left: 50 | for r in right: 51 | if char == '+': 52 | res.append(l + r) 53 | elif char == '-': 54 | res.append(l - r) 55 | else: 56 | res.append(l * r) 57 | 58 | return res 59 | ``` 60 | 61 | ## 2. 不同的二叉搜索树 62 | 63 | 95\. Unique Binary Search Trees II (Medium) 64 | 65 | [Leetcode](https://leetcode.com/problems/unique-binary-search-trees-ii/description/) / [力扣](https://leetcode-cn.com/problems/unique-binary-search-trees-ii/description/) 66 | 67 | 给定一个数字 n,要求生成所有值为 1...n 的二叉搜索树。 68 | 69 | ```html 70 | Input: 3 71 | Output: 72 | [ 73 | [1,null,3,2], 74 | [3,2,null,1], 75 | [3,1,null,null,2], 76 | [2,1,3], 77 | [1,null,2,null,3] 78 | ] 79 | Explanation: 80 | The above output corresponds to the 5 unique BST's shown below: 81 | 82 | 1 3 3 2 1 83 | \ / / / \ \ 84 | 3 2 1 1 3 2 85 | / / \ \ 86 | 2 1 2 3 87 | ``` 88 | 89 | ```java 90 | public List generateTrees(int n) { 91 | if (n < 1) { 92 | return new LinkedList(); 93 | } 94 | return generateSubtrees(1, n); 95 | } 96 | 97 | private List generateSubtrees(int s, int e) { 98 | List res = new LinkedList(); 99 | if (s > e) { 100 | res.add(null); 101 | return res; 102 | } 103 | for (int i = s; i <= e; ++i) { 104 | List leftSubtrees = generateSubtrees(s, i - 1); 105 | List rightSubtrees = generateSubtrees(i + 1, e); 106 | for (TreeNode left : leftSubtrees) { 107 | for (TreeNode right : rightSubtrees) { 108 | TreeNode root = new TreeNode(i); 109 | root.left = left; 110 | root.right = right; 111 | res.add(root); 112 | } 113 | } 114 | } 115 | return res; 116 | } 117 | ``` 118 | -------------------------------------------------------------------------------- /Leetcode 题解 - 双指针.md: -------------------------------------------------------------------------------- 1 | # Leetcode 题解 - 双指针 2 | 3 | * [Leetcode 题解 - 双指针](#leetcode-题解---双指针) 4 | * [1. 有序数组的 Two Sum](#1-有序数组的-two-sum) 5 | * [2. 两数平方和](#2-两数平方和) 6 | * [3. 反转字符串中的元音字符](#3-反转字符串中的元音字符) 7 | * [4. 回文字符串](#4-回文字符串) 8 | * [5. 归并两个有序数组](#5-归并两个有序数组) 9 | * [6. 判断链表是否存在环](#6-判断链表是否存在环) 10 | * [7. 最长子序列](#7-最长子序列) 11 | * [8. 移动零](#8-移动零) 12 | 13 | 14 | 15 | 双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。 16 | 17 | ## 1. 有序数组的 Two Sum 18 | 19 | 167\. Two Sum II - Input array is sorted (Easy) 20 | 21 | [Leetcode](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/description/) / [力扣](https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted/description/) 22 | 23 | ```html 24 | Input: numbers={2, 7, 11, 15}, target=9 25 | Output: index1=1, index2=2 26 | ``` 27 | 28 | 题目描述:在有序数组中找出两个数,使它们的和为 target。 29 | 30 | 使用双指针,一个指针指向值较小的元素,一个指针指向值较大的元素。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。 31 | 32 | - 如果两个指针指向元素的和 sum == target,那么得到要求的结果; 33 | - 如果 sum \> target,移动较大的元素,使 sum 变小一些; 34 | - 如果 sum \< target,移动较小的元素,使 sum 变大一些。 35 | 36 | 数组中的元素最多遍历一次,时间复杂度为 O(N)。只使用了两个额外变量,空间复杂度为 O(1)。 37 | 38 |

39 | 40 | ```java 41 | public int[] twoSum(int[] numbers, int target) { 42 | if (numbers == null) return null; 43 | int i = 0, j = numbers.length - 1; 44 | while (i < j) { 45 | int sum = numbers[i] + numbers[j]; 46 | if (sum == target) { 47 | return new int[]{i + 1, j + 1}; 48 | } else if (sum < target) { 49 | i++; 50 | } else { 51 | j--; 52 | } 53 | } 54 | return null; 55 | } 56 | ``` 57 | 58 | ## 2. 两数平方和 59 | 60 | 633\. Sum of Square Numbers (Easy) 61 | 62 | [Leetcode](https://leetcode.com/problems/sum-of-square-numbers/description/) / [力扣](https://leetcode-cn.com/problems/sum-of-square-numbers/description/) 63 | 64 | ```html 65 | Input: 5 66 | Output: True 67 | Explanation: 1 * 1 + 2 * 2 = 5 68 | ``` 69 | 70 | 题目描述:判断一个非负整数是否为两个整数的平方和。 71 | 72 | 可以看成是在元素为 0\~target 的有序数组中查找两个数,使得这两个数的平方和为 target,如果能找到,则返回 true,表示 target 是两个整数的平方和。 73 | 74 | 本题和 167\. Two Sum II - Input array is sorted 类似,只有一个明显区别:一个是和为 target,一个是平方和为 target。本题同样可以使用双指针得到两个数,使其平方和为 target。 75 | 76 | 本题的关键是右指针的初始化,实现剪枝,从而降低时间复杂度。设右指针为 x,左指针固定为 0,为了使 02 + x2 的值尽可能接近 target,我们可以将 x 取为 sqrt(target)。 77 | 78 | 因为最多只需要遍历一次 0\~sqrt(target),所以时间复杂度为 O(sqrt(target))。又因为只使用了两个额外的变量,因此空间复杂度为 O(1)。 79 | 80 | ```java 81 | public boolean judgeSquareSum(int target) { 82 | if (target < 0) return false; 83 | int i = 0, j = (int) Math.sqrt(target); 84 | while (i <= j) { 85 | int powSum = i * i + j * j; 86 | if (powSum == target) { 87 | return true; 88 | } else if (powSum > target) { 89 | j--; 90 | } else { 91 | i++; 92 | } 93 | } 94 | return false; 95 | } 96 | ``` 97 | 98 | ## 3. 反转字符串中的元音字符 99 | 100 | 345\. Reverse Vowels of a String (Easy) 101 | 102 | [Leetcode](https://leetcode.com/problems/reverse-vowels-of-a-string/description/) / [力扣](https://leetcode-cn.com/problems/reverse-vowels-of-a-string/description/) 103 | 104 | ```html 105 | Given s = "leetcode", return "leotcede". 106 | ``` 107 | 108 |

109 | 110 | 使用双指针,一个指针从头向尾遍历,一个指针从尾到头遍历,当两个指针都遍历到元音字符时,交换这两个元音字符。 111 | 112 | 为了快速判断一个字符是不是元音字符,我们将全部元音字符添加到集合 HashSet 中,从而以 O(1) 的时间复杂度进行该操作。 113 | 114 | - 时间复杂度为 O(N):只需要遍历所有元素一次 115 | - 空间复杂度 O(1):只需要使用两个额外变量 116 | 117 |

118 | 119 | ```java 120 | private final static HashSet vowels = new HashSet<>( 121 | Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U')); 122 | 123 | public String reverseVowels(String s) { 124 | if (s == null) return null; 125 | int i = 0, j = s.length() - 1; 126 | char[] result = new char[s.length()]; 127 | while (i <= j) { 128 | char ci = s.charAt(i); 129 | char cj = s.charAt(j); 130 | if (!vowels.contains(ci)) { 131 | result[i++] = ci; 132 | } else if (!vowels.contains(cj)) { 133 | result[j--] = cj; 134 | } else { 135 | result[i++] = cj; 136 | result[j--] = ci; 137 | } 138 | } 139 | return new String(result); 140 | } 141 | ``` 142 | 143 | ## 4. 回文字符串 144 | 145 | 680\. Valid Palindrome II (Easy) 146 | 147 | [Leetcode](https://leetcode.com/problems/valid-palindrome-ii/description/) / [力扣](https://leetcode-cn.com/problems/valid-palindrome-ii/description/) 148 | 149 | ```html 150 | Input: "abca" 151 | Output: True 152 | Explanation: You could delete the character 'c'. 153 | ``` 154 | 155 | 题目描述:可以删除一个字符,判断是否能构成回文字符串。 156 | 157 | 所谓的回文字符串,是指具有左右对称特点的字符串,例如 "abcba" 就是一个回文字符串。 158 | 159 | 使用双指针可以很容易判断一个字符串是否是回文字符串:令一个指针从左到右遍历,一个指针从右到左遍历,这两个指针同时移动一个位置,每次都判断两个指针指向的字符是否相同,如果都相同,字符串才是具有左右对称性质的回文字符串。 160 | 161 |

162 | 163 | 本题的关键是处理删除一个字符。在使用双指针遍历字符串时,如果出现两个指针指向的字符不相等的情况,我们就试着删除一个字符,再判断删除完之后的字符串是否是回文字符串。 164 | 165 | 在判断是否为回文字符串时,我们不需要判断整个字符串,因为左指针左边和右指针右边的字符之前已经判断过具有对称性质,所以只需要判断中间的子字符串即可。 166 | 167 | 在试着删除字符时,我们既可以删除左指针指向的字符,也可以删除右指针指向的字符。 168 | 169 |

170 | 171 | ```java 172 | public boolean validPalindrome(String s) { 173 | for (int i = 0, j = s.length() - 1; i < j; i++, j--) { 174 | if (s.charAt(i) != s.charAt(j)) { 175 | return isPalindrome(s, i, j - 1) || isPalindrome(s, i + 1, j); 176 | } 177 | } 178 | return true; 179 | } 180 | 181 | private boolean isPalindrome(String s, int i, int j) { 182 | while (i < j) { 183 | if (s.charAt(i++) != s.charAt(j--)) { 184 | return false; 185 | } 186 | } 187 | return true; 188 | } 189 | ``` 190 | 191 | ## 5. 归并两个有序数组 192 | 193 | 88\. Merge Sorted Array (Easy) 194 | 195 | [Leetcode](https://leetcode.com/problems/merge-sorted-array/description/) / [力扣](https://leetcode-cn.com/problems/merge-sorted-array/description/) 196 | 197 | ```html 198 | Input: 199 | nums1 = [1,2,3,0,0,0], m = 3 200 | nums2 = [2,5,6], n = 3 201 | 202 | Output: [1,2,2,3,5,6] 203 | ``` 204 | 205 | 题目描述:把归并结果存到第一个数组上。 206 | 207 | 需要从尾开始遍历,否则在 nums1 上归并得到的值会覆盖还未进行归并比较的值。 208 | 209 | ```java 210 | public void merge(int[] nums1, int m, int[] nums2, int n) { 211 | int index1 = m - 1, index2 = n - 1; 212 | int indexMerge = m + n - 1; 213 | while (index2 >= 0) { 214 | if (index1 < 0) { 215 | nums1[indexMerge--] = nums2[index2--]; 216 | } else if (index2 < 0) { 217 | nums1[indexMerge--] = nums1[index1--]; 218 | } else if (nums1[index1] > nums2[index2]) { 219 | nums1[indexMerge--] = nums1[index1--]; 220 | } else { 221 | nums1[indexMerge--] = nums2[index2--]; 222 | } 223 | } 224 | } 225 | ``` 226 | 227 | ## 6. 判断链表是否存在环 228 | 229 | 141\. Linked List Cycle (Easy) 230 | 231 | [Leetcode](https://leetcode.com/problems/linked-list-cycle/description/) / [力扣](https://leetcode-cn.com/problems/linked-list-cycle/description/) 232 | 233 | 使用双指针,一个指针每次移动一个节点,一个指针每次移动两个节点,如果存在环,那么这两个指针一定会相遇。 234 | 235 | ```java 236 | public boolean hasCycle(ListNode head) { 237 | if (head == null) { 238 | return false; 239 | } 240 | ListNode l1 = head, l2 = head.next; 241 | while (l1 != null && l2 != null && l2.next != null) { 242 | if (l1 == l2) { 243 | return true; 244 | } 245 | l1 = l1.next; 246 | l2 = l2.next.next; 247 | } 248 | return false; 249 | } 250 | ``` 251 | 252 | ## 7. 最长子序列 253 | 254 | 524\. Longest Word in Dictionary through Deleting (Medium) 255 | 256 | [Leetcode](https://leetcode.com/problems/longest-word-in-dictionary-through-deleting/description/) / [力扣](https://leetcode-cn.com/problems/longest-word-in-dictionary-through-deleting/description/) 257 | 258 | ``` 259 | Input: 260 | s = "abpcplea", d = ["ale","apple","monkey","plea"] 261 | 262 | Output: 263 | "apple" 264 | ``` 265 | 266 | 题目描述:删除 s 中的一些字符,使得它构成字符串列表 d 中的一个字符串,找出能构成的最长字符串。如果有多个相同长度的结果,返回字典序的最小字符串。 267 | 268 | 通过删除字符串 s 中的一个字符能得到字符串 t,可以认为 t 是 s 的子序列,我们可以使用双指针来判断一个字符串是否为另一个字符串的子序列。 269 | 270 | ```java 271 | public String findLongestWord(String s, List d) { 272 | String longestWord = ""; 273 | for (String target : d) { 274 | int l1 = longestWord.length(), l2 = target.length(); 275 | if (l1 > l2 || (l1 == l2 && longestWord.compareTo(target) < 0)) { 276 | continue; 277 | } 278 | if (isSubstr(s, target)) { 279 | longestWord = target; 280 | } 281 | } 282 | return longestWord; 283 | } 284 | 285 | private boolean isSubstr(String s, String target) { 286 | int i = 0, j = 0; 287 | while (i < s.length() && j < target.length()) { 288 | if (s.charAt(i) == target.charAt(j)) { 289 | j++; 290 | } 291 | i++; 292 | } 293 | return j == target.length(); 294 | } 295 | ``` 296 | 297 | 298 | ## 8. 移动零 299 | 300 | 283\. Move Zeroes (Easy)[力扣](https://leetcode-cn.com/problems/move-zeroes/) 301 | 302 | Given an integer array nums, move all 0's to the end of it while maintaining the relative order of the non-zero elements. 303 | 304 | Note that you must do this in-place without making a copy of the array. 305 | ``` 306 | Example 1: 307 | 308 | Input: nums = [0,1,0,3,12] 309 | Output: [1,3,12,0,0] 310 | Example 2: 311 | 312 | Input: nums = [0] 313 | Output: [0] 314 | 315 | 316 | Constraints: 317 | 318 | 1 <= nums.length <= 104 319 | -231 <= nums[i] <= 231 - 1 320 | ``` 321 | 使用双指针,左指针指向当前已经处理好的序列的尾部,右指针指向待处理序列的头部。 322 | 323 | 右指针不断向右移动,每次右指针指向非零数,则将左右指针对应的数交换,同时左指针右移。 324 | ![1111](https://user-images.githubusercontent.com/70521393/118089617-22fb7480-b3fb-11eb-8ef7-3f74d1cdda49.gif) 325 | 326 | 注意到以下性质: 327 | 328 | **左指针左边**均为非零数; 329 | 330 | **右指针左边直到左指针处**均为零。 331 | 332 | 因此每次交换,都是将左指针的零与右指针的非零数交换,且非零数的相对顺序并未改变。 333 | 334 | ```python3 335 | class Solution: 336 | def moveZeroes(self, nums: List[int]) -> None: 337 | """ 338 | Do not return anything, modify nums in-place instead. 339 | """ 340 | n = len(nums) 341 | left = right = 0 342 | while right < n: 343 | if nums[right] != 0:#调换位置时关键是右边不为0 344 | nums[left], nums[right] = nums[right], nums[left] 345 | left += 1 346 | right += 1 347 | ``` 348 | 复杂度分析: 349 | 350 | 时间复杂度:O(n),其中 n 为序列长度。每个位置至多被遍历两次。 351 | 352 | 空间复杂度:O(1)。只需要常数的空间存放若干变量。 353 | -------------------------------------------------------------------------------- /Leetcode 题解 - 哈希表.md: -------------------------------------------------------------------------------- 1 | # Leetcode 题解 - 哈希表 2 | 3 | * [Leetcode 题解 - 哈希表](#leetcode-题解---哈希表) 4 | * [1. 数组中两个数的和为给定值](#1-数组中两个数的和为给定值) 5 | * [2. 判断数组是否含有重复元素](#2-判断数组是否含有重复元素) 6 | * [3. 数组中重复的数字](#3-数组中重复的数字) 7 | * [4. 最长和谐序列](#4-最长和谐序列) 8 | * [5. 最长连续序列](#5-最长连续序列) 9 | 10 | 11 | 1.**哈希(Hash)**,一般翻译做散列、杂凑,或音译为哈希,是把**任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出**,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将**任意长度的消息压缩到某一固定长度的消息摘要**的函数。 12 | 13 | 2.**哈希函数(散列函数)**,能够将任意长度的输入值转变成固定长度的值输出,该值称为散列值,输出值通常为字母与数字组合。 14 | 15 | 3.**[哈希表](https://baike.baidu.com/item/%E6%95%A3%E5%88%97%E8%A1%A8/10027933)(Hash table,也叫散列表)**,是根据关键码值(Key value)而直接进行访问的[数据结构](https://baike.baidu.com/item/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/1450)。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做[散列函数](https://baike.baidu.com/item/%E6%95%A3%E5%88%97%E5%87%BD%E6%95%B0/2366288),**存放记录的[数组](https://baike.baidu.com/item/%E6%95%B0%E7%BB%84/3794097)叫做[散列表](https://baike.baidu.com/item/%E6%95%A3%E5%88%97%E8%A1%A8/10027933)。** 16 | 17 | **给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。** 18 | 19 | ## 1. 数组中两个数的和为给定值 20 | 21 | 1\. Two Sum (Easy) 22 | 23 | [力扣](https://leetcode-cn.com/problems/two-sum/description/)/[题解](https://leetcode-cn.com/problems/two-sum/solution/liang-shu-zhi-he-by-leetcode-solution/) 24 | 25 | 用查找表法,查找表有两种:哈希表和平衡二叉搜索树。因为不用保存顺序,所以用哈希表。 26 | 27 | 用hashtable(一个字典)存储数组元素和索引的映射,在访问到 nums[i] 时,判断hashtable中是否存在 target - nums[i],如果存在说明 target - nums[i] 所在的索引和 i 就是要找的两个数。该方法的时间复杂度为 O(N),空间复杂度为 O(N),**使用空间来换取时间。** 28 | 29 | dict() 函数用于创建一个字典。 30 | 31 | >dict()# 创建空字典 32 | >输出:{} 33 | 34 | > dict(a='a', b='b', t='t')# 传入关键字 35 | > 输出:{'a': 'a', 'b': 'b', 't': 't'} 36 | 37 | >dict(zip(['one', 'two', 'three'], [1, 2, 3])) # 映射函数方式来构造字典 38 | >输出:{'three': 3, 'two': 2, 'one': 1} 39 | 40 | >dict([('one', 1), ('two', 2), ('three', 3)]) # 可迭代对象方式来构造字典 41 | >输出:{'three': 3, 'two': 2, 'one': 1} 42 | 43 | [enumerate() 函数](https://www.runoob.com/python/python-func-enumerate.html)用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出**数据下标和数据**,一般用在 for 循环当中。 44 | 45 | ```python 46 | class Solution: 47 | def twoSum(self, nums: List[int], target: int) -> List[int]: 48 | hashtable = dict() 49 | for i, num in enumerate(nums): 50 | if target - num in hashtable:#target - num的计算结果在哈希表里的键key中,所以键key为数值,值value为下标 51 | return [hashtable[target - num], i]#找出target - num的下标和num对应的下标(注意num和nums的区别!) 52 | hashtable[nums[i]] = i#最后输出的是下标,所以哈希表里键key为数值,值value为下标 53 | return [] 54 | ``` 55 | 56 | ## 2. 判断数组是否含有重复元素 57 | 58 | 217\. Contains Duplicate (Easy) 59 | 60 | [力扣](https://leetcode-cn.com/problems/contains-duplicate/description/)/[题解](https://leetcode-cn.com/problems/contains-duplicate/solution/cun-zai-zhong-fu-de-yuan-su-yi-yi-ti-san-ihfa/) 61 | 62 | 63 | [set() 函数](https://www.runoob.com/python/python-func-set.html/) 创建一个无序不重复元素集,可进行关系测试,删除重复数据,还可以计算交集、差集、并集等。 64 | 65 | ```python 66 | class Solution: 67 | def containsDuplicate(self, nums: List[int]) -> bool: 68 | return len(nums) != len(set(nums)) 69 | 70 | ``` 71 | 72 | ## 3. 数组中重复的数字 73 | 74 | 03\. 数组中重复的数字 (Easy) 75 | 76 | [力扣](https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/)/[题解](https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/solution/mian-shi-ti-03-shu-zu-zhong-zhong-fu-de-shu-zi-yua/) 77 | 78 | [add() 方法](https://www.runoob.com/python3/ref-set-add.html/)用于给集合添加元素,如果添加的元素在集合中已存在,则不执行任何操作。 79 | 80 | ```python 81 | class Solution: 82 | def findRepeatNumber(self, nums: [int]) -> int: 83 | dic = set() 84 | for num in nums: 85 | if num in dic: 86 | return num#在dic中了,则输出来 87 | dic.add(num)#如果num不在dic中则加入dic 88 | return -1 89 | ``` 90 | 复杂度分析: 91 | 时间复杂度 O(N) : 遍历数组使用 O(N) ,HashSet 添加与查找元素皆为 O(1) 。 92 | 空间复杂度 O(N) : HashSet 占用 O(N) 大小的额外空间。 93 | 94 | 95 | ## 4. 最长和谐序列 96 | 97 | 594\. Longest Harmonious Subsequence (Easy) 98 | 99 | [Leetcode](https://leetcode.com/problems/longest-harmonious-subsequence/description/) / [力扣](https://leetcode-cn.com/problems/longest-harmonious-subsequence/description/) 100 | 101 | ```html 102 | Input: [1,3,2,2,5,2,3,7] 103 | Output: 5 104 | Explanation: The longest harmonious subsequence is [3,2,2,2,3]. 105 | ``` 106 | 107 | 和谐序列中最大数和最小数之差正好为 1,应该注意的是序列的元素不一定是数组的连续元素。 108 | 109 | 解题思路: 110 | 利用哈希表存储数组中元素出现的次数。 111 | 遍历哈希表,如果当前元素+1也在哈希表中,那么计算两者次数之和,保留最大值。 112 | 113 | 字典 get() 函数返回指定键的值。 114 | 语法: 115 | dict.get(key, default=None) 116 | 参数: 117 | key -- 字典中要查找的键。 118 | default -- 如果指定的键不存在时,返回该默认值。 119 | 120 | ```python 121 | class Solution: 122 | def findLHS(self, nums: List[int]) -> int: 123 | dicts={}#用键key存num数,用值value存num数的出现次数。 124 | for num in nums: 125 | dicts[num]=dicts.get(num,0)+1#一开始dicts为空,没有元素(没有i),所以返回值为0,需要+1! 126 | res=0 127 | for num in dicts: 128 | if num+1 in dicts: 129 | res=max(res,dicts[num]+dicts[num+1]) 130 | return res 131 | ``` 132 | 133 | ## 5. 最长连续序列 134 | 135 | 128\. Longest Consecutive Sequence (Hard) 136 | 137 | [Leetcode](https://leetcode.com/problems/longest-consecutive-sequence/description/) / [力扣](https://leetcode-cn.com/problems/longest-consecutive-sequence/description/) 138 | 139 | ```html 140 | Given [100, 4, 200, 1, 3, 2], 141 | The longest consecutive elements sequence is [1, 2, 3, 4]. Return its length: 4. 142 | ``` 143 | 144 | 要求以 O(N) 的时间复杂度求解。 145 | 146 | +=, 加法赋值运算符, c += a 等效于c = c + a 147 | 148 | ```python 149 | class Solution: 150 | def longestConsecutive(self, nums): 151 | longest_streak = 0 152 | num_set = set(nums)#去掉重复的元素 153 | 154 | for num in num_set: 155 | if num - 1 not in num_set:#保证开始计算序列长度时,是连续序列的起点(没有比num小1的值) 156 | current_num = num#满足条件时,起点即为num 157 | current_streak = 1#此时的序列长为1 158 | 159 | while current_num + 1 in num_set:#注意这里是CURRENT_NUM+1! 160 | current_num += 1#序列数+1(因为是连续序列) 161 | current_streak += 1#序列长度+1 162 | 163 | longest_streak = max(longest_streak, current_streak) 164 | 165 | return longest_streak 166 | ``` 167 | -------------------------------------------------------------------------------- /Leetcode 题解 - 栈和队列.md: -------------------------------------------------------------------------------- 1 | # Leetcode101-python3 2 | # Leetcode101 题解 - 栈和队列 3 | 4 | * [Leetcode 题解 - 栈和队列](#leetcode-题解---栈和队列) 5 | * [1. 用栈实现队列](#1-用栈实现队列) 6 | * [2. 用队列实现栈](#2-用队列实现栈) 7 | * [3. 最小值栈](#3-最小值栈) 8 | * [4. 用栈实现括号匹配](#4-用栈实现括号匹配) 9 | * [5. 数组中元素与下一个比它大的元素之间的距离](#5-数组中元素与下一个比它大的元素之间的距离) 10 | * [6. 循环数组中比当前元素大的下一个元素](#6-循环数组中比当前元素大的下一个元素) 11 | * [7. 基本计算器II](#7-基本计算器II) 12 | 13 | 14 | 15 | ## 1. 用栈实现队列 16 | 17 | 232\. Implement Queue using Stacks (Easy) 18 | 19 | [Leetcode](https://leetcode.com/problems/implement-queue-using-stacks/description/) / [力扣](https://leetcode-cn.com/problems/implement-queue-using-stacks/description/) 20 | 21 | 栈的顺序为后进先出,而队列的顺序为先进先出。使用两个栈实现队列,一个元素需要经过两个栈才能出队列,在经过第一个栈时元素顺序被反转,经过第二个栈时再次被反转,此时就是先进先出顺序。 22 | 23 | ```python 24 | class MyQueue(object): 25 | 26 | def __init__(self): 27 | self.stack1 = [] 28 | self.stack2 = [] 29 | 30 | def push(self, x): 31 | self.stack1.append(x) 32 | 33 | def pop(self): 34 | if not self.stack2: 35 | while self.stack1: 36 | self.stack2.append(self.stack1.pop()) 37 | return self.stack2.pop() 38 | 39 | def peek(self): 40 | if not self.stack2: 41 | while self.stack1: 42 | self.stack2.append(self.stack1.pop()) 43 | return self.stack2[-1] 44 | 45 | def empty(self): 46 | return not self.stack1 and not self.stack2 47 | ``` 48 | 49 | ## 2. 用队列实现栈 50 | 51 | 225\. Implement Stack using Queues (Easy) 52 | 53 | [Leetcode](https://leetcode.com/problems/implement-stack-using-queues/description/) / [力扣](https://leetcode-cn.com/problems/implement-stack-using-queues/description/) 54 | 55 | 方法1:两个队列 56 | 57 | 58 | ```python 59 | class MyStack: 60 | 61 | def __init__(self): 62 | """ 63 | Initialize your data structure here. 64 | """ 65 | self.queue1 = collections.deque() 66 | self.queue2 = collections.deque() 67 | 68 | 69 | def push(self, x: int) -> None: 70 | """ 71 | Push element x onto stack. 72 | """ 73 | self.queue2.append(x) 74 | while self.queue1: 75 | self.queue2.append(self.queue1.popleft())把之前的所有元素放的新元素的队后,让新元素在队首(先入先出) 76 | self.queue1, self.queue2 = self.queue2, self.queue1 77 | 78 | 79 | def pop(self) -> int: 80 | """ 81 | Removes the element on top of the stack and returns that element. 82 | """ 83 | return self.queue1.popleft() 84 | 85 | 86 | def top(self) -> int: 87 | """ 88 | Get the top element. 89 | """ 90 | return self.queue1[0] 91 | 92 | 93 | def empty(self) -> bool: 94 | """ 95 | Returns whether the stack is empty. 96 | """ 97 | return not self.queue1 98 | 99 | ``` 100 | 101 | 方法2:一个队列 102 | 主要是入栈的时候,先把队列之前的所有元素全部依次出队再入队(一个队列,从队前出,又进队尾,可以想象成一个环) 103 | 104 | ```python 105 | class MyStack: 106 | 107 | def __init__(self): 108 | """ 109 | Initialize your data structure here. 110 | """ 111 | self.queue = collections.deque() 112 | 113 | 114 | def push(self, x: int) -> None: 115 | """ 116 | Push element x onto stack. 117 | """ 118 | n = len(self.queue)#把加入x之前有n个元素 119 | self.queue.append(x) 120 | for _ in range(n):#把加入x之前的n个元素全部拿出再放回,保证x在队列最前,即先进先出 121 | self.queue.append(self.queue.popleft()) 122 | 123 | 124 | def pop(self) -> int: 125 | """ 126 | Removes the element on top of the stack and returns that element. 127 | """ 128 | return self.queue.popleft() 129 | 130 | 131 | def top(self) -> int: 132 | """ 133 | Get the top element. 134 | """ 135 | return self.queue[0]#队列最前最先出,最先出即为栈顶 136 | 137 | 138 | def empty(self) -> bool: 139 | """ 140 | Returns whether the stack is empty. 141 | """ 142 | return not self.queue 143 | 144 | ``` 145 | 146 | 147 | ## 3. 最小值栈 148 | 149 | 155\. Min Stack (Easy) 150 | 151 | [Leetcode](https://leetcode.com/problems/min-stack/description/) / [力扣](https://leetcode-cn.com/problems/min-stack/description/) 152 | 153 | 用一个辅助栈来存“每一次入栈push时”的最小值,所以注意每次出栈时,两个栈都要出。 154 | 155 | ```python 156 | class MinStack: 157 | def __init__(self): 158 | self.stack = [] 159 | self.min_stack = [math.inf] 160 | 161 | def push(self, x: int) -> None: 162 | self.stack.append(x) 163 | self.min_stack.append(min(x, self.min_stack[-1]))#前面记得加self 164 | 165 | def pop(self) -> None: 166 | self.stack.pop() 167 | self.min_stack.pop() 168 | 169 | def top(self) -> int: 170 | return self.stack[-1] 171 | 172 | def getMin(self) -> int: 173 | return self.min_stack[-1] 174 | 175 | ``` 176 | 177 | 对于实现最小值队列问题,可以先将队列使用栈来实现,然后就将问题转换为最小值栈,这个问题出现在 编程之美:3.7。 178 | 179 | ## 4. 用栈实现括号匹配 180 | 181 | 20\. Valid Parentheses (Easy) 182 | 183 | [Leetcode](https://leetcode.com/problems/valid-parentheses/description/) / [力扣](https://leetcode-cn.com/problems/valid-parentheses/description/) 184 | 185 | ```html 186 | "()[]{}" 187 | 188 | Output : true 189 | ``` 190 | 只把左括号“{,(,[” 放入栈中,等待匹配 191 | 192 | in和字典的操作,判断键是否存在于字典中: https://www.runoob.com/python3/python3-att-dictionary-in-html.html 193 | 194 | ```python 195 | class Solution: 196 | def isValid(self, s: str) -> bool: 197 | dic = {')':'(', 198 | ']':'[', 199 | '}':'{'} 200 | stack = [] 201 | for i in s:#遍历s字符串中的字符 202 | if stack and i in dic:#如果stack不为空,且i为dic的键,即为右括号时,进行匹配 203 | if stack[-1] == dic[i]: 204 | stack.pop() 205 | else: 206 | return False 207 | else:#如果stcak为空,或,i不是dic的键,即为左括号,则入栈 208 | stack.append(i) 209 | return not stack 210 | ``` 211 | 212 | ## 5. 数组中元素与下一个比它大的元素之间的距离 213 | 214 | 739\. Daily Temperatures (Medium) 215 | 216 | [Leetcode](https://leetcode.com/problems/daily-temperatures/description/) / [力扣](https://leetcode-cn.com/problems/daily-temperatures/description/) 217 | 218 | ```html 219 | Input: [73, 74, 75, 71, 69, 72, 76, 73] 220 | Output: [1, 1, 4, 2, 1, 1, 0, 0] 221 | ``` 222 | 223 | 单调栈: 224 | 单调栈满足从栈底到栈顶元素对应的温度递减,因此每次有元素进栈时,会将温度更低的元素全部移除,并更新出栈元素对应的等待天数,这样可以确保等待天数一定是最小的。 225 | 226 | ```python 227 | class Solution: 228 | def dailyTemperatures(self, T: List[int]) -> List[int]: 229 | length = len(T) 230 | ans = [0] * length 231 | stack = []#单调栈,用于存储下标不存储温度(用于计算升温间隔日期),但是存储的下标对应温度递减,栈底对应温度最高。 232 | for i in range(length): 233 | temperature = T[i] 234 | while stack and temperature > T[stack[-1]]:#当栈不为空,且新温度大于当前栈顶温度时 235 | prev_index = stack.pop()#之前的最高温度对应的下标出栈 236 | ans[prev_index] = i - prev_index#计算升温间隔日期 237 | stack.append(i)#新温度小于等于当前栈顶温度,则入栈 238 | return ans 239 | 240 | ``` 241 | 242 | ## 6. 循环数组中比当前元素大的下一个元素 243 | 244 | 503\. Next Greater Element II (Medium) 245 | 246 | [Leetcode](https://leetcode.com/problems/next-greater-element-ii/description/) / [力扣](https://leetcode-cn.com/problems/next-greater-element-ii/description/) 247 | 248 | ```text 249 | Input: [1,2,1] 250 | Output: [2,-1,2] 251 | Explanation: The first 1's next greater number is 2; 252 | The number 2 can't find next greater number; 253 | The second 1's next greater number needs to search circularly, which is also 2. 254 | ``` 255 | 256 | 与 739. Daily Temperatures (Medium) 不同的是,数组是循环数组,并且最后要求的不是距离而是下一个元素。 257 | 同样使用单调栈,但是 258 | 使用取模运算 % 可以把下标 i 映射到数组 nums 长度的 0 - N 内。 259 | 260 | ```python 261 | class Solution: 262 | def nextGreaterElements(self, nums: List[int]) -> List[int]: 263 | n = len(nums) 264 | res = [-1] * n 265 | stk = list() 266 | 267 | for i in range(n * 2 - 1): 268 | value = nums[i % n] 269 | while stk and value > nums[stk[-1]]: 270 | res[stk.pop()] = value 271 | stk.append(i % n) 272 | 273 | return res 274 | 275 | ``` 276 | 277 | ## 7. 基本计算器II 278 | 279 | 227\. Basic Calculator II(Medium) 280 | 给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。 281 | 282 | 整数除法仅保留整数部分。 283 | [力扣](https://leetcode-cn.com/problems/basic-calculator-ii/) 284 | 285 | 一个运算符表达式分为三个部分,可以用下面的情况表示: 286 | 287 | 数字①, 运算符②, 数字③ 288 | 289 | 数字①,在栈中保存,为栈顶的元素; 290 | 运算符②,用一个变量 pre_op 保存; 291 | 数字③,用一个变量 num 保存。 292 | 293 | 操作情况: 294 | 运算符②,决定了现在的操作: 295 | 296 | 如果 运算符② 为 +, - :如果是+,是把数字③入栈;如果 -,是把 数字③取反再入栈。 297 | 如果 运算符② 为 *,/ ,则需要计算 数字① 运算符② 数字③,然后把结果 入栈。 298 | 这样遍历一次后,优先把所有的 *,/ 都计算出来,而且与需要做加减运算的数字一起,全都都放到了栈中,对栈求和,即为最终的结果。 299 | 300 | enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出**数据下标和数据**,一般用在 for 循环当中。 301 | 302 | //: 取整除 - 向下取接近商的整数,-9//2=-5 303 | 但题目要求保留整数部分,所以直接用浮点除再取整数 304 | 305 | ```python 306 | class Solution: 307 | def calculate(self, s): 308 | stack = [] 309 | pre_op = '+' 310 | num = 0 311 | for i, each in enumerate(s): 312 | if each.isdigit(): 313 | num = 10 * num + int(each)#保证能够识别多位数(两位及两位以上的数字) 314 | if i == len(s) - 1 or each in '+-*/':#i循环到最后或者循环到运算符 315 | stack.append(num)#如果是加号直接入栈 316 | elif pre_op == '-': 317 | stack.append(-num)#如果是负号取反入栈 318 | elif pre_op == '*': 319 | stack.append(stack.pop() * num)#如果是乘号,取栈顶计算,将结果入栈 320 | elif pre_op == '/': 321 | top = stack.pop() 322 | stack.append(int(top / num))#如果是除号,取栈顶计算,将结果入栈 323 | pre_op = each#每一步更新pre_op 324 | num = 0#每次将num清0 325 | return sum(stack) 326 | ``` 327 | -------------------------------------------------------------------------------- /Leetcode 题解 - 树&递归.md: -------------------------------------------------------------------------------- 1 | # Leetcode 题解 - 树&递归 2 | 3 | * [Leetcode 题解 - 树&递归](#leetcode-题解---树&递归) 4 | * [递归](#递归) 5 | * [1. 树的高度](#1-树的高度) 6 | * [2. 平衡树](#2-平衡树) 7 | * [3. 二叉树的直径](#3-二叉树的直径) 8 | * [4. 翻转树](#4-翻转树) 9 | * [5. 合并两棵树](#5-合并两棵树) 10 | * [6. 路径总和](#6-路径总和) 11 | * [7. 路径总和III](#7-路径总和III) 12 | * [8. 另一个树的子树](#8-另一个树的子树) 13 | * [9. 树的对称](#9-树的对称) 14 | * [10. 最小路径](#10-最小路径) 15 | * [11. 统计左叶子节点的和](#11-统计左叶子节点的和) 16 | * [12. 相同节点值的最大路径长度](#12-相同节点值的最大路径长度) 17 | * [13. 间隔遍历](#13-间隔遍历) 18 | * [14. 找出二叉树中第二小的节点](#14-找出二叉树中第二小的节点) 19 | * [层次遍历](#层次遍历) 20 | * [1. 一棵树每层节点的平均数](#1-一棵树每层节点的平均数) 21 | * [2. 得到左下角的节点](#2-得到左下角的节点) 22 | * [前中后序遍历](#前中后序遍历) 23 | * [1. 非递归实现二叉树的前序遍历](#1-非递归实现二叉树的前序遍历) 24 | * [2. 非递归实现二叉树的后序遍历](#2-非递归实现二叉树的后序遍历) 25 | * [3. 非递归实现二叉树的中序遍历](#3-非递归实现二叉树的中序遍历) 26 | * [BST](#bst) 27 | * [1. 修剪二叉查找树](#1-修剪二叉查找树) 28 | * [2. 寻找二叉查找树的第 k 个元素](#2-寻找二叉查找树的第-k-个元素) 29 | * [3. 把二叉查找树每个节点的值都加上比它大的节点的值](#3-把二叉查找树每个节点的值都加上比它大的节点的值) 30 | * [4. 二叉查找树的最近公共祖先](#4-二叉查找树的最近公共祖先) 31 | * [5. 二叉树的最近公共祖先](#5-二叉树的最近公共祖先) 32 | * [6. 从有序数组中构造二叉查找树](#6-从有序数组中构造二叉查找树) 33 | * [7. 根据有序链表构造平衡的二叉查找树](#7-根据有序链表构造平衡的二叉查找树) 34 | * [8. 在二叉查找树中寻找两个节点,使它们的和为一个给定值](#8-在二叉查找树中寻找两个节点,使它们的和为一个给定值) 35 | * [9. 在二叉查找树中查找两个节点之差的最小绝对值](#9-在二叉查找树中查找两个节点之差的最小绝对值) 36 | * [10. 寻找二叉查找树中出现次数最多的值](#10-寻找二叉查找树中出现次数最多的值) 37 | * [Trie](#trie) 38 | * [1. 实现一个 Trie](#1-实现一个-trie) 39 | * [2. 实现一个 Trie,用来求前缀和](#2-实现一个-trie,用来求前缀和) 40 | 41 | 42 | 43 | ## 递归 44 | 45 | 一棵树要么是空树,要么有两个指针,每个指针指向一棵树。树是一种递归结构,很多树的问题可以使用递归来处理。 46 | 47 | ### 1. 树的高度 48 | 49 | 104\. Maximum Depth of Binary Tree (Easy) 50 | 51 | [Leetcode](https://leetcode.com/problems/maximum-depth-of-binary-tree/description/) / [力扣](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/description/) 52 | 53 | 方法一:深度优先搜索DFS 54 | 55 | 如果我们知道了左子树和右子树的最大深度 l 和 r,那么该二叉树的最大深度即为 56 | 57 | max(l,r)+1 58 | 59 | 而左子树和右子树的最大深度又可以以同样的方式进行计算。因此我们可以用「深度优先搜索」的方法来计算二叉树的最大深度。具体而言,在计算当前二叉树的最大深度时,可以先递归计算出其左子树和右子树的最大深度,然后在 O(1) 时间内计算出当前二叉树的最大深度。递归在访问到空节点时退出。 60 | 61 | ```python 62 | # Definition for a binary tree node. 63 | # class TreeNode: 64 | # def __init__(self, val=0, left=None, right=None): 65 | # self.val = val 66 | # self.left = left 67 | # self.right = right 68 | class Solution: 69 | def maxDepth(self, root): 70 | if root is None: 71 | return 0 72 | else: 73 | left_height = self.maxDepth(root.left) 74 | right_height = self.maxDepth(root.right) 75 | return max(left_height, right_height) + 1 76 | ``` 77 | 复杂度分析 78 | 79 | 时间复杂度:O(n),其中 n 为二叉树节点的个数。每个节点在递归中只被遍历一次。 80 | 81 | 空间复杂度:O(height),其中 height 表示二叉树的高度。递归函数需要栈空间,而栈空间取决于递归的深度,因此空间复杂度等价于二叉树的高度。 82 | 83 | ### 2. 平衡树 84 | 85 | 110\. Balanced Binary Tree (Easy) 86 | 87 | [Leetcode](https://leetcode.com/problems/balanced-binary-tree/description/) / [力扣](https://leetcode-cn.com/problems/balanced-binary-tree/description/) 88 | 89 | ```html 90 | 3 91 | / \ 92 | 9 20 93 | / \ 94 | 15 7 95 | ``` 96 | 97 | 平衡树左右子树高度差都小于等于 1 98 | 99 | **自底向上递归**的做法类似于后序遍历,对于当前遍历到的节点,先递归地判断其左右子树是否平衡,再判断以当前节点为根的子树是否平衡。 100 | 101 | 如果使用自底向上的做法,则对于每个节点,函数 height 只会被调用一次。 102 | 103 | 如果一棵子树是平衡的,则返回其高度(高度一定是非负整数),否则返回 -1。如果存在一棵子树不平衡,则整个二叉树一定不平衡。 104 | 105 | ```python 106 | class Solution: 107 | def isBalanced(self, root: TreeNode) -> bool: 108 | def height(root: TreeNode) -> int: 109 | if not root: 110 | return 0 111 | leftHeight = height(root.left) 112 | rightHeight = height(root.right) 113 | if leftHeight == -1 or rightHeight == -1 or abs(leftHeight - rightHeight) > 1: 114 | return -1 115 | else: 116 | return max(leftHeight, rightHeight) + 1 117 | 118 | return height(root) >= 0 119 | ``` 120 | 复杂度分析 121 | 122 | 时间复杂度:O(n),其中 n 是二叉树中的节点个数。使用自底向上的递归,每个节点的计算高度和判断是否平衡都只需要处理一次,最坏情况下需要遍历二叉树中的所有节点,因此时间复杂度是 O(n)。 123 | 124 | 空间复杂度:O(n),其中 n 是二叉树中的节点个数。空间复杂度主要取决于递归调用的层数,递归调用的层数不会超过 n。 125 | 126 | 127 | ### 3. 二叉树的直径 128 | 129 | 543\. Diameter of Binary Tree (Easy) 130 | 131 | 给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。 132 | 133 | [Leetcode](https://leetcode.com/problems/diameter-of-binary-tree/description/) / [力扣](https://leetcode-cn.com/problems/diameter-of-binary-tree/description/) 134 | 135 | ```html 136 | Input: 137 | 138 | 1 139 | / \ 140 | 2 3 141 | / \ 142 | 4 5 143 | 144 | Return 3, which is the length of the path [4,2,1,3] or [5,2,1,3]. 145 | ``` 146 | 147 | ```python3 148 | # Definition for a binary tree node. 149 | # class TreeNode: 150 | # def __init__(self, val=0, left=None, right=None): 151 | # self.val = val 152 | # self.left = left 153 | # self.right = right 154 | class Solution: 155 | def diameterOfBinaryTree(self, root: TreeNode) -> int: 156 | self.ans = 1 157 | def depth(node): 158 | if not node: 159 | return 0 160 | 161 | L = depth(node.left) 162 | R = depth(node.right) 163 | 164 | self.ans = max(self.ans , L+R+1)#以该节点为起点的路径经过节点数的最大值即为L+R+1 165 | return max(L,R) + 1 166 | 167 | depth(root) 168 | return self.ans - 1#一条路径的长度为该路径经过的节点数减一(两节点之间边的数目) 169 | ``` 170 | ```python3 171 | class Solution: 172 | def diameterOfBinaryTree(self, root: TreeNode) -> int: 173 | self.ans = 1 174 | def depth(node): 175 | # 访问到空节点了,返回0 176 | if not node: 177 | return 0 178 | # 左儿子为根的子树的深度 179 | L = depth(node.left) 180 | # 右儿子为根的子树的深度 181 | R = depth(node.right) 182 | self.ans = max(self.ans, L + R)#以该节点为起点的路径长度L+R 183 | # 返回该节点为根的子树的深度 184 | return max(L, R) + 1 185 | 186 | depth(root) 187 | return self.ans#返回以root节点为起点的路径长度 188 | ``` 189 | 190 | ### 4. 翻转树 191 | 192 | 226\. Invert Binary Tree (Easy) 193 | 194 | [Leetcode](https://leetcode.com/problems/invert-binary-tree/description/) / [力扣](https://leetcode-cn.com/problems/invert-binary-tree/description/) 195 | 196 | 我们从根节点开始,递归地对树进行遍历,并从叶子结点先开始翻转。如果当前遍历到的节点 root 的左右两棵子树都已经翻转,那么我们只需要交换两棵子树的位置,即可完成以 root 为根节点的整棵子树的翻转。 197 | 198 | ```python 199 | class Solution: 200 | def invertTree(self, root: TreeNode) -> TreeNode: 201 | if not root: 202 | return root 203 | 204 | left = self.invertTree(root.left) 205 | right = self.invertTree(root.right) 206 | root.left, root.right = right, left 207 | return root 208 | ``` 209 | 复杂度分析 210 | 211 | 时间复杂度:O(N),其中 N 为二叉树节点的数目。我们会遍历二叉树中的每一个节点,对每个节点而言,我们在常数时间内交换其两棵子树。 212 | 213 | 空间复杂度:O(N)。使用的空间由递归栈的深度决定,它等于当前节点在二叉树中的高度。在平均情况下,二叉树的高度与节点个数为对数关系,即O(logN)。而在最坏情况下,树形成链状,空间复杂度为 O(N)。 214 | 215 | 216 | ### 5. 合并两棵树 217 | 218 | 617\. Merge Two Binary Trees (Easy) 219 | 220 | [Leetcode](https://leetcode.com/problems/merge-two-binary-trees/description/) / [力扣](https://leetcode-cn.com/problems/merge-two-binary-trees/description/) 221 | 222 | ```html 223 | Input: 224 | Tree 1 Tree 2 225 | 1 2 226 | / \ / \ 227 | 3 2 1 3 228 | / \ \ 229 | 5 4 7 230 | 231 | Output: 232 | 3 233 | / \ 234 | 4 5 235 | / \ \ 236 | 5 4 7 237 | ``` 238 | 239 | 递归 240 | 对于两棵树的同一位置,只会存在三种情况: 241 | 242 | 1.两树非空,那么 root1.val = root1.val + root2.val 243 | 2.Tree1Tree1 或 Tree2Tree2 其中一颗不存在, 只需将存在的那个节点返回给上层。 244 | 3.两树都空,返回空。(代码中利用第二点的顺序判定已经将此种情况涵盖,故不需要单独写出) 245 | 246 | 247 | ```python3 248 | # Definition for a binary tree node. 249 | # class TreeNode: 250 | # def __init__(self, val=0, left=None, right=None): 251 | # self.val = val 252 | # self.left = left 253 | # self.right = right 254 | class Solution: 255 | def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode: 256 | if not root1: 257 | return root2 258 | if not root2: 259 | return root1 260 | 261 | merged = TreeNode(root1.val + root2.val)#值为root.val+root2.val的节点 262 | merged.left = self.mergeTrees(root1.left , root2.left) 263 | merged.right = self.mergeTrees(root1.right , root2.right) 264 | return merged 265 | ``` 266 | 267 | ### 6. 路径总和 268 | 269 | Leetcdoe : 112. Path Sum (Easy) 270 | 271 | [Leetcode](https://leetcode.com/problems/path-sum/description/) / [力扣](https://leetcode-cn.com/problems/path-sum/description/) 272 | 273 | ```html 274 | Given the below binary tree and sum = 22, 275 | 276 | 5 277 | / \ 278 | 4 8 279 | / / \ 280 | 11 13 4 281 | / \ \ 282 | 7 2 1 283 | 284 | return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22. 285 | ``` 286 | 287 | 路径和定义为从 root 到 leaf 的所有节点的和。 288 | 289 | 递归 290 | 思路及算法 291 | 292 | 观察要求我们完成的函数,我们可以归纳出它的功能:询问是否存在从当前节点 root 到叶子节点的路径,满足其路径和为 sum。 293 | 294 | 假定从根节点到当前节点的值之和为 val,我们可以将这个大问题转化为一个小问题:是否存在从当前节点的子节点到叶子的路径,满足其路径和为 sum - val。 295 | 296 | 不难发现这满足递归的性质,若当前节点就是叶子节点,那么我们直接判断 sum 是否等于 val 即可(因为路径和已经确定,就是当前节点的值,我们只需要判断该路径和是否满足条件)。若当前节点不是叶子节点,我们只需要递归地询问它的子节点是否能满足条件即可。 297 | 298 | ```python3 299 | class Solution: 300 | def hasPathSum(self, root: TreeNode, sum: int) -> bool: 301 | if not root: 302 | return False 303 | if not root.left and not root.right: 304 | return sum == root.val 305 | return self.hasPathSum(root.left, sum - root.val) or self.hasPathSum(root.right, sum - root.val) 306 | ``` 307 | 复杂度分析 308 | 309 | 时间复杂度:O(N),其中 N 是树的节点数。对每个节点访问一次。 310 | 311 | 空间复杂度:O(H),其中 H 是树的高度。空间复杂度主要取决于递归时栈空间的开销,最坏情况下,树呈现链状,空间复杂度为 O(N)。平均情况下树的高度与节点数的对数正相关,空间复杂度为 O(log N)。 312 | 313 | 314 | ### 7. 路径总和III 315 | 316 | 437\. Path Sum III (Medium) 317 | 318 | [Leetcode](https://leetcode.com/problems/path-sum-iii/description/) / [力扣](https://leetcode-cn.com/problems/path-sum-iii/description/) 319 | 320 | ```html 321 | root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8 322 | 323 | 10 324 | / \ 325 | 5 -3 326 | / \ \ 327 | 3 2 11 328 | / \ \ 329 | 3 -2 1 330 | 331 | Return 3. The paths that sum to 8 are: 332 | 333 | 1. 5 -> 3 334 | 2. 5 -> 2 -> 1 335 | 3. -3 -> 11 336 | ``` 337 | 338 | 路径不一定以 root 开头,也不一定以 leaf 结尾,但是必须连续。 339 | 340 | 解题思路 341 | 只需一次递归五行代码,用列表记录下当前结果即可,为什么要双重递归呢? 342 | 343 | sumlist[]记录当前路径上的和,在如下样例中: 344 | ```html 345 | 10 346 | / \ 347 | 5 -3 348 | / \ \ 349 | 3 2 11 350 | / \ \ 351 | 3 -2 1 352 | ``` 353 | 当DFS刚走到2时,此时sumlist[]从根节点10到2的变化过程为: 354 | 355 | 10 356 | 15 5 357 | 17 7 2 358 | 当DFS继续走到1时,此时sumlist[]从节点2到1的变化为: 359 | 360 | 18 8 3 1 361 | 因此,只需计算每一步中,sum在数组sumlist中出现的次数,然后与每一轮递归的结果相加即可 362 | 363 | count = sumlist.count(sum)等价于: 364 | 365 | count = 0 366 | for num in sumlist: 367 | if num == sum: 368 | count += 1 369 | count计算本轮sum在数组sumlist中出现的次数 370 | 371 | ```python3 372 | # Definition for a binary tree node. 373 | # class TreeNode: 374 | # def __init__(self, x): 375 | # self.val = x 376 | # self.left = None 377 | # self.right = None 378 | 379 | 380 | # 精简版,五行代码不解释 381 | class Solution: 382 | def pathSum(self, root: TreeNode, sum: int) -> int: 383 | def dfs(root, sumlist): 384 | if root is None: return 0 385 | sumlist = [num + root.val for num in sumlist] + [root.val] 386 | return sumlist.count(sum) + dfs(root.left, sumlist) + dfs(root.right, sumlist) 387 | return dfs(root, []) 388 | ``` 389 | 390 | ```python3 391 | # 展开版,易理解 392 | class Solution: 393 | def pathSum(self, root: TreeNode, sum: int) -> int: 394 | 395 | def dfs(root, sumlist): 396 | if root is None: 397 | return 0 398 | 399 | sumlist = [num+root.val for num in sumlist] 400 | sumlist.append(root.val) 401 | 402 | count = 0 403 | for num in sumlist: 404 | if num == sum: 405 | count += 1 406 | # count = sumlist.count(sum) 407 | 408 | return count + dfs(root.left, sumlist) + dfs(root.right, sumlist) 409 | 410 | return dfs(root, []) 411 | ``` 412 | 413 | ### 8. 另一个树的子树 414 | 415 | 572\. Subtree of Another Tree (Easy) 416 | 417 | [Leetcode](https://leetcode.com/problems/subtree-of-another-tree/description/) / [力扣](https://leetcode-cn.com/problems/subtree-of-another-tree/description/) 418 | 419 | ```html 420 | Given tree s: 421 | 3 422 | / \ 423 | 4 5 424 | / \ 425 | 1 2 426 | 427 | Given tree t: 428 | 4 429 | / \ 430 | 1 2 431 | 432 | Return true, because t has the same structure and node values with a subtree of s. 433 | 434 | Given tree s: 435 | 436 | 3 437 | / \ 438 | 4 5 439 | / \ 440 | 1 2 441 | / 442 | 0 443 | 444 | Given tree t: 445 | 4 446 | / \ 447 | 1 2 448 | 449 | Return false. 450 | ``` 451 | 452 | ```python3 453 | # Definition for a binary tree node. 454 | # class TreeNode: 455 | # def __init__(self, val=0, left=None, right=None): 456 | # self.val = val 457 | # self.left = left 458 | # self.right = right 459 | class Solution: 460 | def isSubtree(self, root: TreeNode, subRoot: TreeNode) -> bool: 461 | if not root and not subRoot: 462 | return True 463 | if not root or not subRoot: 464 | return False 465 | #判断是子树的逻辑都是或 466 | return self.isSametree(root,subRoot) or self.isSubtree(root.left , subRoot) or self.isSubtree(root.right , subRoot) 467 | 468 | def isSametree(self, root: TreeNode, subRoot: TreeNode) -> bool: 469 | if not root and not subRoot: 470 | return True 471 | if not root or not subRoot: 472 | return False 473 | #判断相等的逻辑都是与 474 | return root.val == subRoot.val and self.isSametree(root.left , subRoot.left) and self.isSametree(root.right , subRoot.right) 475 | ``` 476 | 477 | ### 9. 树的对称 478 | 479 | 101\. Symmetric Tree (Easy) 480 | 481 | [Leetcode](https://leetcode.com/problems/symmetric-tree/description/) / [力扣](https://leetcode-cn.com/problems/symmetric-tree/description/) 482 | 483 | ```html 484 | 1 485 | / \ 486 | 2 2 487 | / \ / \ 488 | 3 4 4 3 489 | ``` 490 | 491 | ```python3 492 | class Solution(object): 493 | def isSymmetric(self, root): 494 | """ 495 | :type root: TreeNode 496 | :rtype: bool 497 | """ 498 | if not root: 499 | return True 500 | def dfs(left,right): 501 | # 递归的终止条件是两个节点都为空 502 | # 或者两个节点中有一个为空 503 | # 或者两个节点的值不相等 504 | if not (left or right):#等价于 not left and not right 505 | return True 506 | if not (left and right):#等价于 not left or not right 507 | return False 508 | if left.val!=right.val: 509 | return False 510 | return dfs(left.left,right.right) and dfs(left.right,right.left) 511 | # 用递归函数,比较左节点,右节点 512 | return dfs(root.left,root.right) 513 | ``` 514 | 515 | ### 10. 最小路径 516 | 517 | 111\. Minimum Depth of Binary Tree (Easy) 518 | 519 | [Leetcode](https://leetcode.com/problems/minimum-depth-of-binary-tree/description/) / [力扣](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/description/) 520 | 521 | 树的根节点到叶子节点的最小路径长度 522 | 523 | ```java 524 | public int minDepth(TreeNode root) { 525 | if (root == null) return 0; 526 | int left = minDepth(root.left); 527 | int right = minDepth(root.right); 528 | if (left == 0 || right == 0) return left + right + 1; 529 | return Math.min(left, right) + 1; 530 | } 531 | ``` 532 | 533 | ### 11. 统计左叶子节点的和 534 | 535 | 404\. Sum of Left Leaves (Easy) 536 | 537 | [Leetcode](https://leetcode.com/problems/sum-of-left-leaves/description/) / [力扣](https://leetcode-cn.com/problems/sum-of-left-leaves/description/) 538 | 539 | ```html 540 | 3 541 | / \ 542 | 9 20 543 | / \ 544 | 15 7 545 | 546 | There are two left leaves in the binary tree, with values 9 and 15 respectively. Return 24. 547 | ``` 548 | 549 | ```java 550 | public int sumOfLeftLeaves(TreeNode root) { 551 | if (root == null) return 0; 552 | if (isLeaf(root.left)) return root.left.val + sumOfLeftLeaves(root.right); 553 | return sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right); 554 | } 555 | 556 | private boolean isLeaf(TreeNode node){ 557 | if (node == null) return false; 558 | return node.left == null && node.right == null; 559 | } 560 | ``` 561 | 562 | ### 12. 相同节点值的最大路径长度 563 | 564 | 687\. Longest Univalue Path (Easy) 565 | 566 | [Leetcode](https://leetcode.com/problems/longest-univalue-path/) / [力扣](https://leetcode-cn.com/problems/longest-univalue-path/) 567 | 568 | ```html 569 | 1 570 | / \ 571 | 4 5 572 | / \ \ 573 | 4 4 5 574 | 575 | Output : 2 576 | ``` 577 | 578 | ```java 579 | private int path = 0; 580 | 581 | public int longestUnivaluePath(TreeNode root) { 582 | dfs(root); 583 | return path; 584 | } 585 | 586 | private int dfs(TreeNode root){ 587 | if (root == null) return 0; 588 | int left = dfs(root.left); 589 | int right = dfs(root.right); 590 | int leftPath = root.left != null && root.left.val == root.val ? left + 1 : 0; 591 | int rightPath = root.right != null && root.right.val == root.val ? right + 1 : 0; 592 | path = Math.max(path, leftPath + rightPath); 593 | return Math.max(leftPath, rightPath); 594 | } 595 | ``` 596 | 597 | ### 13. 间隔遍历 598 | 599 | 337\. House Robber III (Medium) 600 | 601 | [Leetcode](https://leetcode.com/problems/house-robber-iii/description/) / [力扣](https://leetcode-cn.com/problems/house-robber-iii/description/) 602 | 603 | ```html 604 | 3 605 | / \ 606 | 2 3 607 | \ \ 608 | 3 1 609 | Maximum amount of money the thief can rob = 3 + 3 + 1 = 7. 610 | ``` 611 | 612 | ```java 613 | Map cache = new HashMap<>(); 614 | 615 | public int rob(TreeNode root) { 616 | if (root == null) return 0; 617 | if (cache.containsKey(root)) return cache.get(root); 618 | int val1 = root.val; 619 | if (root.left != null) val1 += rob(root.left.left) + rob(root.left.right); 620 | if (root.right != null) val1 += rob(root.right.left) + rob(root.right.right); 621 | int val2 = rob(root.left) + rob(root.right); 622 | int res = Math.max(val1, val2); 623 | cache.put(root, res); 624 | return res; 625 | } 626 | ``` 627 | 628 | ### 14. 找出二叉树中第二小的节点 629 | 630 | 671\. Second Minimum Node In a Binary Tree (Easy) 631 | 632 | [Leetcode](https://leetcode.com/problems/second-minimum-node-in-a-binary-tree/description/) / [力扣](https://leetcode-cn.com/problems/second-minimum-node-in-a-binary-tree/description/) 633 | 634 | ```html 635 | Input: 636 | 2 637 | / \ 638 | 2 5 639 | / \ 640 | 5 7 641 | 642 | Output: 5 643 | ``` 644 | 645 | 一个节点要么具有 0 个或 2 个子节点,如果有子节点,那么根节点是最小的节点。 646 | 647 | ```java 648 | public int findSecondMinimumValue(TreeNode root) { 649 | if (root == null) return -1; 650 | if (root.left == null && root.right == null) return -1; 651 | int leftVal = root.left.val; 652 | int rightVal = root.right.val; 653 | if (leftVal == root.val) leftVal = findSecondMinimumValue(root.left); 654 | if (rightVal == root.val) rightVal = findSecondMinimumValue(root.right); 655 | if (leftVal != -1 && rightVal != -1) return Math.min(leftVal, rightVal); 656 | if (leftVal != -1) return leftVal; 657 | return rightVal; 658 | } 659 | ``` 660 | 661 | ## 层次遍历 662 | 663 | 使用 BFS 进行层次遍历。不需要使用两个队列来分别存储当前层的节点和下一层的节点,因为在开始遍历一层的节点时,当前队列中的节点数就是当前层的节点数,只要控制遍历这么多节点数,就能保证这次遍历的都是当前层的节点。 664 | 665 | ### 1. 一棵树每层节点的平均数 666 | 667 | 637\. Average of Levels in Binary Tree (Easy) 668 | 669 | [Leetcode](https://leetcode.com/problems/average-of-levels-in-binary-tree/description/) / [力扣](https://leetcode-cn.com/problems/average-of-levels-in-binary-tree/description/) 670 | 671 | ```java 672 | public List averageOfLevels(TreeNode root) { 673 | List ret = new ArrayList<>(); 674 | if (root == null) return ret; 675 | Queue queue = new LinkedList<>(); 676 | queue.add(root); 677 | while (!queue.isEmpty()) { 678 | int cnt = queue.size(); 679 | double sum = 0; 680 | for (int i = 0; i < cnt; i++) { 681 | TreeNode node = queue.poll(); 682 | sum += node.val; 683 | if (node.left != null) queue.add(node.left); 684 | if (node.right != null) queue.add(node.right); 685 | } 686 | ret.add(sum / cnt); 687 | } 688 | return ret; 689 | } 690 | ``` 691 | 692 | ### 2. 得到左下角的节点 693 | 694 | 513\. Find Bottom Left Tree Value (Easy) 695 | 696 | [Leetcode](https://leetcode.com/problems/find-bottom-left-tree-value/description/) / [力扣](https://leetcode-cn.com/problems/find-bottom-left-tree-value/description/) 697 | 698 | ```html 699 | Input: 700 | 701 | 1 702 | / \ 703 | 2 3 704 | / / \ 705 | 4 5 6 706 | / 707 | 7 708 | 709 | Output: 710 | 7 711 | ``` 712 | 713 | ```java 714 | public int findBottomLeftValue(TreeNode root) { 715 | Queue queue = new LinkedList<>(); 716 | queue.add(root); 717 | while (!queue.isEmpty()) { 718 | root = queue.poll(); 719 | if (root.right != null) queue.add(root.right); 720 | if (root.left != null) queue.add(root.left); 721 | } 722 | return root.val; 723 | } 724 | ``` 725 | 726 | ## 前中后序遍历 727 | 728 | ```html 729 | 1 730 | / \ 731 | 2 3 732 | / \ \ 733 | 4 5 6 734 | ``` 735 | 736 | - 层次遍历顺序:[1 2 3 4 5 6] 737 | - 前序遍历顺序:[1 2 4 5 3 6] 738 | - 中序遍历顺序:[4 2 5 1 3 6] 739 | - 后序遍历顺序:[4 5 2 6 3 1] 740 | 741 | 层次遍历使用 BFS 实现,利用的就是 BFS 一层一层遍历的特性;而前序、中序、后序遍历利用了 DFS 实现。 742 | 743 | 前序、中序、后序遍只是在对节点访问的顺序有一点不同,其它都相同。 744 | 745 | ① 前序 746 | 747 | ```java 748 | void dfs(TreeNode root) { 749 | visit(root); 750 | dfs(root.left); 751 | dfs(root.right); 752 | } 753 | ``` 754 | 755 | ② 中序 756 | 757 | ```java 758 | void dfs(TreeNode root) { 759 | dfs(root.left); 760 | visit(root); 761 | dfs(root.right); 762 | } 763 | ``` 764 | 765 | ③ 后序 766 | 767 | ```java 768 | void dfs(TreeNode root) { 769 | dfs(root.left); 770 | dfs(root.right); 771 | visit(root); 772 | } 773 | ``` 774 | 775 | ### 1. 非递归实现二叉树的前序遍历 776 | 777 | 144\. Binary Tree Preorder Traversal (Medium) 778 | 779 | [Leetcode](https://leetcode.com/problems/binary-tree-preorder-traversal/description/) / [力扣](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/description/) 780 | 781 | ```java 782 | public List preorderTraversal(TreeNode root) { 783 | List ret = new ArrayList<>(); 784 | Stack stack = new Stack<>(); 785 | stack.push(root); 786 | while (!stack.isEmpty()) { 787 | TreeNode node = stack.pop(); 788 | if (node == null) continue; 789 | ret.add(node.val); 790 | stack.push(node.right); // 先右后左,保证左子树先遍历 791 | stack.push(node.left); 792 | } 793 | return ret; 794 | } 795 | ``` 796 | 797 | ### 2. 非递归实现二叉树的后序遍历 798 | 799 | 145\. Binary Tree Postorder Traversal (Medium) 800 | 801 | [Leetcode](https://leetcode.com/problems/binary-tree-postorder-traversal/description/) / [力扣](https://leetcode-cn.com/problems/binary-tree-postorder-traversal/description/) 802 | 803 | 前序遍历为 root -\> left -\> right,后序遍历为 left -\> right -\> root。可以修改前序遍历成为 root -\> right -\> left,那么这个顺序就和后序遍历正好相反。 804 | 805 | ```java 806 | public List postorderTraversal(TreeNode root) { 807 | List ret = new ArrayList<>(); 808 | Stack stack = new Stack<>(); 809 | stack.push(root); 810 | while (!stack.isEmpty()) { 811 | TreeNode node = stack.pop(); 812 | if (node == null) continue; 813 | ret.add(node.val); 814 | stack.push(node.left); 815 | stack.push(node.right); 816 | } 817 | Collections.reverse(ret); 818 | return ret; 819 | } 820 | ``` 821 | 822 | ### 3. 非递归实现二叉树的中序遍历 823 | 824 | 94\. Binary Tree Inorder Traversal (Medium) 825 | 826 | [Leetcode](https://leetcode.com/problems/binary-tree-inorder-traversal/description/) / [力扣](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/description/) 827 | 828 | ```java 829 | public List inorderTraversal(TreeNode root) { 830 | List ret = new ArrayList<>(); 831 | if (root == null) return ret; 832 | Stack stack = new Stack<>(); 833 | TreeNode cur = root; 834 | while (cur != null || !stack.isEmpty()) { 835 | while (cur != null) { 836 | stack.push(cur); 837 | cur = cur.left; 838 | } 839 | TreeNode node = stack.pop(); 840 | ret.add(node.val); 841 | cur = node.right; 842 | } 843 | return ret; 844 | } 845 | ``` 846 | 847 | ## BST 848 | 849 | 二叉查找树(BST):根节点大于等于左子树所有节点,小于等于右子树所有节点。 850 | 851 | 二叉查找树中序遍历有序。 852 | 853 | ### 1. 修剪二叉查找树 854 | 855 | 669\. Trim a Binary Search Tree (Easy) 856 | 857 | [Leetcode](https://leetcode.com/problems/trim-a-binary-search-tree/description/) / [力扣](https://leetcode-cn.com/problems/trim-a-binary-search-tree/description/) 858 | 859 | ```html 860 | Input: 861 | 862 | 3 863 | / \ 864 | 0 4 865 | \ 866 | 2 867 | / 868 | 1 869 | 870 | L = 1 871 | R = 3 872 | 873 | Output: 874 | 875 | 3 876 | / 877 | 2 878 | / 879 | 1 880 | ``` 881 | 882 | 题目描述:只保留值在 L \~ R 之间的节点 883 | 884 | ```java 885 | public TreeNode trimBST(TreeNode root, int L, int R) { 886 | if (root == null) return null; 887 | if (root.val > R) return trimBST(root.left, L, R); 888 | if (root.val < L) return trimBST(root.right, L, R); 889 | root.left = trimBST(root.left, L, R); 890 | root.right = trimBST(root.right, L, R); 891 | return root; 892 | } 893 | ``` 894 | 895 | ### 2. 寻找二叉查找树的第 k 个元素 896 | 897 | 230\. Kth Smallest Element in a BST (Medium) 898 | 899 | [Leetcode](https://leetcode.com/problems/kth-smallest-element-in-a-bst/description/) / [力扣](https://leetcode-cn.com/problems/kth-smallest-element-in-a-bst/description/) 900 | 901 | 902 | 中序遍历解法: 903 | 904 | ```java 905 | private int cnt = 0; 906 | private int val; 907 | 908 | public int kthSmallest(TreeNode root, int k) { 909 | inOrder(root, k); 910 | return val; 911 | } 912 | 913 | private void inOrder(TreeNode node, int k) { 914 | if (node == null) return; 915 | inOrder(node.left, k); 916 | cnt++; 917 | if (cnt == k) { 918 | val = node.val; 919 | return; 920 | } 921 | inOrder(node.right, k); 922 | } 923 | ``` 924 | 925 | 递归解法: 926 | 927 | ```java 928 | public int kthSmallest(TreeNode root, int k) { 929 | int leftCnt = count(root.left); 930 | if (leftCnt == k - 1) return root.val; 931 | if (leftCnt > k - 1) return kthSmallest(root.left, k); 932 | return kthSmallest(root.right, k - leftCnt - 1); 933 | } 934 | 935 | private int count(TreeNode node) { 936 | if (node == null) return 0; 937 | return 1 + count(node.left) + count(node.right); 938 | } 939 | ``` 940 | 941 | ### 3. 把二叉查找树每个节点的值都加上比它大的节点的值 942 | 943 | Convert BST to Greater Tree (Easy) 944 | 945 | [Leetcode](https://leetcode.com/problems/convert-bst-to-greater-tree/description/) / [力扣](https://leetcode-cn.com/problems/convert-bst-to-greater-tree/description/) 946 | 947 | ```html 948 | Input: The root of a Binary Search Tree like this: 949 | 950 | 5 951 | / \ 952 | 2 13 953 | 954 | Output: The root of a Greater Tree like this: 955 | 956 | 18 957 | / \ 958 | 20 13 959 | ``` 960 | 961 | 先遍历右子树。 962 | 963 | ```java 964 | private int sum = 0; 965 | 966 | public TreeNode convertBST(TreeNode root) { 967 | traver(root); 968 | return root; 969 | } 970 | 971 | private void traver(TreeNode node) { 972 | if (node == null) return; 973 | traver(node.right); 974 | sum += node.val; 975 | node.val = sum; 976 | traver(node.left); 977 | } 978 | ``` 979 | 980 | ### 4. 二叉查找树的最近公共祖先 981 | 982 | 235\. Lowest Common Ancestor of a Binary Search Tree (Easy) 983 | 984 | [Leetcode](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/description/) / [力扣](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/description/) 985 | 986 | ```html 987 | _______6______ 988 | / \ 989 | ___2__ ___8__ 990 | / \ / \ 991 | 0 4 7 9 992 | / \ 993 | 3 5 994 | 995 | For example, the lowest common ancestor (LCA) of nodes 2 and 8 is 6. Another example is LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition. 996 | ``` 997 | 998 | ```java 999 | public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { 1000 | if (root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q); 1001 | if (root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q); 1002 | return root; 1003 | } 1004 | ``` 1005 | 1006 | ### 5. 二叉树的最近公共祖先 1007 | 1008 | 236\. Lowest Common Ancestor of a Binary Tree (Medium) 1009 | 1010 | [Leetcode](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/description/) / [力扣](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/description/) 1011 | 1012 | ```html 1013 | _______3______ 1014 | / \ 1015 | ___5__ ___1__ 1016 | / \ / \ 1017 | 6 2 0 8 1018 | / \ 1019 | 7 4 1020 | 1021 | For example, the lowest common ancestor (LCA) of nodes 5 and 1 is 3. Another example is LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition. 1022 | ``` 1023 | 1024 | ```java 1025 | public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { 1026 | if (root == null || root == p || root == q) return root; 1027 | TreeNode left = lowestCommonAncestor(root.left, p, q); 1028 | TreeNode right = lowestCommonAncestor(root.right, p, q); 1029 | return left == null ? right : right == null ? left : root; 1030 | } 1031 | ``` 1032 | 1033 | ### 6. 从有序数组中构造二叉查找树 1034 | 1035 | 108\. Convert Sorted Array to Binary Search Tree (Easy) 1036 | 1037 | [Leetcode](https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/description/) / [力扣](https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/description/) 1038 | 1039 | ```java 1040 | public TreeNode sortedArrayToBST(int[] nums) { 1041 | return toBST(nums, 0, nums.length - 1); 1042 | } 1043 | 1044 | private TreeNode toBST(int[] nums, int sIdx, int eIdx){ 1045 | if (sIdx > eIdx) return null; 1046 | int mIdx = (sIdx + eIdx) / 2; 1047 | TreeNode root = new TreeNode(nums[mIdx]); 1048 | root.left = toBST(nums, sIdx, mIdx - 1); 1049 | root.right = toBST(nums, mIdx + 1, eIdx); 1050 | return root; 1051 | } 1052 | ``` 1053 | 1054 | ### 7. 根据有序链表构造平衡的二叉查找树 1055 | 1056 | 109\. Convert Sorted List to Binary Search Tree (Medium) 1057 | 1058 | [Leetcode](https://leetcode.com/problems/convert-sorted-list-to-binary-search-tree/description/) / [力扣](https://leetcode-cn.com/problems/convert-sorted-list-to-binary-search-tree/description/) 1059 | 1060 | ```html 1061 | Given the sorted linked list: [-10,-3,0,5,9], 1062 | 1063 | One possible answer is: [0,-3,9,-10,null,5], which represents the following height balanced BST: 1064 | 1065 | 0 1066 | / \ 1067 | -3 9 1068 | / / 1069 | -10 5 1070 | ``` 1071 | 1072 | ```java 1073 | public TreeNode sortedListToBST(ListNode head) { 1074 | if (head == null) return null; 1075 | if (head.next == null) return new TreeNode(head.val); 1076 | ListNode preMid = preMid(head); 1077 | ListNode mid = preMid.next; 1078 | preMid.next = null; // 断开链表 1079 | TreeNode t = new TreeNode(mid.val); 1080 | t.left = sortedListToBST(head); 1081 | t.right = sortedListToBST(mid.next); 1082 | return t; 1083 | } 1084 | 1085 | private ListNode preMid(ListNode head) { 1086 | ListNode slow = head, fast = head.next; 1087 | ListNode pre = head; 1088 | while (fast != null && fast.next != null) { 1089 | pre = slow; 1090 | slow = slow.next; 1091 | fast = fast.next.next; 1092 | } 1093 | return pre; 1094 | } 1095 | ``` 1096 | 1097 | ### 8. 在二叉查找树中寻找两个节点,使它们的和为一个给定值 1098 | 1099 | 653\. Two Sum IV - Input is a BST (Easy) 1100 | 1101 | [Leetcode](https://leetcode.com/problems/two-sum-iv-input-is-a-bst/description/) / [力扣](https://leetcode-cn.com/problems/two-sum-iv-input-is-a-bst/description/) 1102 | 1103 | ```html 1104 | Input: 1105 | 1106 | 5 1107 | / \ 1108 | 3 6 1109 | / \ \ 1110 | 2 4 7 1111 | 1112 | Target = 9 1113 | 1114 | Output: True 1115 | ``` 1116 | 1117 | 使用中序遍历得到有序数组之后,再利用双指针对数组进行查找。 1118 | 1119 | 应该注意到,这一题不能用分别在左右子树两部分来处理这种思想,因为两个待求的节点可能分别在左右子树中。 1120 | 1121 | ```java 1122 | public boolean findTarget(TreeNode root, int k) { 1123 | List nums = new ArrayList<>(); 1124 | inOrder(root, nums); 1125 | int i = 0, j = nums.size() - 1; 1126 | while (i < j) { 1127 | int sum = nums.get(i) + nums.get(j); 1128 | if (sum == k) return true; 1129 | if (sum < k) i++; 1130 | else j--; 1131 | } 1132 | return false; 1133 | } 1134 | 1135 | private void inOrder(TreeNode root, List nums) { 1136 | if (root == null) return; 1137 | inOrder(root.left, nums); 1138 | nums.add(root.val); 1139 | inOrder(root.right, nums); 1140 | } 1141 | ``` 1142 | 1143 | ### 9. 在二叉查找树中查找两个节点之差的最小绝对值 1144 | 1145 | 530\. Minimum Absolute Difference in BST (Easy) 1146 | 1147 | [Leetcode](https://leetcode.com/problems/minimum-absolute-difference-in-bst/description/) / [力扣](https://leetcode-cn.com/problems/minimum-absolute-difference-in-bst/description/) 1148 | 1149 | ```html 1150 | Input: 1151 | 1152 | 1 1153 | \ 1154 | 3 1155 | / 1156 | 2 1157 | 1158 | Output: 1159 | 1160 | 1 1161 | ``` 1162 | 1163 | 利用二叉查找树的中序遍历为有序的性质,计算中序遍历中临近的两个节点之差的绝对值,取最小值。 1164 | 1165 | ```java 1166 | private int minDiff = Integer.MAX_VALUE; 1167 | private TreeNode preNode = null; 1168 | 1169 | public int getMinimumDifference(TreeNode root) { 1170 | inOrder(root); 1171 | return minDiff; 1172 | } 1173 | 1174 | private void inOrder(TreeNode node) { 1175 | if (node == null) return; 1176 | inOrder(node.left); 1177 | if (preNode != null) minDiff = Math.min(minDiff, node.val - preNode.val); 1178 | preNode = node; 1179 | inOrder(node.right); 1180 | } 1181 | ``` 1182 | 1183 | ### 10. 寻找二叉查找树中出现次数最多的值 1184 | 1185 | 501\. Find Mode in Binary Search Tree (Easy) 1186 | 1187 | [Leetcode](https://leetcode.com/problems/find-mode-in-binary-search-tree/description/) / [力扣](https://leetcode-cn.com/problems/find-mode-in-binary-search-tree/description/) 1188 | 1189 | ```html 1190 | 1 1191 | \ 1192 | 2 1193 | / 1194 | 2 1195 | 1196 | return [2]. 1197 | ``` 1198 | 1199 | 答案可能不止一个,也就是有多个值出现的次数一样多。 1200 | 1201 | ```java 1202 | private int curCnt = 1; 1203 | private int maxCnt = 1; 1204 | private TreeNode preNode = null; 1205 | 1206 | public int[] findMode(TreeNode root) { 1207 | List maxCntNums = new ArrayList<>(); 1208 | inOrder(root, maxCntNums); 1209 | int[] ret = new int[maxCntNums.size()]; 1210 | int idx = 0; 1211 | for (int num : maxCntNums) { 1212 | ret[idx++] = num; 1213 | } 1214 | return ret; 1215 | } 1216 | 1217 | private void inOrder(TreeNode node, List nums) { 1218 | if (node == null) return; 1219 | inOrder(node.left, nums); 1220 | if (preNode != null) { 1221 | if (preNode.val == node.val) curCnt++; 1222 | else curCnt = 1; 1223 | } 1224 | if (curCnt > maxCnt) { 1225 | maxCnt = curCnt; 1226 | nums.clear(); 1227 | nums.add(node.val); 1228 | } else if (curCnt == maxCnt) { 1229 | nums.add(node.val); 1230 | } 1231 | preNode = node; 1232 | inOrder(node.right, nums); 1233 | } 1234 | ``` 1235 | 1236 | ## Trie 1237 | 1238 |

1239 | 1240 | Trie,又称前缀树或字典树,用于判断字符串是否存在或者是否具有某种字符串前缀。 1241 | 1242 | ### 1. 实现一个 Trie 1243 | 1244 | 208\. Implement Trie (Prefix Tree) (Medium) 1245 | 1246 | [Leetcode](https://leetcode.com/problems/implement-trie-prefix-tree/description/) / [力扣](https://leetcode-cn.com/problems/implement-trie-prefix-tree/description/) 1247 | 1248 | ```java 1249 | class Trie { 1250 | 1251 | private class Node { 1252 | Node[] childs = new Node[26]; 1253 | boolean isLeaf; 1254 | } 1255 | 1256 | private Node root = new Node(); 1257 | 1258 | public Trie() { 1259 | } 1260 | 1261 | public void insert(String word) { 1262 | insert(word, root); 1263 | } 1264 | 1265 | private void insert(String word, Node node) { 1266 | if (node == null) return; 1267 | if (word.length() == 0) { 1268 | node.isLeaf = true; 1269 | return; 1270 | } 1271 | int index = indexForChar(word.charAt(0)); 1272 | if (node.childs[index] == null) { 1273 | node.childs[index] = new Node(); 1274 | } 1275 | insert(word.substring(1), node.childs[index]); 1276 | } 1277 | 1278 | public boolean search(String word) { 1279 | return search(word, root); 1280 | } 1281 | 1282 | private boolean search(String word, Node node) { 1283 | if (node == null) return false; 1284 | if (word.length() == 0) return node.isLeaf; 1285 | int index = indexForChar(word.charAt(0)); 1286 | return search(word.substring(1), node.childs[index]); 1287 | } 1288 | 1289 | public boolean startsWith(String prefix) { 1290 | return startWith(prefix, root); 1291 | } 1292 | 1293 | private boolean startWith(String prefix, Node node) { 1294 | if (node == null) return false; 1295 | if (prefix.length() == 0) return true; 1296 | int index = indexForChar(prefix.charAt(0)); 1297 | return startWith(prefix.substring(1), node.childs[index]); 1298 | } 1299 | 1300 | private int indexForChar(char c) { 1301 | return c - 'a'; 1302 | } 1303 | } 1304 | ``` 1305 | 1306 | ### 2. 实现一个 Trie,用来求前缀和 1307 | 1308 | 677\. Map Sum Pairs (Medium) 1309 | 1310 | [Leetcode](https://leetcode.com/problems/map-sum-pairs/description/) / [力扣](https://leetcode-cn.com/problems/map-sum-pairs/description/) 1311 | 1312 | ```html 1313 | Input: insert("apple", 3), Output: Null 1314 | Input: sum("ap"), Output: 3 1315 | Input: insert("app", 2), Output: Null 1316 | Input: sum("ap"), Output: 5 1317 | ``` 1318 | 1319 | ```java 1320 | class MapSum { 1321 | 1322 | private class Node { 1323 | Node[] child = new Node[26]; 1324 | int value; 1325 | } 1326 | 1327 | private Node root = new Node(); 1328 | 1329 | public MapSum() { 1330 | 1331 | } 1332 | 1333 | public void insert(String key, int val) { 1334 | insert(key, root, val); 1335 | } 1336 | 1337 | private void insert(String key, Node node, int val) { 1338 | if (node == null) return; 1339 | if (key.length() == 0) { 1340 | node.value = val; 1341 | return; 1342 | } 1343 | int index = indexForChar(key.charAt(0)); 1344 | if (node.child[index] == null) { 1345 | node.child[index] = new Node(); 1346 | } 1347 | insert(key.substring(1), node.child[index], val); 1348 | } 1349 | 1350 | public int sum(String prefix) { 1351 | return sum(prefix, root); 1352 | } 1353 | 1354 | private int sum(String prefix, Node node) { 1355 | if (node == null) return 0; 1356 | if (prefix.length() != 0) { 1357 | int index = indexForChar(prefix.charAt(0)); 1358 | return sum(prefix.substring(1), node.child[index]); 1359 | } 1360 | int sum = node.value; 1361 | for (Node child : node.child) { 1362 | sum += sum(prefix, child); 1363 | } 1364 | return sum; 1365 | } 1366 | 1367 | private int indexForChar(char c) { 1368 | return c - 'a'; 1369 | } 1370 | } 1371 | ``` 1372 | 1373 | -------------------------------------------------------------------------------- /Leetcode 题解 - 链表.md: -------------------------------------------------------------------------------- 1 | # Leetcode 题解 - 链表 2 | 3 | * [Leetcode 题解 - 链表](#leetcode-题解---链表) 4 | * [1. 相交链表](#1-相交链表) 5 | * [2. 反转链表](#2-反转链表) 6 | * [3. 合并两个有序的链表](#3-合并两个有序的链表) 7 | * [4. 删除排序链表中的重复元素](#4-删除排序链表中的重复元素) 8 | * [5. 删除链表的倒数第 n 个节点](#5-删除链表的倒数第-n-个节点) 9 | * [6. 两两交换链表中的节点](#6-两两交换链表中的节点) 10 | * [7. 两数相加II](#7-两数相加II) 11 | * [8. 回文链表](#8-回文链表) 12 | * [9. 分隔链表](#9-分隔链表) 13 | * [10. 链表元素按奇偶聚集](#10-链表元素按奇偶聚集) 14 | * [11. 分隔链表crn](#11-分隔链表crn) 15 | * [12. 反转链表II](#12-反转链表II) 16 | * [13.环形链表](#13-环形链表) 17 | 18 | 19 | 20 | 链表是空节点,或者有一个值和一个指向下一个链表的指针,因此很多链表问题可以用递归来处理。 21 | 22 | ## 1. 相交链表 23 | 24 | 160\. Intersection of Two Linked Lists (Easy) 25 | 26 | [Leetcode](https://leetcode.com/problems/intersection-of-two-linked-lists/description/) / [力扣](https://leetcode-cn.com/problems/intersection-of-two-linked-lists/description/) 27 | 28 | 例如以下示例中 A 和 B 两个链表相交于 c1: 29 | 30 | ```html 31 | A: a1 → a2 32 | ↘ 33 | c1 → c2 → c3 34 | ↗ 35 | B: b1 → b2 → b3 36 | ``` 37 | 38 | 但是不会出现以下相交的情况,因为每个节点只有一个 next 指针,也就只能有一个后继节点,而以下示例中节点 c 有两个后继节点。 39 | 40 | ```html 41 | A: a1 → a2 d1 → d2 42 | ↘ ↗ 43 | c 44 | ↗ ↘ 45 | B: b1 → b2 → b3 e1 → e2 46 | ``` 47 | 48 | 要求时间复杂度为 O(N),空间复杂度为 O(1)。如果不存在交点则返回 null。 49 | 50 | 设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。 51 | 52 | 当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部开始访问链表 B;同样地,当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。 53 | 54 | 如果不存在交点,那么 a + b = b + a(没有公共部分c),以下实现代码中 a 和 b 会同时为 null,从而退出循环。**判断条件可以是任何表达式,任何非零、或非空(null)的值均为true。** 55 | 56 | ```python 57 | class Solution: 58 | def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: 59 | a, b = headA, headB 60 | while a != b:#如果没有公共部分,a 和 b 会同时(各自走完对方的链表后变)为 null,从而退出循环 61 | a = a.next if a else headB 62 | b = b.next if b else headA 63 | return a 64 | ``` 65 | 等价于 66 | ```python 67 | class Solution: 68 | def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: 69 | a,b = headA,headB 70 | while a != b: 71 | if a : 72 | a = a.next 73 | else: 74 | a = headB 75 | if b : 76 | b = b.next 77 | else: 78 | b = headA 79 | return a 80 | ``` 81 | 82 | 如果只是判断是否存在交点,那么就是另一个问题,即 [编程之美 3.6]() 的问题。有两种解法: 83 | 84 | - 把第一个链表的结尾连接到第二个链表的开头,看第二个链表是否存在环; 85 | - 或者直接比较两个链表的最后一个节点是否相同。 86 | 87 | ## 2. 反转链表 88 | 89 | 206\. Reverse Linked List (Easy) 90 | 91 | [Leetcode](https://leetcode.com/problems/reverse-linked-list/description/) / [力扣](https://leetcode-cn.com/problems/reverse-linked-list/description/) 92 | 93 | 法1:迭代 94 | 95 | ![image](https://user-images.githubusercontent.com/70521393/117388602-a1e53e80-af1d-11eb-9a8f-ed76392f8798.png) 96 | 97 | ```python 98 | class Solution: 99 | def reverseList(self, head: ListNode) -> ListNode: 100 | if head is None or head.next is None: 101 | return head 102 | pre = None 103 | cur = head 104 | while cur: 105 | temp = cur.next# 先把原来cur.next位置存起来 106 | cur.next = pre#注意顺序,每个赋值(右边的)后的变成下一个被赋值的(左边的) 107 | pre = cur 108 | cur = temp 109 | return pre 110 | ``` 111 | 时间复杂度:O(n) 112 | 空间复杂度:O(1) 113 | 114 | 法2:递归 [讲解](https://leetcode-cn.com/problems/reverse-linked-list/solution/shi-pin-jiang-jie-die-dai-he-di-gui-hen-hswxy/) 115 | 116 | ```python3 117 | class Solution: 118 | def reverseList(self, head: ListNode) -> ListNode: 119 | if head is None or head.next is None: 120 | return head 121 | node = self.reverseList(head.next)#递归调用的“递”,调用自己 122 | #下面两步是递归调用的“归” 123 | head.next.next = head#现在head的next的next变成head,反转 124 | head.next = None#变成链表的结尾 125 | return node 126 | 127 | ``` 128 | 时间复杂度:O(n) 129 | 空间复杂度:O(n) 130 | 131 | 法3:队列+头插法 132 | 133 | ### 解题思路 134 | 此处撰写解题思路 135 | 1.先将所有链表的值放入队列中; 136 | 137 | 2.使用头插法,将每次出队的值放在dummy后面,(注意头插法,后插入的在链表左边/前面) 138 | 139 | ```python3 140 | # Definition for singly-linked list. 141 | # class ListNode: 142 | # def __init__(self, val=0, next=None): 143 | # self.val = val 144 | # self.next = next 145 | class Solution: 146 | def reverseList(self, head: ListNode) -> ListNode: 147 | #1.先将所有链表的值放入队列中; 148 | queue = collections.deque() 149 | while head: 150 | queue.append(head.val) 151 | head = head.next 152 | 153 | #2.使用头插法,将每次出队的值放在dummy后面,(注意头插法,后插入的在链表左边/前面) 154 | dummy = ListNode(-1) 155 | while queue: 156 | cur = ListNode(queue.popleft()) 157 | cur.next = dummy.next 158 | dummy.next = cur 159 | 160 | return dummy.next 161 | ``` 162 | 163 | ## 3. 合并两个有序的链表 164 | 165 | 21\. Merge Two Sorted Lists (Easy) 166 | 167 | [Leetcode](https://leetcode.com/problems/merge-two-sorted-lists/description/) / [力扣](https://leetcode-cn.com/problems/merge-two-sorted-lists/description/) 168 | 169 | 递归: 170 | 171 | ```python 172 | # Definition for singly-linked list. 173 | # class ListNode: 174 | # def __init__(self, val=0, next=None): 175 | # self.val = val 176 | # self.next = next 177 | class Solution: 178 | def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: 179 | if l1 is None: 180 | return l2 181 | elif l2 is None: 182 | return l1 183 | elif l1.val < l2.val: 184 | l1.next = self.mergeTwoLists(l1.next, l2) 185 | return l1 186 | else: 187 | l2.next = self.mergeTwoLists(l1, l2.next) 188 | return l2 189 | ``` 190 | 时间复杂度:O(n + m),其中 n 和 m 分别为两个链表的长度。因为每次调用递归都会去掉 l1 或者 l2 的头节点(直到至少有一个链表为空),函数 mergeTwoList 至多只会递归调用每个节点一次。因此,时间复杂度取决于合并后的链表长度,即 O(n+m)。 191 | 192 | 空间复杂度:O(n + m),其中 n 和 m 分别为两个链表的长度。递归调用 mergeTwoLists 函数时需要消耗栈空间,栈空间的大小取决于递归调用的深度。结束递归调用时 mergeTwoLists 函数最多调用 n+m 次,因此空间复杂度为 O(n+m)。 193 | 194 | 迭代: 195 | 196 | ```python 197 | # Definition for singly-linked list. 198 | # class ListNode: 199 | # def __init__(self, val=0, next=None): 200 | # self.val = val 201 | # self.next = next 202 | class Solution: 203 | def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: 204 | prehead = ListNode(-1)#随便赋一个值给ListNode类作为起点。 205 | prev = prehead 206 | while l1 and l2: 207 | if l1.val <= l2.val: 208 | prev.next = l1 209 | l1 = l1.next 210 | else: 211 | prev.next = l2 212 | l2 = l2.next 213 | prev = prev.next 214 | 215 | # 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可 216 | prev.next = l1 if l1 is not None else l2 217 | 218 | return prehead.next#从头开始,即以随便赋值的起点的下一位开始,此位才是真正的起点。 219 | ``` 220 | 时间复杂度:O(n + m),其中 n 和 m 分别为两个链表的长度。因为每次循环迭代中,l1 和 l2 只有一个元素会被放进合并链表中, 因此 while 循环的次数不会超过两个链表的长度之和。所有其他操作的时间复杂度都是常数级别的,因此总的时间复杂度为 O(n+m)。 221 | 222 | 空间复杂度:O(1)。我们只需要常数的空间存放若干变量。 223 | 224 | ## 4. 删除排序链表中的重复元素 225 | 226 | 83\. Remove Duplicates from Sorted List (Easy) 227 | 228 | [Leetcode](https://leetcode.com/problems/remove-duplicates-from-sorted-list/description/) / [力扣](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/description/) 229 | 230 | ```html 231 | Given 1->1->2, return 1->2. 232 | Given 1->1->2->3->3, return 1->2->3. 233 | ``` 234 | 235 | ```python 236 | class Solution: 237 | def deleteDuplicates(self, head: ListNode) -> ListNode: 238 | if not head: 239 | return head 240 | 241 | cur = head 242 | while cur.next: 243 | if cur.val == cur.next.val:#注意是值相等 244 | cur.next = cur.next.next 245 | else: 246 | cur = cur.next 247 | 248 | return head 249 | ``` 250 | 时间复杂度:O(n),其中 n 是链表的长度。 251 | 空间复杂度:O(1)。 252 | 253 | ## 5. 删除链表的倒数第 n 个节点 254 | 255 | 19\. Remove Nth Node From End of List (Medium) 256 | 257 | [Leetcode](https://leetcode.com/problems/remove-nth-node-from-end-of-list/description/) / [力扣](https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/description/) 258 | 259 | ```html 260 | Given linked list: 1->2->3->4->5, and n = 2. 261 | After removing the second node from the end, the linked list becomes 1->2->3->5. 262 | ``` 263 | 264 | (暴力法)求链表长度: 265 | 266 | ```python3 267 | class Solution: 268 | def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: 269 | def getLength(head: ListNode) -> int:#获取链表的长度 270 | length = 0 271 | while head: 272 | length += 1 273 | head = head.next 274 | return length 275 | 276 | dummy = ListNode(0, head)#哑节点(随便赋什么值,但要下一个结点需要是head 277 | length = getLength(head)#是原链表的长度 278 | cur = dummy 279 | for i in range(length - n): 280 | cur = cur.next 281 | cur.next = cur.next.next 282 | return dummy.next 283 | ``` 284 | 285 | 快慢指针: 286 | 287 | ```PYTHON3 288 | class Solution: 289 | def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: 290 | dummy = ListNode(0, head) 291 | first = head 292 | second = dummy 293 | for i in range(n): 294 | first = first.next 295 | 296 | while first: 297 | first = first.next 298 | second = second.next 299 | 300 | second.next = second.next.next 301 | return dummy.next 302 | 303 | ``` 304 | 305 | ## 6. 两两交换链表中的节点 306 | 307 | 24\. Swap Nodes in Pairs (Medium) 308 | 309 | [Leetcode](https://leetcode.com/problems/swap-nodes-in-pairs/description/) / [力扣](https://leetcode-cn.com/problems/swap-nodes-in-pairs/description/) 310 | 311 | ```html 312 | Given 1->2->3->4, you should return the list as 2->1->4->3. 313 | ``` 314 | 315 | 题目要求:不能修改结点的 val 值,O(1) 空间复杂度。->所以用迭代,不用递归 316 | 317 | ```python3 318 | # Definition for singly-linked list. 319 | # class ListNode: 320 | # def __init__(self, val=0, next=None): 321 | # self.val = val 322 | # self.next = next 323 | class Solution: 324 | def swapPairs(self, head: ListNode) -> ListNode: 325 | dummy = ListNode(0,head) 326 | temp = dummy 327 | while temp.next and temp.next.next:#temp 的后面没有节点**或者**只有一个节点(只要有一个发生,就停止),则没有更多的节点需要交换,因此结束交换 328 | node1 = temp.next 329 | node2 = node1.next 330 | temp.next = node2 331 | node1.next = node2.next 332 | node2.next = node1 333 | temp = node1#从往后两个(node1换成temp.next.next也行)开始,因为是两两交换 334 | return dummy.next 335 | ``` 336 | 337 | ## 7. 两数相加II 338 | 339 | 445\. Add Two Numbers II (Medium) 340 | 341 | [Leetcode](https://leetcode.com/problems/add-two-numbers-ii/description/) / [力扣](https://leetcode-cn.com/problems/add-two-numbers-ii/description/) 342 | 343 | ```html 344 | Input: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4) 345 | Output: 7 -> 8 -> 0 -> 7 346 | ``` 347 | 348 | 题目要求:不能修改原始链表。 349 | 350 | ### 解题思路 351 | 链表中数位的顺序与我们做加法的顺序是相反的,为了逆序处理所有数位,我们可以使用栈:把所有数字压入栈中,再依次取出相加。计算过程中需要注意进位的情况。 352 | 353 | ### 头插法: 354 | 355 | 原状态如下: 356 | dummy -> dummy.next 357 | new_node -> new_node.next 358 | 359 | 1:将cur当前值赋值给new_node节点 360 | new_node = ListNode(cur) 361 | 362 | 2:将new_node的下一个节点变为dummy.next 363 | new_node.next = dummy.next 364 | 状态更新: 365 | dummy -> dummy.next 366 | **new_node** -> dummy.next 367 | 368 | 3:将dummy的下一个节点变为new_node 369 | dummy.next = new_node 370 | 状态更新: 371 | **dummy** -> new_node -> dummy.next 372 | 373 | 这样new_node就插入到头(dummy)的后面啦 374 | 375 | ### 代码 376 | 377 | ```python3 378 | # Definition for singly-linked list. 379 | # class ListNode: 380 | # def __init__(self, val=0, next=None): 381 | # self.val = val 382 | # self.next = next 383 | class Solution: 384 | def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode: 385 | stack1,stack2 =[],[] 386 | dummy = ListNode(-1) 387 | 388 | while l1: 389 | stack1.append(l1.val) 390 | l1 = l1.next 391 | while l2: 392 | stack2.append(l2.val) 393 | l2 = l2.next 394 | 395 | carry = 0#保存进位的值 396 | while stack1 or stack2 or carry!=0: 397 | tmp1,tmp2 = 0 , 0 398 | if stack1: 399 | tmp1 = stack1.pop() 400 | if stack2: 401 | tmp2 = stack2.pop() 402 | cur = tmp1 + tmp2 + carry 403 | carry = cur // 10#进到下一位的值 404 | cur %= 10#当前值为除10的余数 405 | #下面三步是头插法,保证头节点不变,在头节点后不断插入 406 | new_node = ListNode(cur)#将当前值作为新节点的值 407 | new_node.next = dummy.next 408 | dummy.next = new_node 409 | 410 | return dummy.next 411 | 412 | 413 | ``` 414 | 415 | ## 8. 回文链表 416 | 417 | 234\. Palindrome Linked List (Easy) 418 | 419 | [Leetcode](https://leetcode.com/problems/palindrome-linked-list/description/) / [力扣](https://leetcode-cn.com/problems/palindrome-linked-list/description/) 420 | 421 | 方法1:python3有简单的解法,即利用列表的反转([::-1])来判断 422 | 423 | ```python3 424 | # Definition for singly-linked list. 425 | # class ListNode: 426 | # def __init__(self, val=0, next=None): 427 | # self.val = val 428 | # self.next = next 429 | class Solution: 430 | def isPalindrome(self, head: ListNode) -> bool: 431 | val = [] 432 | while head: 433 | val.append(head.val) 434 | head = head.next 435 | return val == val[::-1] 436 | ``` 437 | 438 | 方法2:递归(效果差) 439 | 440 | **面向对象中的self** 441 | 442 | self代表类的实例,而非类 443 | 类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self。 444 | 445 | ```python 446 | class Test: 447 | def prt(self): 448 | print(self) 449 | print(self.__class__) 450 | 451 | t = Test() 452 | t.prt() 453 | ``` 454 | 以上实例执行结果为: 455 | <__main__.Test instance at 0x100771878> 456 | __main__.Test 457 | 从执行结果可以很明显的看出,self 代表的是类的实例,代表当前对象的地址,而 self.class 则指向类。 458 | 459 | self 不是 python 关键字,我们把他换成 runoob 也是可以正常执行的: 460 | ```python 461 | class Test: 462 | def prt(runoob): 463 | print(runoob) 464 | print(runoob.__class__) 465 | 466 | t = Test() 467 | t.prt() 468 | ``` 469 | 470 | **解题的code:** 471 | ```python3 472 | class Solution: 473 | def isPalindrome(self, head: ListNode) -> bool: 474 | 475 | self.front_pointer = head 476 | 477 | def recursively_check(current_node=head): 478 | if current_node is not None: 479 | if not recursively_check(current_node.next): 480 | return False 481 | if self.front_pointer.val != current_node.val: 482 | return False 483 | self.front_pointer = self.front_pointer.next 484 | return True 485 | 486 | return recursively_check() 487 | ``` 488 | 489 | 方法3:快慢指针 490 | 整个流程可以分为以下五个步骤: 491 | 492 | 1.找到前半部分链表的尾节点。 493 | 2.反转后半部分链表。 494 | 3.判断是否回文。 495 | 4.恢复链表。 496 | 5.返回结果。 497 | 执行步骤一,我们可以计算链表节点的数量,然后遍历链表找到前半部分的尾节点。 498 | 499 | 我们也可以使用快慢指针在一次遍历中找到:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针恰好到链表的中间。通过慢指针将链表分为两部分。 500 | 501 | 若链表有奇数个节点,则中间的节点应该看作是前半部分。 502 | 503 | 步骤二可以使用「206. 反转链表」问题中的解决方法来反转链表的后半部分。 504 | 505 | 步骤三比较两个部分的值,当后半部分到达末尾则比较完成,可以忽略计数情况中的中间节点。 506 | 507 | 步骤四与步骤二使用的函数相同,再反转一次恢复链表本身。 508 | 509 | ```python3 510 | class Solution: 511 | 512 | def isPalindrome(self, head: ListNode) -> bool: 513 | if head is None: 514 | return True 515 | 516 | # 找到前半部分链表的尾节点并反转后半部分链表 517 | first_half_end = self.end_of_first_half(head) 518 | second_half_start = self.reverse_list(first_half_end.next) 519 | 520 | # 判断是否回文 521 | result = True 522 | first_position = head 523 | second_position = second_half_start 524 | while result and second_position is not None: 525 | if first_position.val != second_position.val: 526 | result = False 527 | first_position = first_position.next 528 | second_position = second_position.next 529 | 530 | # 还原链表并返回结果 531 | first_half_end.next = self.reverse_list(second_half_start) 532 | return result 533 | 534 | def end_of_first_half(self, head): 535 | fast = head 536 | slow = head 537 | while fast.next is not None and fast.next.next is not None: 538 | fast = fast.next.next 539 | slow = slow.next 540 | return slow 541 | 542 | def reverse_list(self, head): 543 | previous = None 544 | current = head 545 | while current is not None: 546 | next_node = current.next 547 | current.next = previous 548 | previous = current 549 | current = next_node 550 | return previous 551 | ``` 552 | 复杂度分析 553 | 554 | 时间复杂度:O(n),其中 n 指的是链表的大小。 555 | 556 | 空间复杂度:O(1)。我们只会修改原本链表中节点的指向,而在堆栈上的堆栈帧不超过 O(1)。 557 | 558 | ## 9. 分隔链表 559 | 560 | 725\. Split Linked List in Parts(Medium) 561 | 562 | [Leetcode](https://leetcode.com/problems/split-linked-list-in-parts/description/) / [力扣](https://leetcode-cn.com/problems/split-linked-list-in-parts/description/) 563 | 564 | ```html 565 | Input: 566 | root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3 567 | Output: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]] 568 | Explanation: 569 | The input has been split into consecutive parts with size difference at most 1, and earlier parts are a larger size than the later parts. 570 | ``` 571 | 572 | 题目描述:把链表分隔成 k 部分,每部分的长度都应该尽可能相同,排在前面的长度应该大于等于后面的。 573 | 574 | 解题思路: 575 | 如果链表有 N 个结点,则分隔的链表中每个部分中都有 N/k 个结点,且前 N%k 部分有一个额外的结点。我们可以用一个简单的循环来计算 N。 576 | 577 | 现在对于每个部分,我们已经计算出该部分有多少个节点:width + (i < remainder ? 1 : 0)。我们创建一个新列表并将该部分写入该列表。 578 | 579 | divmod(a, b) 580 | 参数说明: 581 | a,b: 数字,非复数。 582 | 583 | 如果参数 a 与 参数 b 都是整数,函数返回的结果相当于 (a // b, a % b)。 584 | 585 | 如果其中一个参数为浮点数时,函数返回的结果相当于 (q, a % b),q 通常是 math.floor(a / b),但也有可能是 1 ,比小,不过 q * b + a % b 的值会非常接近 a。 586 | 587 | 如果 a % b 的求余结果不为 0 ,则余数的正负符号跟参数 b 是一样的,若 b 是正数,余数为正数,若 b 为负数,余数也为负数,并且 0 <= abs(a % b) < abs(b)。 588 | 589 | ```python3 590 | class Solution(object): 591 | def splitListToParts(self, root, k): 592 | cur = root 593 | for N in range(1001): 594 | if not cur:break#在cur为None的时候跳出循环,即可得到N(链表长度) 595 | cur = cur.next 596 | width, remainder = divmod(N, k)#分隔的链表中每个部分中都有 N//k 个结点(width),且前 N%k 部分有一个额外的结点(remainder) 597 | 598 | ans = [] 599 | cur = root 600 | for i in range(k): 601 | dummy = ListNode(0) 602 | sub = dummy 603 | for j in range(width + (i < remainder)):#注意后面是i2->3->4->5->NULL, 624 | return 1->3->5->2->4->NULL. 625 | ``` 626 | 627 | ```python3 628 | # Definition for singly-linked list. 629 | # class ListNode: 630 | # def __init__(self, x): 631 | # self.val = x 632 | # self.next = None 633 | class Solution: 634 | def oddEvenList(self, head: ListNode) -> ListNode: 635 | if not head:return head 636 | odd = head 637 | even_head = even = head.next#偶节点头单独拿出来,因为要和奇节点链表的末端相连 638 | while odd.next and even.next:#??? 639 | odd.next = odd.next.next 640 | even.next = even.next.next 641 | odd,even = odd.next,even.next 642 | odd.next = even_head 643 | return head 644 | 645 | ``` 646 | 647 | ## 11. 分隔链表crn 648 | 649 | 86\. Partition List(Medium) 650 | 651 | [力扣](https://leetcode-cn.com/problems/partition-list/) 652 | 653 | ![image](https://user-images.githubusercontent.com/70521393/117416984-7e85b800-af4c-11eb-8ae8-f9698e64a0cf.png) 654 | ```html 655 | Input: 656 | 输入:head = [1,4,3,2,5,2], x = 3 657 | 输出:[1,2,2,4,3,5] 658 | ``` 659 | 660 | 题目描述:给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 661 | 662 | 你应当 保留 两个分区中每个节点的初始相对位置。 663 | 664 | ```python3 665 | # Definition for singly-linked list. 666 | # class ListNode: 667 | # def __init__(self, x): 668 | # self.val = x 669 | # self.next = None 670 | 671 | class Solution: 672 | def partition(self, head: ListNode, x: int) -> ListNode: 673 | p=less=ListNode(0) 674 | q=more=ListNode(0) 675 | 676 | while head: 677 | if head.val ListNode: 717 | # 设置 dummyNode 是这一类问题的一般做法 718 | dummy_node = ListNode(-1) 719 | dummy_node.next = head 720 | pre = dummy_node 721 | for _ in range(left - 1): 722 | pre = pre.next 723 | 724 | cur = pre.next 725 | for _ in range(right - left): 726 | next = cur.next#一定要注意上式的右边就是下式的左边,最后的右边为最早是左边,这样就连着了 727 | cur.next = next.next 728 | next.next = pre.next 729 | pre.next = next 730 | return dummy_node.next 731 | ``` 732 | 733 | ## 13. 环形链表 734 | 141\. Linked List Cycle 735 | 736 | [力扣](https://leetcode-cn.com/problems/linked-list-cycle/) 737 | 738 | 739 | ```python3 740 | # Definition for singly-linked list. 741 | # class ListNode: 742 | # def __init__(self, x): 743 | # self.val = x 744 | # self.next = None 745 | 746 | class Solution: 747 | def hasCycle(self, head: ListNode) -> bool: 748 | slow = fast = head 749 | while fast and fast.next:# 防止head为空和出现空指针的next的情况 750 | slow = slow.next 751 | fast = fast.next.next 752 | if slow is fast: 753 | return True 754 | 755 | return False 756 | ``` 757 | 758 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Leetcode101-python3 2 | 3 | ## 前言 4 | 5 | 本文从 Leetcode 中精选大概 200 左右的题目,去除了某些繁杂但是没有多少算法思想的题目,同时保留了面试中经常被问到的经典题目。 6 | 7 | ## 算法思想 8 | 9 | - [双指针](Leetcode%20题解%20-%20双指针.md) 10 | - [排序](Leetcode%20题解%20-%20排序.md) 11 | - [贪心思想](Leetcode%20题解%20-%20贪心思想.md) 12 | - [二分查找](Leetcode%20题解%20-%20二分查找.md) 13 | - [分治](Leetcode%20题解%20-%20分治.md) 14 | - [搜索](Leetcode%20题解%20-%20搜索.md) 15 | - [动态规划](Leetcode%20题解%20-%20动态规划.md) 16 | - [数学](Leetcode%20题解%20-%20数学.md) 17 | 18 | ## 数据结构相关 19 | 20 | - [链表](Leetcode%20题解%20-%20链表.md) 21 | - [树&递归](Leetcode%20题解%20-%20树&递归.md) 22 | - [栈和队列](Leetcode%20题解%20-%20栈和队列.md) 23 | - [哈希表](Leetcode%20题解%20-%20哈希表.md) 24 | - [字符串](Leetcode%20题解%20-%20字符串.md) 25 | - [数组与矩阵](Leetcode%20题解%20-%20数组与矩阵.md) 26 | - [图](Leetcode%20题解%20-%20图.md) 27 | - [位运算](Leetcode%20题解%20-%20位运算.md) 28 | 29 | 参考:https://github.com/CyC2018/CS-Notes 30 | -------------------------------------------------------------------------------- /数据结构与算法python3总目录.md: -------------------------------------------------------------------------------- 1 | # Leetcode-python3 2 | 3 | ## 前言 4 | 5 | 本文从 Leetcode 中精选大概 200 左右的题目,去除了某些繁杂但是没有多少算法思想的题目,同时保留了面试中经常被问到的经典题目。 6 | 7 | ## 算法思想 8 | 9 | - [双指针](Leetcode%20题解%20-%20双指针.md) 10 | - [排序](Leetcode%20题解%20-%20排序.md) 11 | - [贪心思想](Leetcode%20题解%20-%20贪心思想.md) 12 | - [二分查找](Leetcode%20题解%20-%20二分查找.md) 13 | - [分治](Leetcode%20题解%20-%20分治.md) 14 | - [搜索](Leetcode%20题解%20-%20搜索.md) 15 | - [动态规划](Leetcode%20题解%20-%20动态规划.md) 16 | - [数学](Leetcode%20题解%20-%20数学.md) 17 | 18 | ## 数据结构相关 19 | 20 | - [链表](Leetcode%20题解%20-%20链表.md) 21 | - [树](Leetcode%20题解%20-%20树.md) 22 | - [栈和队列](Leetcode%20题解%20-%20栈和队列.md) 23 | - [哈希表](Leetcode%20题解%20-%20哈希表.md) 24 | - [字符串](Leetcode%20题解%20-%20字符串.md) 25 | - [数组与矩阵](Leetcode%20题解%20-%20数组与矩阵.md) 26 | - [图](Leetcode%20题解%20-%20图.md) 27 | - [位运算](Leetcode%20题解%20-%20位运算.md) 28 | --------------------------------------------------------------------------------