└── README.md /README.md: -------------------------------------------------------------------------------- 1 | [TOC] 2 | 3 | # 简述 4 | 5 | 极客时间算法40讲中所出现的leetcode算法题 6 | 7 | # 题目 8 | 9 | #### [【链表】reverse-linked-list(反转一个单链表)](https://leetcode-cn.com/problems/reverse-linked-list/) 10 | 11 | ``` 12 | 示例: 13 | 输入: 1->2->3->4->5->NULL 14 | 输出: 5->4->3->2->1->NULL 15 | ``` 16 | 17 | **代码** 18 | 19 | `递归` 20 | 21 | ```java 22 | /** 23 | * Definition for singly-linked list. 24 | * public class ListNode { 25 | * int val; 26 | * ListNode next; 27 | * ListNode(int x) { val = x; } 28 | * } 29 | */ 30 | class Solution { 31 | public ListNode reverseList(ListNode head) { 32 | if(head == null || head.next == null) return head; 33 | ListNode node = reverseList(head.next); 34 | head.next.next = head; 35 | head.next = null; 36 | return node; 37 | } 38 | } 39 | ``` 40 | 41 | `迭代` 42 | 43 | ```java 44 | /** 45 | * Definition for singly-linked list. 46 | * public class ListNode { 47 | * int val; 48 | * ListNode next; 49 | * ListNode(int x) { val = x; } 50 | * } 51 | */ 52 | class Solution { 53 | public ListNode reverseList(ListNode head) { 54 | ListNode prev = null; 55 | while(head != null){ 56 | ListNode tmp = head.next; 57 | head.next = prev; 58 | prev = head; 59 | head = tmp; 60 | } 61 | return prev; 62 | } 63 | } 64 | ``` 65 | 66 | ------ 67 | 68 | #### [【链表】swap-nodes-in-pairs(两两交换链表中的节点)](https://leetcode-cn.com/problems/swap-nodes-in-pairs/) 69 | 70 | 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 71 | 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 72 | 73 | ``` 74 | 示例: 75 | 给定 1->2->3->4, 你应该返回 2->1->4->3. 76 | ``` 77 | 78 | **代码** 79 | 80 | ```java 81 | /** 82 | * Definition for singly-linked list. 83 | * public class ListNode { 84 | * int val; 85 | * ListNode next; 86 | * ListNode(int x) { val = x; } 87 | * } 88 | */ 89 | class Solution { 90 | public ListNode swapPairs(ListNode head) { 91 | if(head == null || head.next == null) return head; 92 | ListNode res = head.next; 93 | ListNode pre = new ListNode(-1); 94 | pre.next = head; 95 | while(pre.next != null && pre.next.next != null){ 96 | ListNode a = pre.next; 97 | ListNode b = a.next; 98 | pre.next = b; 99 | a.next = b.next; 100 | b.next = a; 101 | pre = a; 102 | } 103 | return res; 104 | } 105 | } 106 | ``` 107 | 108 | ------ 109 | 110 | #### [【链表】linked-list-cycle(环形链表)](https://leetcode-cn.com/problems/linked-list-cycle/) 111 | 112 | 给定一个链表,判断链表中是否有环。 113 | 114 | 为了表示给定链表中的环,我们使用整数 `pos` 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 `pos` 是 `-1`,则在该链表中没有环。 115 | 116 | **示例 1:** 117 | 118 | ``` 119 | 输入:head = [3,2,0,-4], pos = 1 120 | 输出:true 121 | 解释:链表中有一个环,其尾部连接到第二个节点。 122 | ``` 123 | 124 | ![](http://upload-images.jianshu.io/upload_images/13065313-34213dc5d38dcd68.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 125 | 126 | **示例 2:** 127 | 128 | ``` 129 | 输入:head = [1,2], pos = 0 130 | 输出:true 131 | 解释:链表中有一个环,其尾部连接到第一个节点。 132 | ``` 133 | 134 | ![](http://upload-images.jianshu.io/upload_images/13065313-fe9726415b946d03.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 135 | 136 | **示例 3:** 137 | 138 | ``` 139 | 输入:head = [1], pos = -1 140 | 输出:false 141 | 解释:链表中没有环。 142 | ``` 143 | 144 | ![](http://upload-images.jianshu.io/upload_images/13065313-c6d3fb051f518174.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 145 | 146 | **代码** 147 | 148 | `双指针` 149 | 150 | ```java 151 | /** 152 | * Definition for singly-linked list. 153 | * class ListNode { 154 | * int val; 155 | * ListNode next; 156 | * ListNode(int x) { 157 | * val = x; 158 | * next = null; 159 | * } 160 | * } 161 | */ 162 | public class Solution { 163 | public boolean hasCycle(ListNode head) { 164 | if(head == null || head.next == null || head.next.next == null) return false; 165 | ListNode slow = head; 166 | ListNode fast = head; 167 | while(fast != null && fast.next != null){ 168 | fast = fast.next.next; 169 | slow = slow.next; 170 | if(fast == slow){ 171 | return true; 172 | } 173 | } 174 | return false; 175 | } 176 | } 177 | 178 | ``` 179 | 180 | `set检测重复` 181 | 182 | ```java 183 | /** 184 | * Definition for singly-linked list. 185 | * class ListNode { 186 | * int val; 187 | * ListNode next; 188 | * ListNode(int x) { 189 | * val = x; 190 | * next = null; 191 | * } 192 | * } 193 | */ 194 | public class Solution { 195 | public boolean hasCycle(ListNode head) { 196 | Set set = new HashSet<>(); 197 | while(head != null){ 198 | if(set.contains(head)) return true; 199 | set.add(head); 200 | head = head.next; 201 | } 202 | return false; 203 | } 204 | } 205 | 206 | ``` 207 | 208 | ------ 209 | 210 | #### [【链表】linked-list-cycle-ii(环形链表 II)](https://leetcode-cn.com/problems/linked-list-cycle-ii/) 211 | 212 | 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 `null`。 213 | 214 | 为了表示给定链表中的环,我们使用整数 `pos` 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 `pos` 是 `-1`,则在该链表中没有环。 215 | 216 | **说明:**不允许修改给定的链表。 217 | 218 | **示例 1:** 219 | 220 | ``` 221 | 输入:head = [3,2,0,-4], pos = 1 222 | 输出:tail connects to node index 1 223 | 解释:链表中有一个环,其尾部连接到第二个节点。 224 | 225 | ``` 226 | 227 | ![](http://upload-images.jianshu.io/upload_images/13065313-95a7d4681aecab46.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 228 | 229 | **示例 2:** 230 | 231 | ``` 232 | 输入:head = [1,2], pos = 0 233 | 输出:tail connects to node index 0 234 | 解释:链表中有一个环,其尾部连接到第一个节点。 235 | 236 | ``` 237 | 238 | ![](http://upload-images.jianshu.io/upload_images/13065313-0cfe0c4b0edbe375.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 239 | 240 | **示例 3:** 241 | 242 | ```输入:head = [1], pos = -1 243 | 输出:no cycle 244 | 解释:链表中没有环。 245 | 246 | ``` 247 | 248 | ![](http://upload-images.jianshu.io/upload_images/13065313-018f26f441a37030.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 249 | 250 | **代码** 251 | 252 | `双指针` 253 | 254 | ```java 255 | /** 256 | * Definition for singly-linked list. 257 | * class ListNode { 258 | * int val; 259 | * ListNode next; 260 | * ListNode(int x) { 261 | * val = x; 262 | * next = null; 263 | * } 264 | * } 265 | */ 266 | public class Solution { 267 | public ListNode detectCycle(ListNode head) { 268 | boolean isCycle = false; 269 | ListNode a = head;//慢指针 270 | ListNode b = head;//快指针 271 | while(b != null && b.next != null){ 272 | a = a.next; 273 | b = b.next.next; 274 | if(a == b){ 275 | isCycle = true; 276 | break; 277 | } 278 | } 279 | if(isCycle){ 280 | ListNode c = head; 281 | while(c != a){ 282 | c = c.next; 283 | a = a.next; 284 | } 285 | return a; 286 | }else return null; 287 | } 288 | } 289 | 290 | ``` 291 | 292 | `set判断重复` 293 | 294 | ```java 295 | /** 296 | * Definition for singly-linked list. 297 | * class ListNode { 298 | * int val; 299 | * ListNode next; 300 | * ListNode(int x) { 301 | * val = x; 302 | * next = null; 303 | * } 304 | * } 305 | */ 306 | public class Solution { 307 | public ListNode detectCycle(ListNode head) { 308 | Set set = new HashSet<>(); 309 | while(head != null){ 310 | if(set.contains(head)) return head; 311 | set.add(head); 312 | head = head.next; 313 | } 314 | return null; 315 | } 316 | } 317 | 318 | ``` 319 | 320 | ------ 321 | 322 | #### [【链表】reverse-nodes-in-k-group(k个一组翻转链表)](https://leetcode-cn.com/problems/reverse-nodes-in-k-group/) 323 | 324 | 给出一个链表,每 k 个节点一组进行翻转,并返回翻转后的链表。 325 | k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么将最后剩余节点保持原有顺序。 326 | 327 | ``` 328 | 示例 : 329 | 给定这个链表:1->2->3->4->5 330 | 当 k = 2 时,应当返回: 2->1->4->3->5 331 | 当 k = 3 时,应当返回: 3->2->1->4->5 332 | 333 | ``` 334 | 335 | **代码** 336 | 337 | ```java 338 | /** 339 | * Definition for singly-linked list. 340 | * public class ListNode { 341 | * int val; 342 | * ListNode next; 343 | * ListNode(int x) { val = x; } 344 | * } 345 | */ 346 | class Solution { 347 | public ListNode reverseKGroup(ListNode head, int k) { 348 | ListNode tmp = new ListNode(-1); 349 | ListNode pre = tmp; 350 | ListNode cur = pre; 351 | tmp.next = head; 352 | int num = 0; 353 | while(cur != null){ 354 | cur = cur.next; 355 | num ++; 356 | } 357 | while(num > k){ 358 | cur = pre.next; 359 | for(int i = 1; i < k; i ++){ 360 | ListNode t = cur.next; 361 | cur.next = t.next; 362 | t.next = pre.next; 363 | pre.next = t; 364 | } 365 | pre = cur; 366 | num -= k; 367 | } 368 | return tmp.next; 369 | 370 | } 371 | } 372 | 373 | ``` 374 | 375 | ------ 376 | 377 | #### [【栈】valid-parentheses(有效的括号)](https://leetcode-cn.com/problems/valid-parentheses/) 378 | 379 | 给定一个只包括 `'('`,`')'`,`'{'`,`'}'`,`'['`,`']'` 的字符串,判断字符串是否有效。 380 | 381 | 有效字符串需满足: 382 | 383 | 1. 左括号必须用相同类型的右括号闭合。 384 | 2. 左括号必须以正确的顺序闭合。 385 | 386 | 注意空字符串可被认为是有效字符串。 387 | 388 | **示例 1:** 389 | 390 | ``` 391 | 输入: "()" 392 | 输出: true 393 | 394 | ``` 395 | 396 | **示例 2:** 397 | 398 | ``` 399 | 输入: "()[]{}" 400 | 输出: true 401 | 402 | ``` 403 | 404 | **示例 3:** 405 | 406 | ``` 407 | 输入: "(]" 408 | 输出: false 409 | 410 | ``` 411 | 412 | **示例 4:** 413 | 414 | ``` 415 | 输入: "([)]" 416 | 输出: false 417 | 418 | ``` 419 | 420 | **示例 5:** 421 | 422 | ``` 423 | 输入: "{[]}" 424 | 输出: true 425 | 426 | ``` 427 | 428 | **代码** 429 | 430 | ```java 431 | class Solution { 432 | public boolean isValid(String s) { 433 | Map map = new HashMap<>(); 434 | map.put(')', '('); 435 | map.put('}', '{'); 436 | map.put(']', '['); 437 | Stack stack = new Stack<>(); 438 | 439 | char[] arr = s.toCharArray(); 440 | for(char c : arr){ 441 | if(!map.containsKey(c)) stack.push(c); 442 | else if(stack.isEmpty() || map.get(c) != stack.pop()) return false; 443 | } 444 | return stack.isEmpty(); 445 | } 446 | } 447 | 448 | ``` 449 | 450 | ------ 451 | 452 | #### [【队列&栈】implement-stack-using-queues(用队列实现栈)](https://leetcode-cn.com/problems/implement-stack-using-queues/) 453 | 454 | 使用队列实现栈的下列操作: 455 | 456 | - push(x) -- 元素 x 入栈 457 | - pop() -- 移除栈顶元素 458 | - top() -- 获取栈顶元素 459 | - empty() -- 返回栈是否为空 460 | 461 | **代码** 462 | 463 | ```java 464 | class MyStack { 465 | 466 | Queue q; 467 | /** Initialize your data structure here. */ 468 | public MyStack() { 469 | q = new LinkedList<>(); 470 | } 471 | 472 | /** Push element x onto stack. */ 473 | public void push(int x) { 474 | q.offer(x); 475 | int n = q.size(); 476 | for(int i = 0; i < n - 1; i ++){ 477 | q.offer(q.poll()); 478 | } 479 | } 480 | 481 | /** Removes the element on top of the stack and returns that element. */ 482 | public int pop() { 483 | return q.poll(); 484 | } 485 | 486 | /** Get the top element. */ 487 | public int top() { 488 | return q.peek(); 489 | } 490 | 491 | /** Returns whether the stack is empty. */ 492 | public boolean empty() { 493 | return q.isEmpty(); 494 | } 495 | } 496 | 497 | ``` 498 | 499 | ------ 500 | 501 | #### [【队列&栈】Implement Queue using Stacks(用栈实现队列)](https://leetcode-cn.com/problems/implement-queue-using-stacks/) 502 | 503 | 使用栈实现队列的下列操作: 504 | 505 | - push(x) -- 将一个元素放入队列的尾部。 506 | - pop() -- 从队列首部移除元素。 507 | - peek() -- 返回队列首部的元素。 508 | - empty() -- 返回队列是否为空。 509 | 510 | **代码** 511 | 512 | ```java 513 | class MyQueue { 514 | 515 | Stack s1, s2; 516 | /** Initialize your data structure here. */ 517 | public MyQueue() { 518 | s1 = new Stack<>(); 519 | s2 = new Stack<>(); 520 | } 521 | 522 | /** Push element x to the back of queue. */ 523 | public void push(int x) { 524 | s1.push(x); 525 | } 526 | 527 | /** Removes the element from in front of queue and returns that element. */ 528 | public int pop() { 529 | if(!s2.isEmpty()) return s2.pop(); 530 | else{ 531 | while(!s1.isEmpty()){ 532 | s2.push(s1.pop()); 533 | } 534 | return s2.pop(); 535 | } 536 | } 537 | 538 | /** Get the front element. */ 539 | public int peek() { 540 | if(!s2.isEmpty()) return s2.peek(); 541 | else{ 542 | while(!s1.isEmpty()){ 543 | s2.push(s1.pop()); 544 | } 545 | return s2.peek(); 546 | } 547 | } 548 | 549 | /** Returns whether the queue is empty. */ 550 | public boolean empty() { 551 | return s1.isEmpty() && s2.isEmpty(); 552 | } 553 | } 554 | 555 | ``` 556 | 557 | ------ 558 | 559 | #### [【优先队列】kth-largest-element-in-a-stream(数据流中的第K大元素)](https://leetcode-cn.com/problems/kth-largest-element-in-a-stream/) 560 | 561 | 设计一个找到数据流中第K大元素的类(class)。注意是排序后的第K大元素,不是第K个不同的元素。 562 | 563 | 你的 `KthLargest `类需要一个同时接收整数` k` 和整数数组`nums `的构造器,它包含数据流中的初始元素。每次调用 `KthLargest.add`,返回当前数据流中第`K`大的元素。 564 | 565 | ``` 566 | 示例: 567 | int k = 3; 568 | int[] arr = [4,5,8,2]; 569 | KthLargest kthLargest = new KthLargest(3, arr); 570 | kthLargest.add(3); // returns 4 571 | kthLargest.add(5); // returns 5 572 | kthLargest.add(10); // returns 5 573 | kthLargest.add(9); // returns 8 574 | kthLargest.add(4); // returns 8 575 | 576 | ``` 577 | 578 | 说明: 579 | 你可以假设 nums 的长度≥ k-1 且k ≥ 1。 580 | 581 | **代码** 582 | 583 | ```java 584 | class KthLargest { 585 | 586 | PriorityQueue q; 587 | int k; 588 | public KthLargest(int k, int[] nums) { 589 | this.k = k; 590 | q = new PriorityQueue<>(); 591 | for(int i : nums) add(i); 592 | } 593 | 594 | public int add(int val) { 595 | if(q.size() < k) q.offer(val); 596 | else if(val > q.peek()){ 597 | q.poll(); 598 | q.offer(val); 599 | } 600 | return q.peek(); 601 | } 602 | } 603 | 604 | ``` 605 | 606 | ------ 607 | 608 | #### [【优先队列】sliding-window-maximum(滑动窗口最大值)](https://leetcode-cn.com/problems/sliding-window-maximum/) 609 | 610 | 给定一个数组 *nums*,有一个大小为 *k* 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 *k* 内的数字。滑动窗口每次只向右移动一位。 611 | 612 | 返回滑动窗口最大值。 613 | 614 | **示例:** 615 | 616 | ``` 617 | 输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3 618 | 输出: [3,3,5,5,6,7] 619 | 解释: 620 | 621 | 滑动窗口的位置 最大值 622 | --------------- ----- 623 | [1 3 -1] -3 5 3 6 7 3 624 | 1 [3 -1 -3] 5 3 6 7 3 625 | 1 3 [-1 -3 5] 3 6 7 5 626 | 1 3 -1 [-3 5 3] 6 7 5 627 | 1 3 -1 -3 [5 3 6] 7 6 628 | 1 3 -1 -3 5 [3 6 7] 7 629 | 630 | ``` 631 | 632 | **注意:** 633 | 634 | 你可以假设 *k* 总是有效的,1 ≤ k ≤ 输入数组的大小,且输入数组不为空。 635 | 636 | **代码** 637 | 638 | `优先队列` 639 | 640 | ```java 641 | class Solution { 642 | public int[] maxSlidingWindow(int[] nums, int k) { 643 | if(nums.length == 0) return new int[0]; 644 | int[] res = new int[nums.length - k + 1]; 645 | PriorityQueue q = new PriorityQueue<>(k, Comparator.reverseOrder()); 646 | for(int i = 0; i < k; i ++){ 647 | q.offer(nums[i]); 648 | } 649 | res[0] = q.peek(); 650 | int index = 1; 651 | for(int i = k; i < nums.length; i ++){ 652 | q.remove(nums[index - 1]); 653 | q.offer(nums[i]); 654 | res[index++] = q.peek(); 655 | } 656 | return res; 657 | } 658 | } 659 | 660 | ``` 661 | 662 | `双端队列` 663 | 664 | ```java 665 | class Solution { 666 | public int[] maxSlidingWindow(int[] nums, int k) { 667 | if(nums.length == 0) return new int[0]; 668 | int[] res = new int[nums.length - k + 1]; 669 | Deque q = new LinkedList<>(); 670 | int index = 0; 671 | for(int i = 0; i < nums.length; i ++){ 672 | while(!q.isEmpty() && nums[q.peekLast()] <= nums[i]){ 673 | q.pollLast(); 674 | } 675 | q.offerLast(i); 676 | if(q.peek() == i - k) q.pollFirst(); 677 | if(i >= k - 1) res[index ++] = nums[q.peek()]; 678 | } 679 | return res; 680 | } 681 | } 682 | 683 | ``` 684 | 685 | ------ 686 | 687 | #### [【哈希】valid-anagram(有效的字母异位词)](https://leetcode-cn.com/problems/valid-anagram/) 688 | 689 | 给定两个字符串 *s* 和 *t* ,编写一个函数来判断 *t* 是否是 *s* 的一个字母异位词。 690 | 691 | **示例 1:** 692 | 693 | ``` 694 | 输入: s = "anagram", t = "nagaram" 695 | 输出: true 696 | 697 | ``` 698 | 699 | **示例 2:** 700 | 701 | ``` 702 | 输入: s = "rat", t = "car" 703 | 输出: false 704 | 705 | ``` 706 | 707 | **说明:** 708 | 你可以假设字符串只包含小写字母。 709 | 710 | **代码** 711 | 712 | `hashmap` 713 | 714 | ```java 715 | class Solution { 716 | public boolean isAnagram(String s, String t) { 717 | Map sMap = new HashMap<>(); 718 | Map tMap = new HashMap<>(); 719 | char[] sC = s.toCharArray(); 720 | char[] sT = t.toCharArray(); 721 | for(char c : sC){ 722 | sMap.put(c, sMap.getOrDefault(c, 0) + 1); 723 | } 724 | 725 | for(char c : sT){ 726 | tMap.put(c, tMap.getOrDefault(c, 0) + 1); 727 | } 728 | return sMap.equals(tMap); 729 | } 730 | } 731 | 732 | ``` 733 | 734 | `数组映射` 735 | 736 | ```java 737 | class Solution { 738 | public boolean isAnagram(String s, String t) { 739 | int[] a = new int[26]; 740 | int[] b = new int[26]; 741 | 742 | char[] sC = s.toCharArray(); 743 | char[] tC = t.toCharArray(); 744 | 745 | for(char c : sC){ 746 | a[c - 'a'] += 1; 747 | } 748 | 749 | for(char c : tC){ 750 | b[c - 'a'] += 1; 751 | } 752 | return Arrays.equals(a, b); 753 | } 754 | } 755 | 756 | ``` 757 | 758 | ------ 759 | 760 | #### [【哈希】two-sum(两数之和)](https://leetcode-cn.com/problems/two-sum/) 761 | 762 | 给定一个整数数组 `nums` 和一个目标值 `target`,请你在该数组中找出和为目标值的那 **两个** 整数,并返回他们的数组下标。 763 | 764 | 你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。 765 | 766 | **示例:** 767 | 768 | ``` 769 | 给定 nums = [2, 7, 11, 15], target = 9 770 | 771 | 因为 nums[0] + nums[1] = 2 + 7 = 9 772 | 所以返回 [0, 1] 773 | 774 | ``` 775 | 776 | **代码** 777 | 778 | ```java 779 | class Solution { 780 | public int[] twoSum(int[] nums, int target) { 781 | Map map = new HashMap<>(); 782 | for(int i = 0; i < nums.length; i ++){ 783 | if(map.containsKey(target - nums[i])) { 784 | return new int[]{map.get(target - nums[i]), i}; 785 | } 786 | map.put(nums[i], i); 787 | } 788 | return new int[2]; 789 | } 790 | } 791 | 792 | ``` 793 | 794 | ------ 795 | 796 | #### [【哈希】3sum(三数之和)](https://leetcode-cn.com/problems/3sum/) 797 | 798 | 给定一个包含 *n* 个整数的数组 `nums`,判断 `nums` 中是否存在三个元素 *a,b,c ,*使得 *a + b + c =* 0 ?找出所有满足条件且不重复的三元组。 799 | 800 | **注意:**答案中不可以包含重复的三元组。 801 | 802 | ``` 803 | 例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4], 804 | 805 | 满足要求的三元组集合为: 806 | [ 807 | [-1, 0, 1], 808 | [-1, -1, 2] 809 | ] 810 | 811 | ``` 812 | 813 | **代码** 814 | 815 | `排序后枚举` 816 | 817 | ```java 818 | class Solution { 819 | public List> threeSum(int[] nums) { 820 | Arrays.sort(nums); 821 | Set> res = new HashSet<>(); 822 | for(int i = 0; i < nums.length - 1; i ++){ 823 | if(i > 0 && nums[i] == nums[i - 1]) continue; 824 | Set set = new HashSet<>(); 825 | for(int j = i + 1; j < nums.length; j ++){ 826 | if(!set.contains(nums[j])) set.add(-nums[i]-nums[j]); 827 | else { 828 | res.add(Arrays.asList(nums[i], -nums[i] - nums[j], nums[j])); 829 | } 830 | } 831 | } 832 | return new ArrayList<>(res); 833 | } 834 | } 835 | 836 | ``` 837 | 838 | `排序后使用双指针` 839 | 840 | ```java 841 | class Solution { 842 | public List> threeSum(int[] nums) { 843 | Arrays.sort(nums); 844 | List> res = new ArrayList<>(); 845 | for(int i = 0; i < nums.length - 1; i ++){ 846 | if(i > 0 && nums[i] == nums[i - 1]) continue; 847 | int start = i + 1; 848 | int end = nums.length - 1; 849 | while(start < end){ 850 | int sum = nums[i] + nums[start] + nums[end]; 851 | if(sum > 0) end --; 852 | else if(sum < 0) start ++; 853 | else { 854 | res.add(Arrays.asList(nums[i], nums[start], nums[end])); 855 | //判重 856 | while(start < end && nums[start] == nums[start + 1]) start ++; 857 | while(start < end && nums[end] == nums[end - 1]) end --; 858 | start ++; 859 | end --; 860 | } 861 | } 862 | } 863 | return res; 864 | } 865 | } 866 | 867 | ``` 868 | 869 | ------ 870 | 871 | #### [【二叉树】validate-binary-search-tree(验证二叉搜索树)](https://leetcode-cn.com/problems/validate-binary-search-tree/) 872 | 873 | 给定一个二叉树,判断其是否是一个有效的二叉搜索树。 874 | 875 | 假设一个二叉搜索树具有如下特征: 876 | 877 | - 节点的左子树只包含**小于**当前节点的数。 878 | - 节点的右子树只包含**大于**当前节点的数。 879 | - 所有左子树和右子树自身必须也是二叉搜索树。 880 | 881 | **示例 1:** 882 | 883 | ``` 884 | 输入: 885 | 2 886 | / \ 887 | 1 3 888 | 输出: true 889 | 890 | ``` 891 | 892 | **示例 2:** 893 | 894 | ``` 895 | 输入: 896 | 5 897 | / \ 898 | 1 4 899 | / \ 900 | 3 6 901 | 输出: false 902 | 解释: 输入为: [5,1,4,null,null,3,6]。 903 | 根节点的值为 5 ,但是其右子节点值为 4 。 904 | 905 | ``` 906 | 907 | **代码** 908 | 909 | `利用中序遍历的有序性` 910 | 911 | ```java 912 | /** 913 | * Definition for a binary tree node. 914 | * public class TreeNode { 915 | * int val; 916 | * TreeNode left; 917 | * TreeNode right; 918 | * TreeNode(int x) { val = x; } 919 | * } 920 | */ 921 | class Solution { 922 | 923 | List list = new ArrayList<>(); 924 | 925 | public boolean isValidBST(TreeNode root) { 926 | midOrder(root); 927 | if(list.size() == 0) return true; 928 | int tmp = list.get(0); 929 | for(int i = 1,length = list.size(); i < length; i ++){ 930 | if(list.get(i) <= tmp){ 931 | return false; 932 | } 933 | tmp = list.get(i); 934 | } 935 | return true; 936 | } 937 | 938 | public void midOrder(TreeNode root){ 939 | if(root != null){ 940 | midOrder(root.left); 941 | list.add(root.val); 942 | midOrder(root.right); 943 | } 944 | } 945 | } 946 | 947 | ``` 948 | 949 | `递归` 950 | 951 | ```java 952 | /** 953 | * Definition for a binary tree node. 954 | * public class TreeNode { 955 | * int val; 956 | * TreeNode left; 957 | * TreeNode right; 958 | * TreeNode(int x) { val = x; } 959 | * } 960 | */ 961 | class Solution { 962 | TreeNode pre = null; 963 | public boolean isValidBST(TreeNode root) { 964 | if(root == null) return true; 965 | if(!isValidBST(root.left)) return false; 966 | if(pre != null && pre.val >= root.val) return false; 967 | pre = root; 968 | return isValidBST(root.right); 969 | } 970 | 971 | } 972 | 973 | ``` 974 | 975 | ------ 976 | 977 | #### [【二叉树】lowest-common-ancestor-of-a-binary-search-tree(二叉搜索树的最近公共祖先)](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/) 978 | 979 | 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 980 | 981 | [百度百科](https://baike.baidu.com/item/最近公共祖先/8918834?fr=aladdin)中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(**一个节点也可以是它自己的祖先**)。” 982 | 983 | 例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5] 984 | 985 | ![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/14/binarysearchtree_improved.png) 986 | 987 | 988 | 989 | **代码** 990 | 991 | ```java 992 | /** 993 | * Definition for a binary tree node. 994 | * public class TreeNode { 995 | * int val; 996 | * TreeNode left; 997 | * TreeNode right; 998 | * TreeNode(int x) { val = x; } 999 | * } 1000 | */ 1001 | class Solution { 1002 | public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { 1003 | while(root != null){ 1004 | if(p.val > root.val && q.val > root.val) root = root.right; 1005 | else if(p.val < root.val && q.val < root.val) root = root.left; 1006 | else return root; 1007 | } 1008 | return null; 1009 | } 1010 | } 1011 | 1012 | ``` 1013 | 1014 | ------ 1015 | 1016 | #### [【递归&分治】Pow(x, n)](https://leetcode-cn.com/problems/powx-n/) 1017 | 1018 | 实现 [pow(*x*, *n*)](https://www.cplusplus.com/reference/valarray/pow/) ,即计算 x 的 n 次幂函数。 1019 | 1020 | **示例 1:** 1021 | 1022 | ``` 1023 | 输入: 2.00000, 10 1024 | 输出: 1024.00000 1025 | 1026 | ``` 1027 | 1028 | **示例 2:** 1029 | 1030 | ``` 1031 | 输入: 2.10000, 3 1032 | 输出: 9.26100 1033 | 1034 | ``` 1035 | 1036 | **示例 3:** 1037 | 1038 | ``` 1039 | 输入: 2.00000, -2 1040 | 输出: 0.25000 1041 | 解释: 2-2 = 1/22 = 1/4 = 0.25 1042 | 1043 | ``` 1044 | 1045 | **说明:** 1046 | 1047 | - -100.0 < *x* < 100.0 1048 | - *n* 是 32 位有符号整数,其数值范围是 [−231, 231 − 1] 。 1049 | 1050 | **代码** 1051 | 1052 | ```java 1053 | class Solution { 1054 | public double myPow(double x, int n) { 1055 | if (n < 0) return 1.0 / myPow(x, -n); 1056 | if (n == 0) return 1; 1057 | double v = myPow(x, n / 2); 1058 | return n % 2 == 0 ? v * v : v * v * x; 1059 | } 1060 | } 1061 | 1062 | ``` 1063 | 1064 | ------ 1065 | 1066 | #### [【计数】majority-element(求众数)](https://leetcode-cn.com/problems/majority-element/) 1067 | 1068 | 给定一个大小为 *n* 的数组,找到其中的众数。众数是指在数组中出现次数**大于** `⌊ n/2 ⌋` 的元素。 1069 | 1070 | 你可以假设数组是非空的,并且给定的数组总是存在众数。 1071 | 1072 | **示例 1:** 1073 | 1074 | ``` 1075 | 输入: [3,2,3] 1076 | 输出: 3 1077 | 1078 | ``` 1079 | 1080 | **示例 2:** 1081 | 1082 | ``` 1083 | 输入: [2,2,1,1,1,2,2] 1084 | 输出: 2 1085 | 1086 | ``` 1087 | 1088 | **代码** 1089 | 1090 | `利用map计数` 1091 | 1092 | ```java 1093 | class Solution { 1094 | public int majorityElement(int[] nums) { 1095 | Map map = new HashMap<>(); 1096 | for(int i = 0, length = nums.length; i < length; i ++){ 1097 | if(map.containsKey(nums[i])){ 1098 | map.put(nums[i], map.get(nums[i]) + 1); 1099 | }else{ 1100 | map.put(nums[i], 1); 1101 | } 1102 | } 1103 | Set set = map.keySet(); 1104 | for(Integer key : set){ 1105 | if(map.get(key) > nums.length/2) return key; 1106 | } 1107 | return 0; 1108 | } 1109 | } 1110 | 1111 | ``` 1112 | 1113 | `排序计数` 1114 | 1115 | ```java 1116 | class Solution { 1117 | public int majorityElement(int[] nums) { 1118 | if(nums.length == 1) return nums[0]; 1119 | Arrays.sort(nums); 1120 | int count = 1; 1121 | for(int i = 1; i < nums.length; i ++){ 1122 | if(nums[i] == nums[i-1]){ 1123 | count ++; 1124 | } else count = 1; 1125 | if(count > nums.length / 2) return nums[i]; 1126 | } 1127 | return 0; 1128 | } 1129 | } 1130 | 1131 | ``` 1132 | 1133 | ------ 1134 | 1135 | #### [【贪心】best-time-to-buy-and-sell-stock-ii(买卖股票的最佳时机 II)](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/) 1136 | 1137 | 给定一个数组,它的第 *i* 个元素是一支给定股票第 *i* 天的价格。 1138 | 1139 | 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 1140 | 1141 | **注意:**你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 1142 | 1143 | **示例 1:** 1144 | 1145 | ``` 1146 | 输入: [7,1,5,3,6,4] 1147 | 输出: 7 1148 | 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 1149 | 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 1150 | 1151 | ``` 1152 | 1153 | **示例 2:** 1154 | 1155 | ``` 1156 | 输入: [1,2,3,4,5] 1157 | 输出: 4 1158 | 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 1159 | 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 1160 | 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 1161 | 1162 | ``` 1163 | 1164 | **示例 3:** 1165 | 1166 | ``` 1167 | 输入: [7,6,4,3,1] 1168 | 输出: 0 1169 | 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 1170 | 1171 | ``` 1172 | 1173 | **代码** 1174 | 1175 | ```java 1176 | class Solution { 1177 | public int maxProfit(int[] prices) { 1178 | if(prices == null || prices.length == 0) return 0; 1179 | int cur = prices[0]; 1180 | int length = prices.length; 1181 | int res = 0; 1182 | for(int i = 0; i < length; i ++){ 1183 | if(prices[i] > cur){ 1184 | res += (prices[i] - cur); 1185 | cur = prices[i]; 1186 | }else{ 1187 | cur = prices[i]; 1188 | } 1189 | } 1190 | return res; 1191 | } 1192 | } 1193 | 1194 | ``` 1195 | 1196 | ------ 1197 | 1198 | #### 【BFS】 广度优先搜索 (伪码) 1199 | 1200 | ``` 1201 | BFS() 1202 | { 1203 | 输入起始点; 1204 | 初始化所有顶点标记为未遍历; 1205 | 初始化一个队列queue并将起始点放入队列; 1206 | 1207 | while(queue不为空) 1208 | { 1209 | 从队列中删除一个顶点s并标记为已遍历; 1210 | 将s邻接的所有还没遍历的点加入队列; 1211 | } 1212 | } 1213 | 1214 | ``` 1215 | 1216 | ------ 1217 | 1218 | #### 【DFS】 深度优先搜索(伪码) 1219 | 1220 | ``` 1221 | DFS(顶点v) 1222 | { 1223 | 标记v为已遍历; 1224 | for(对于每一个邻接v且未标记遍历的点u) 1225 | DFS(u); 1226 | } 1227 | 1228 | ``` 1229 | 1230 | ------ 1231 | 1232 | #### [【BFS】binary-tree-level-order-traversal(二叉树的层次遍历)](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) 1233 | 1234 | 给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。 1235 | 1236 | 例如: 1237 | 给定二叉树: `[3,9,20,null,null,15,7]`, 1238 | 1239 | ``` 1240 | 3 1241 | / \ 1242 | 9 20 1243 | / \ 1244 | 15 7 1245 | 1246 | ``` 1247 | 1248 | 返回其层次遍历结果: 1249 | 1250 | ``` 1251 | [ 1252 | [3], 1253 | [9,20], 1254 | [15,7] 1255 | ] 1256 | 1257 | ``` 1258 | 1259 | **代码** 1260 | 1261 | `bfs` 1262 | 1263 | ```java 1264 | /** 1265 | * Definition for a binary tree node. 1266 | * public class TreeNode { 1267 | * int val; 1268 | * TreeNode left; 1269 | * TreeNode right; 1270 | * TreeNode(int x) { val = x; } 1271 | * } 1272 | */ 1273 | class Solution { 1274 | public List> levelOrder(TreeNode root) { 1275 | if(root == null) return new ArrayList<>(); 1276 | Queue queue = new LinkedList<>(); 1277 | List> result = new ArrayList<>(); 1278 | queue.offer(root); 1279 | while (!queue.isEmpty()){ 1280 | List node = new ArrayList<>(); 1281 | int length = queue.size(); 1282 | while (length > 0){ 1283 | TreeNode tree = queue.poll(); 1284 | if(tree.left != null){ 1285 | queue.offer(tree.left); 1286 | } 1287 | if(tree.right != null){ 1288 | queue.offer(tree.right); 1289 | } 1290 | node.add(tree.val); 1291 | length --; 1292 | } 1293 | result.add(node); 1294 | } 1295 | return result; 1296 | } 1297 | } 1298 | 1299 | ``` 1300 | 1301 | `也可以用dfs` 1302 | 1303 | ```java 1304 | class Solution { 1305 | public List> levelOrder(TreeNode root) { 1306 | if(root == null) return new ArrayList<>(); 1307 | List> res = new ArrayList<>(); 1308 | _dfs(res, root, 0); 1309 | return res; 1310 | } 1311 | 1312 | public void _dfs(List> list, TreeNode node, int level){ 1313 | if(node == null) return; 1314 | if(list.size() < level + 1){ 1315 | list.add(new ArrayList<>()); 1316 | } 1317 | list.get(level).add(node.val); 1318 | _dfs(list, node.left, level + 1); 1319 | _dfs(list, node.right, level + 1); 1320 | } 1321 | } 1322 | 1323 | ``` 1324 | 1325 | ------ 1326 | 1327 | #### [【DFS】maximum-depth-of-binary-tree(二叉树的最大深度)](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) 1328 | 1329 | 给定一个二叉树,找出其最大深度。 1330 | 1331 | 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 1332 | 1333 | **说明:** 叶子节点是指没有子节点的节点。 1334 | 1335 | **示例:** 1336 | 给定二叉树 `[3,9,20,null,null,15,7]`, 1337 | 1338 | ``` 1339 | 3 1340 | / \ 1341 | 9 20 1342 | / \ 1343 | 15 7 1344 | 1345 | ``` 1346 | 1347 | 返回它的最大深度 3 。 1348 | 1349 | **代码** 1350 | 1351 | `DFS` 1352 | 1353 | ```java 1354 | /** 1355 | * Definition for a binary tree node. 1356 | * public class TreeNode { 1357 | * int val; 1358 | * TreeNode left; 1359 | * TreeNode right; 1360 | * TreeNode(int x) { val = x; } 1361 | * } 1362 | */ 1363 | class Solution { 1364 | public int maxDepth(TreeNode root) { 1365 | return root == null ? 0 : Math.max(maxDepth(root.left),maxDepth(root.right)) + 1; 1366 | } 1367 | } 1368 | 1369 | ``` 1370 | 1371 | `BFS` 1372 | 1373 | ```java 1374 | /** 1375 | * Definition for a binary tree node. 1376 | * public class TreeNode { 1377 | * int val; 1378 | * TreeNode left; 1379 | * TreeNode right; 1380 | * TreeNode(int x) { val = x; } 1381 | * } 1382 | */ 1383 | class Solution { 1384 | public int maxDepth(TreeNode root) { 1385 | if(root == null) return 0; 1386 | Queue q = new LinkedList<>(); 1387 | q.offer(root); 1388 | int count = 1; 1389 | while(!q.isEmpty()){ 1390 | int length = q.size(); 1391 | boolean flag = false; 1392 | for(int i = 0; i < length; i ++){ 1393 | TreeNode node = q.poll(); 1394 | if(node.left != null || node.right != null) flag = true; 1395 | if(node.left != null) q.offer(node.left); 1396 | if(node.right != null) q.offer(node.right); 1397 | } 1398 | if(flag) count ++; 1399 | } 1400 | return count; 1401 | } 1402 | } 1403 | 1404 | ``` 1405 | 1406 | ------ 1407 | 1408 | #### [【DFS】minimum-depth-of-binary-tree(二叉树的最小深度)](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/) 1409 | 1410 | 给定一个二叉树,找出其最小深度。 1411 | 1412 | 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 1413 | 1414 | **说明:** 叶子节点是指没有子节点的节点。 1415 | 1416 | **示例:** 1417 | 1418 | 给定二叉树 `[3,9,20,null,null,15,7]`, 1419 | 1420 | ``` 1421 | 3 1422 | / \ 1423 | 9 20 1424 | / \ 1425 | 15 7 1426 | 1427 | ``` 1428 | 1429 | 返回它的最小深度 2. 1430 | 1431 | **代码** 1432 | 1433 | `BFS` 1434 | 1435 | ```java 1436 | class Solution { 1437 | public int minDepth(TreeNode root) { 1438 | if(root == null) return 0; 1439 | Queue q = new LinkedList<>(); 1440 | q.offer(root); 1441 | int count = 1; 1442 | while(!q.isEmpty()){ 1443 | int length = q.size(); 1444 | boolean flag = false; 1445 | for(int i = 0; i < length; i ++){ 1446 | TreeNode node = q.poll(); 1447 | if(node.left == null && node.right == null) return count; 1448 | if(node.left != null || node.right != null) flag = true; 1449 | if(node.left != null) q.offer(node.left); 1450 | if(node.right != null) q.offer(node.right); 1451 | } 1452 | if(flag) count ++; 1453 | } 1454 | return count; 1455 | } 1456 | } 1457 | 1458 | ``` 1459 | 1460 | `DFS` 1461 | 1462 | ```java 1463 | /** 1464 | * Definition for a binary tree node. 1465 | * public class TreeNode { 1466 | * int val; 1467 | * TreeNode left; 1468 | * TreeNode right; 1469 | * TreeNode(int x) { val = x; } 1470 | * } 1471 | */ 1472 | class Solution { 1473 | public int minDepth(TreeNode root) { 1474 | if(null == root) return 0; 1475 | if(null == root.left) return minDepth(root.right) + 1; 1476 | if(null == root.right) return minDepth(root.left) + 1; 1477 | return Math.min(minDepth(root.left),minDepth(root.right)) + 1; 1478 | } 1479 | } 1480 | 1481 | ``` 1482 | 1483 | ------ 1484 | 1485 | #### [【回溯】generate-parentheses(括号生成)](https://leetcode-cn.com/problems/generate-parentheses/) 1486 | 1487 | 给出 *n* 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且**有效的**括号组合。 1488 | 1489 | 例如,给出 *n* = 3,生成结果为: 1490 | 1491 | ``` 1492 | [ 1493 | "((()))", 1494 | "(()())", 1495 | "(())()", 1496 | "()(())", 1497 | "()()()" 1498 | ] 1499 | 1500 | ``` 1501 | 1502 | **代码** 1503 | 1504 | ```java 1505 | class Solution { 1506 | public List generateParenthesis(int n) { 1507 | List res = new ArrayList<>(); 1508 | _gen(res, 0, 0, n, ""); 1509 | return res; 1510 | } 1511 | 1512 | public void _gen(List list, int left, int right, int n, String res){ 1513 | if(left == n && right == n){ 1514 | list.add(res); 1515 | return; 1516 | } 1517 | if(left < n){ 1518 | _gen(list, left + 1, right, n, res + "("); 1519 | } 1520 | if(left > right && right < n){ 1521 | _gen(list, left, right + 1, n, res + ")"); 1522 | } 1523 | } 1524 | } 1525 | 1526 | ``` 1527 | 1528 | ------ 1529 | 1530 | #### [【剪枝】n-queens(N皇后)](https://leetcode-cn.com/problems/n-queens/) 1531 | 1532 | *n* 皇后问题研究的是如何将 *n* 个皇后放置在 *n*×*n* 的棋盘上,并且使皇后彼此之间不能相互攻击。 1533 | 1534 | ![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/10/12/8-queens.png) 1535 | 1536 | 上图为 8 皇后问题的一种解法。 1537 | 1538 | 给定一个整数 *n*,返回所有不同的 *n* 皇后问题的解决方案。 1539 | 1540 | 每一种解法包含一个明确的 *n* 皇后问题的棋子放置方案,该方案中 `'Q'` 和 `'.'` 分别代表了皇后和空位。 1541 | 1542 | **示例:** 1543 | 1544 | ``` 1545 | 输入: 4 1546 | 输出: [ 1547 | [".Q..", // 解法 1 1548 | "...Q", 1549 | "Q...", 1550 | "..Q."], 1551 | 1552 | ["..Q.", // 解法 2 1553 | "Q...", 1554 | "...Q", 1555 | ".Q.."] 1556 | ] 1557 | 解释: 4 皇后问题存在两个不同的解法。 1558 | 1559 | ``` 1560 | 1561 | **代码** 1562 | 1563 | ```java 1564 | class Solution { 1565 | private List> res = new ArrayList<>(); 1566 | private Set cols = new HashSet<>(); 1567 | private Set pie = new HashSet<>(); 1568 | private Set na = new HashSet<>(); 1569 | public List> solveNQueens(int n) { 1570 | if (n < 1) { 1571 | return new ArrayList<>(); 1572 | } 1573 | dfs(n, 0, new ArrayList<>()); 1574 | return res; 1575 | } 1576 | 1577 | public void dfs(int n, int row, List curState) { 1578 | if (row >= n) { 1579 | res.add(curState); 1580 | return; 1581 | } 1582 | for (int i = 0; i < n; i ++) { 1583 | if (cols.contains(i) || pie.contains(row + i) || na.contains(row - i)) { 1584 | continue; 1585 | } 1586 | cols.add(i); 1587 | pie.add(row + i); 1588 | na.add(row - i); 1589 | 1590 | StringBuilder sb = new StringBuilder(); 1591 | for(int j = 0; j < n; j ++) { 1592 | if (j == i) { 1593 | sb.append("Q"); 1594 | } else { 1595 | sb.append("."); 1596 | } 1597 | } 1598 | List tmp = new ArrayList(curState); 1599 | tmp.add(sb.toString()); 1600 | dfs(n, row + 1, tmp); 1601 | cols.remove(i); 1602 | pie.remove(row + i); 1603 | na.remove(row - i); 1604 | } 1605 | 1606 | } 1607 | } 1608 | 1609 | ``` 1610 | 1611 | ------ 1612 | 1613 | #### [【哈希】valid-sudoku(有效的数独)](https://leetcode-cn.com/problems/valid-sudoku/) 1614 | 1615 | 判断一个 9x9 的数独是否有效。只需要**根据以下规则**,验证已经填入的数字是否有效即可。 1616 | 1617 | 1. 数字 `1-9` 在每一行只能出现一次。 1618 | 2. 数字 `1-9` 在每一列只能出现一次。 1619 | 3. 数字 `1-9` 在每一个以粗实线分隔的 `3x3` 宫内只能出现一次。 1620 | 1621 | ![img](https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Sudoku-by-L2G-20050714.svg/250px-Sudoku-by-L2G-20050714.svg.png) 1622 | 1623 | 上图是一个部分填充的有效的数独。 1624 | 1625 | 数独部分空格内已填入了数字,空白格用 `'.'` 表示。 1626 | 1627 | **示例 1:** 1628 | 1629 | ``` 1630 | 输入: 1631 | [ 1632 | ["5","3",".",".","7",".",".",".","."], 1633 | ["6",".",".","1","9","5",".",".","."], 1634 | [".","9","8",".",".",".",".","6","."], 1635 | ["8",".",".",".","6",".",".",".","3"], 1636 | ["4",".",".","8",".","3",".",".","1"], 1637 | ["7",".",".",".","2",".",".",".","6"], 1638 | [".","6",".",".",".",".","2","8","."], 1639 | [".",".",".","4","1","9",".",".","5"], 1640 | [".",".",".",".","8",".",".","7","9"] 1641 | ] 1642 | 输出: true 1643 | 1644 | ``` 1645 | 1646 | **示例 2:** 1647 | 1648 | ``` 1649 | 输入: 1650 | [ 1651 | ["8","3",".",".","7",".",".",".","."], 1652 | ["6",".",".","1","9","5",".",".","."], 1653 | [".","9","8",".",".",".",".","6","."], 1654 | ["8",".",".",".","6",".",".",".","3"], 1655 | ["4",".",".","8",".","3",".",".","1"], 1656 | ["7",".",".",".","2",".",".",".","6"], 1657 | [".","6",".",".",".",".","2","8","."], 1658 | [".",".",".","4","1","9",".",".","5"], 1659 | [".",".",".",".","8",".",".","7","9"] 1660 | ] 1661 | 输出: false 1662 | 解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。 1663 | 但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。 1664 | 1665 | ``` 1666 | 1667 | **说明:** 1668 | 1669 | - 一个有效的数独(部分已被填充)不一定是可解的。 1670 | - 只需要根据以上规则,验证已经填入的数字是否有效即可。 1671 | - 给定数独序列只包含数字 `1-9` 和字符 `'.'` 。 1672 | - 给定数独永远是 `9x9` 形式的。 1673 | 1674 | **代码** 1675 | 1676 | ```java 1677 | class Solution { 1678 | public boolean isValidSudoku(char[][] board) { 1679 | // 记录某行,某位数字是否已经被摆放 1680 | boolean[][] row = new boolean[9][9]; 1681 | // 记录某列,某位数字是否已经被摆放 1682 | boolean[][] col = new boolean[9][9]; 1683 | // 记录某 3x3 宫格内,某位数字是否已经被摆放 1684 | boolean[][] block = new boolean[9][9]; 1685 | 1686 | for (int i = 0; i < 9; i++) { 1687 | for (int j = 0; j < 9; j++) { 1688 | if (board[i][j] != '.') { 1689 | int num = board[i][j] - '1'; 1690 | int blockIndex = i / 3 * 3 + j / 3; 1691 | if (row[i][num] || col[j][num] || block[blockIndex][num]) { 1692 | return false; 1693 | } else { 1694 | row[i][num] = true; 1695 | col[j][num] = true; 1696 | block[blockIndex][num] = true; 1697 | } 1698 | } 1699 | } 1700 | } 1701 | return true; 1702 | } 1703 | } 1704 | 1705 | ``` 1706 | 1707 | ------ 1708 | 1709 | #### [【剪枝&回溯】sudoku-solver(解数独)](https://leetcode-cn.com/problems/sudoku-solver/) 1710 | 1711 | 编写一个程序,通过已填充的空格来解决数独问题。 1712 | 1713 | 一个数独的解法需**遵循如下规则**: 1714 | 1715 | 1. 数字 `1-9` 在每一行只能出现一次。 1716 | 2. 数字 `1-9` 在每一列只能出现一次。 1717 | 3. 数字 `1-9` 在每一个以粗实线分隔的 `3x3` 宫内只能出现一次。 1718 | 1719 | 空白格用 `'.'` 表示。 1720 | 1721 | ![img](http://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Sudoku-by-L2G-20050714.svg/250px-Sudoku-by-L2G-20050714.svg.png) 1722 | 1723 | 一个数独。 1724 | 1725 | ![img](http://upload.wikimedia.org/wikipedia/commons/thumb/3/31/Sudoku-by-L2G-20050714_solution.svg/250px-Sudoku-by-L2G-20050714_solution.svg.png) 1726 | 1727 | 答案被标成红色。 1728 | 1729 | **Note:** 1730 | 1731 | - 给定的数独序列只包含数字 `1-9` 和字符 `'.'` 。 1732 | - 你可以假设给定的数独只有唯一解。 1733 | - 给定数独永远是 `9x9` 形式的。 1734 | 1735 | **代码** 1736 | 1737 | ```java 1738 | class Solution { 1739 | 1740 | public void solveSudoku(char[][] board) { 1741 | if (board == null || board.length == 0) return; 1742 | solve(board); 1743 | } 1744 | 1745 | private boolean solve(char[][] board) { 1746 | for (int i = 0; i < board.length; i ++) { 1747 | for (int j = 0; j < board[0].length; j ++) { 1748 | if (board[i][j] == '.') { 1749 | for (char c = '1'; c <= '9'; c ++) { 1750 | if (isValid(board, i, j, c)) { 1751 | board[i][j] = c; 1752 | if (solve(board)) { 1753 | return true; 1754 | } else { 1755 | board[i][j] = '.';//还原 1756 | } 1757 | } 1758 | } 1759 | return false; 1760 | } 1761 | } 1762 | } 1763 | return true; 1764 | } 1765 | 1766 | private boolean isValid(char[][] board, int row, int col, char c) { 1767 | for (int i = 0; i < 9; i ++) { 1768 | if(board[i][col] != '.' && board[i][col] == c) return false; 1769 | if(board[row][i] != '.' && board[row][i] == c) return false; 1770 | int blocki = 3 * (row / 3) + i / 3; 1771 | int blockj = 3 * (col / 3) + i % 3; 1772 | if(board[blocki][blockj] != '.' && board[blocki][blockj] == c) return false; 1773 | } 1774 | return true; 1775 | } 1776 | } 1777 | 1778 | ``` 1779 | 1780 | ------ 1781 | 1782 | #### 【二分查找】返回指定元素下标(基本用法) 1783 | 1784 | ```java 1785 | public int getTargetIndex(int[] arr, int target) { 1786 | int left = 0; 1787 | int right = arr.length - 1; 1788 | while (left <= right) { 1789 | int mid = (left + right) / 2; 1790 | if (arr[mid] == target) { 1791 | return mid; 1792 | } else if (arr[mid] < target) { 1793 | left = mid + 1; 1794 | } else { 1795 | right = mid - 1; 1796 | } 1797 | } 1798 | return -1; 1799 | } 1800 | 1801 | ``` 1802 | 1803 | ------ 1804 | 1805 | #### [【二分查找】sqrtx(x 的平方根)](https://leetcode-cn.com/problems/sqrtx/) 1806 | 1807 | 实现 `int sqrt(int x)` 函数。 1808 | 1809 | 计算并返回 *x* 的平方根,其中 *x* 是非负整数。 1810 | 1811 | 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。 1812 | 1813 | **示例 1:** 1814 | 1815 | ``` 1816 | 输入: 4 1817 | 输出: 2 1818 | 1819 | ``` 1820 | 1821 | **示例 2:** 1822 | 1823 | ``` 1824 | 输入: 8 1825 | 输出: 2 1826 | 说明: 8 的平方根是 2.82842..., 1827 | 由于返回类型是整数,小数部分将被舍去。 1828 | 1829 | ``` 1830 | 1831 | **代码** 1832 | 1833 | ```java 1834 | class Solution { 1835 | public int mySqrt(int x) { 1836 | if(x == 0 || x == 1) return x; 1837 | int left = 1; 1838 | int right = x; 1839 | while (left <= right) { 1840 | int mid = (left + right) / 2; 1841 | if (mid == x / mid) { 1842 | return mid; 1843 | } else if (mid > x / mid) { 1844 | right = mid - 1; 1845 | } else { 1846 | left = mid + 1; 1847 | } 1848 | } 1849 | return left - 1; 1850 | } 1851 | } 1852 | 1853 | ``` 1854 | 1855 | ------ 1856 | 1857 | #### [【二分查找】valid-perfect-square(有效的完全平方数)](https://leetcode-cn.com/problems/valid-perfect-square/) 1858 | 1859 | 给定一个正整数 *num*,编写一个函数,如果 *num* 是一个完全平方数,则返回 True,否则返回 False。 1860 | 1861 | **说明:**不要使用任何内置的库函数,如 `sqrt`。 1862 | 1863 | **示例 1:** 1864 | 1865 | ``` 1866 | 输入:16 1867 | 输出:True 1868 | 1869 | ``` 1870 | 1871 | **示例 2:** 1872 | 1873 | ``` 1874 | 输入:14 1875 | 输出:False 1876 | 1877 | ``` 1878 | 1879 | **代码** 1880 | 1881 | `公式法--1+3+5+7+9+…+(2n-1)=n^2` 1882 | 1883 | ```java 1884 | class Solution { 1885 | public boolean isPerfectSquare(int num) { 1886 | int sum = 0; 1887 | for(int i = 1; num > 0; i +=2){ 1888 | num -= i; 1889 | } 1890 | return num == 0; 1891 | } 1892 | } 1893 | 1894 | ``` 1895 | 1896 | `二分法` 1897 | 1898 | ```java 1899 | class Solution { 1900 | public boolean isPerfectSquare(int num) { 1901 | if(num == 0 || num == 1) return true; 1902 | int left = 1; 1903 | int right = num; 1904 | int res = 0; 1905 | while (left <= right) { 1906 | int mid = (left + right) / 2; 1907 | if (mid == num / mid) { 1908 | res = mid; 1909 | break; 1910 | } else if (mid > num / mid) { 1911 | right = mid - 1; 1912 | } else { 1913 | left = mid + 1; 1914 | res = mid; 1915 | } 1916 | } 1917 | return res * res == num; 1918 | } 1919 | } 1920 | 1921 | ``` 1922 | 1923 | ------ 1924 | 1925 | #### [【字典树】implement-trie-prefix-tree(实现 Trie (前缀树))](https://leetcode-cn.com/problems/implement-trie-prefix-tree/) 1926 | 1927 | 实现一个 Trie (前缀树),包含 `insert`, `search`, 和 `startsWith` 这三个操作。 1928 | 1929 | **示例:** 1930 | 1931 | ``` 1932 | Trie trie = new Trie(); 1933 | 1934 | trie.insert("apple"); 1935 | trie.search("apple"); // 返回 true 1936 | trie.search("app"); // 返回 false 1937 | trie.startsWith("app"); // 返回 true 1938 | trie.insert("app"); 1939 | trie.search("app"); // 返回 true 1940 | 1941 | ``` 1942 | 1943 | **说明:** 1944 | 1945 | - 你可以假设所有的输入都是由小写字母 `a-z` 构成的。 1946 | - 保证所有输入均为非空字符串。 1947 | 1948 | **代码** 1949 | 1950 | ```java 1951 | class Trie { 1952 | 1953 | Trie[] children = new Trie[26]; 1954 | boolean isEndOfWord = false; 1955 | 1956 | /** Initialize your data structure here. */ 1957 | public Trie() { 1958 | 1959 | } 1960 | 1961 | /** Inserts a word into the trie. */ 1962 | public void insert(String word) { 1963 | char[] arr = word.toCharArray(); 1964 | Trie[] childs = children; 1965 | for (int i = 0; i < arr.length; i ++) { 1966 | if (childs[arr[i] - 'a'] == null) { 1967 | childs[arr[i] - 'a'] = new Trie(); 1968 | } 1969 | if (i == arr.length - 1) { 1970 | childs[arr[i] - 'a'].isEndOfWord = true; 1971 | } 1972 | childs = childs[arr[i] - 'a'].children; 1973 | } 1974 | } 1975 | 1976 | /** Returns if the word is in the trie. */ 1977 | public boolean search(String word) { 1978 | char[] arr = word.toCharArray(); 1979 | Trie[] childs = children; 1980 | for (int i = 0; i < arr.length; i ++) { 1981 | if (childs[arr[i] - 'a'] == null) { 1982 | return false; 1983 | } 1984 | if (i == arr.length - 1 && !childs[arr[i] - 'a'].isEndOfWord) { 1985 | return false; 1986 | } 1987 | childs = childs[arr[i] - 'a'].children; 1988 | } 1989 | return true; 1990 | } 1991 | 1992 | /** Returns if there is any word in the trie that starts with the given prefix. */ 1993 | public boolean startsWith (String prefix) { 1994 | char[] arr = prefix.toCharArray(); 1995 | Trie[] childs = children; 1996 | for (int i = 0; i < arr.length; i ++) { 1997 | if (childs[arr[i] - 'a'] == null) { 1998 | return false; 1999 | } 2000 | childs = childs[arr[i] - 'a'].children; 2001 | } 2002 | return true; 2003 | } 2004 | } 2005 | 2006 | /** 2007 | * Your Trie object will be instantiated and called as such: 2008 | * Trie obj = new Trie(); 2009 | * obj.insert(word); 2010 | * boolean param_2 = obj.search(word); 2011 | * boolean param_3 = obj.startsWith(prefix); 2012 | */ 2013 | 2014 | ``` 2015 | 2016 | ------ 2017 | 2018 | #### [【字典树】word-search-ii(单词搜索 II)](https://leetcode-cn.com/problems/word-search-ii/) 2019 | 2020 | 给定一个二维网格 **board** 和一个字典中的单词列表 **words**,找出所有同时在二维网格和字典中出现的单词。 2021 | 2022 | 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。 2023 | 2024 | **示例:** 2025 | 2026 | ``` 2027 | 输入: 2028 | words = ["oath","pea","eat","rain"] and board = 2029 | [ 2030 | ['o','a','a','n'], 2031 | ['e','t','a','e'], 2032 | ['i','h','k','r'], 2033 | ['i','f','l','v'] 2034 | ] 2035 | 2036 | 输出: ["eat","oath"] 2037 | 2038 | ``` 2039 | 2040 | **说明:** 2041 | 你可以假设所有输入都由小写字母 `a-z` 组成。 2042 | 2043 | **提示:** 2044 | 2045 | - 你需要优化回溯算法以通过更大数据量的测试。你能否早点停止回溯? 2046 | - 如果当前单词不存在于所有单词的前缀中,则可以立即停止回溯。什么样的数据结构可以有效地执行这样的操作?散列表是否可行?为什么? 前缀树如何?如果你想学习如何实现一个基本的前缀树,请先查看这个问题: [实现Trie(前缀树)](https://leetcode-cn.com/problems/implement-trie-prefix-tree/description/)。 2047 | 2048 | **代码** 2049 | 2050 | ```java 2051 | class Solution { 2052 | Set res = new HashSet<>(); 2053 | public List findWords(char[][] board, String[] words) { 2054 | Trie trie = new Trie(); 2055 | for (String word : words) { 2056 | trie.insert(word); 2057 | } 2058 | int m = board.length; 2059 | int n = board[0].length; 2060 | boolean[][] visited = new boolean[m][n]; 2061 | for (int i = 0; i < m; i ++) { 2062 | for (int j = 0; j < n; j ++) { 2063 | dfs(board, visited, "", i, j, trie); 2064 | } 2065 | } 2066 | return new ArrayList<>(res); 2067 | } 2068 | 2069 | private void dfs(char[][] board, boolean[][] visited, String str, int x, int y, Trie trie) { 2070 | if (x < 0 || x >= board.length || y < 0 || y >= board[0].length) return; 2071 | if (visited[x][y]) return; 2072 | str += board[x][y]; 2073 | if (!trie.startsWith(str)) return; 2074 | if (trie.search(str)) { 2075 | res.add(str); 2076 | } 2077 | visited[x][y] = true; 2078 | dfs(board, visited, str, x - 1, y, trie); 2079 | dfs(board, visited, str, x + 1, y, trie); 2080 | dfs(board, visited, str, x, y - 1, trie); 2081 | dfs(board, visited, str, x, y + 1, trie); 2082 | visited[x][y] = false; 2083 | } 2084 | } 2085 | class Trie { 2086 | 2087 | Trie[] children = new Trie[26]; 2088 | boolean isEndOfWord = false; 2089 | 2090 | /** Initialize your data structure here. */ 2091 | public Trie() { 2092 | 2093 | } 2094 | 2095 | /** Inserts a word into the trie. */ 2096 | public void insert(String word) { 2097 | char[] arr = word.toCharArray(); 2098 | Trie[] childs = children; 2099 | for (int i = 0; i < arr.length; i ++) { 2100 | if (childs[arr[i] - 'a'] == null) { 2101 | childs[arr[i] - 'a'] = new Trie(); 2102 | } 2103 | if (i == arr.length - 1) { 2104 | childs[arr[i] - 'a'].isEndOfWord = true; 2105 | } 2106 | childs = childs[arr[i] - 'a'].children; 2107 | } 2108 | } 2109 | 2110 | /** Returns if the word is in the trie. */ 2111 | public boolean search(String word) { 2112 | char[] arr = word.toCharArray(); 2113 | Trie[] childs = children; 2114 | for (int i = 0; i < arr.length; i ++) { 2115 | if (childs[arr[i] - 'a'] == null) { 2116 | return false; 2117 | } 2118 | if (i == arr.length - 1 && !childs[arr[i] - 'a'].isEndOfWord) { 2119 | return false; 2120 | } 2121 | childs = childs[arr[i] - 'a'].children; 2122 | } 2123 | return true; 2124 | } 2125 | 2126 | /** Returns if there is any word in the trie that starts with the given prefix. */ 2127 | public boolean startsWith (String prefix) { 2128 | char[] arr = prefix.toCharArray(); 2129 | Trie[] childs = children; 2130 | for (int i = 0; i < arr.length; i ++) { 2131 | if (childs[arr[i] - 'a'] == null) { 2132 | return false; 2133 | } 2134 | childs = childs[arr[i] - 'a'].children; 2135 | } 2136 | return true; 2137 | } 2138 | } 2139 | 2140 | ``` 2141 | 2142 | ------ 2143 | 2144 | #### [【位运算】number-of-1-bits(位1的个数)](https://leetcode-cn.com/problems/number-of-1-bits/) 2145 | 2146 | 编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为[汉明重量](https://baike.baidu.com/item/汉明重量))。 2147 | 2148 | 2149 | 2150 | **示例 1:** 2151 | 2152 | ``` 2153 | 输入:00000000000000000000000000001011 2154 | 输出:3 2155 | 解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。 2156 | 2157 | ``` 2158 | 2159 | **示例 2:** 2160 | 2161 | ``` 2162 | 输入:00000000000000000000000010000000 2163 | 输出:1 2164 | 解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。 2165 | 2166 | ``` 2167 | 2168 | **示例 3:** 2169 | 2170 | ``` 2171 | 输入:11111111111111111111111111111101 2172 | 输出:31 2173 | 解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。 2174 | 2175 | ``` 2176 | 2177 | 2178 | 2179 | **提示:** 2180 | 2181 | - 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。 2182 | - 在 Java 中,编译器使用[二进制补码](https://baike.baidu.com/item/二进制补码/5295284)记法来表示有符号整数。因此,在上面的 **示例 3** 中,输入表示有符号整数 `-3`。 2183 | 2184 | 2185 | 2186 | **代码** 2187 | 2188 | `遍历每一位是否为1` 2189 | 2190 | ```java 2191 | public class Solution { 2192 | // you need to treat n as an unsigned value 2193 | public int hammingWeight(int n) { 2194 | int count = 0; 2195 | for (int i = 0; i < 32; i ++) { 2196 | count += n & 1; 2197 | n >>= 1; 2198 | } 2199 | return count; 2200 | } 2201 | } 2202 | 2203 | ``` 2204 | 2205 | `逐步打掉最后的1` 2206 | 2207 | ```java 2208 | public class Solution { 2209 | // you need to treat n as an unsigned value 2210 | public int hammingWeight(int n) { 2211 | int count = 0; 2212 | while (n != 0) { 2213 | count ++; 2214 | n = n & (n-1); 2215 | } 2216 | return count; 2217 | } 2218 | } 2219 | 2220 | ``` 2221 | 2222 | ------ 2223 | 2224 | #### [【位运算】power-of-two(2的幂)](https://leetcode-cn.com/problems/power-of-two/) 2225 | 2226 | 给定一个整数,编写一个函数来判断它是否是 2 的幂次方。 2227 | 2228 | **示例 1:** 2229 | 2230 | ``` 2231 | 输入: 1 2232 | 输出: true 2233 | 解释: 20 = 1 2234 | 2235 | ``` 2236 | 2237 | **示例 2:** 2238 | 2239 | ``` 2240 | 输入: 16 2241 | 输出: true 2242 | 解释: 24 = 16 2243 | 2244 | ``` 2245 | 2246 | **示例 3:** 2247 | 2248 | ``` 2249 | 输入: 218 2250 | 输出: false 2251 | 2252 | ``` 2253 | 2254 | **代码** 2255 | 2256 | `迭代判断` 2257 | 2258 | ```java 2259 | class Solution { 2260 | public boolean isPowerOfTwo(int n) { 2261 | if (n < 1) return false; 2262 | while (n > 1) { 2263 | if(n % 2 != 0) return false; 2264 | n /= 2; 2265 | } 2266 | return true; 2267 | } 2268 | } 2269 | 2270 | ``` 2271 | 2272 | `位运算(2的幂二进制只有一个1)` 2273 | 2274 | ```java 2275 | class Solution { 2276 | public boolean isPowerOfTwo(int n) { 2277 | return n > 0 && (n & (n - 1)) == 0; 2278 | } 2279 | } 2280 | 2281 | ``` 2282 | 2283 | ------ 2284 | 2285 | #### [【位运算】counting-bits(比特位计数)](https://leetcode-cn.com/problems/counting-bits/) 2286 | 2287 | 给定一个非负整数 **num**。对于 **0 ≤ i ≤ num** 范围中的每个数字 **i** ,计算其二进制数中的 1 的数目并将它们作为数组返回。 2288 | 2289 | **示例 1:** 2290 | 2291 | ``` 2292 | 输入: 2 2293 | 输出: [0,1,1] 2294 | 2295 | ``` 2296 | 2297 | **示例 2:** 2298 | 2299 | ``` 2300 | 输入: 5 2301 | 输出: [0,1,1,2,1,2] 2302 | 2303 | ``` 2304 | 2305 | **进阶:** 2306 | 2307 | - 给出时间复杂度为**O(n\*sizeof(integer))**的解答非常容易。但你可以在线性时间**O(n)**内用一趟扫描做到吗? 2308 | - 要求算法的空间复杂度为**O(n)**。 2309 | - 你能进一步完善解法吗?要求在C++或任何其他语言中不使用任何内置函数(如 C++ 中的 **__builtin_popcount**)来执行此操作。 2310 | 2311 | **代码** 2312 | 2313 | `求每个数的1的数目` 2314 | 2315 | ```java 2316 | class Solution { 2317 | public int[] countBits(int num) { 2318 | int[] res = new int[num + 1]; 2319 | for (int i = 0; i <= num; i ++) { 2320 | res[i] = get1num(i); 2321 | } 2322 | return res; 2323 | } 2324 | 2325 | private int get1num(int num) { 2326 | int count = 0; 2327 | while (num != 0) { 2328 | count ++; 2329 | num &= num-1; 2330 | } 2331 | return count; 2332 | } 2333 | } 2334 | 2335 | ``` 2336 | 2337 | `利用位运算 i&(i-1)要比i少一个1` 2338 | 2339 | ```java 2340 | class Solution { 2341 | public int[] countBits(int num) { 2342 | int[] res = new int[num + 1]; 2343 | for (int i = 1; i <= num; i ++) { 2344 | res[i] = res[i & (i - 1)] + 1; 2345 | } 2346 | return res; 2347 | } 2348 | } 2349 | 2350 | ``` 2351 | 2352 | ------ 2353 | 2354 | #### [【动态规划】fibonacci-number(斐波那契数)](https://leetcode-cn.com/problems/fibonacci-number/) 2355 | 2356 | **斐波那契数**,通常用 `F(n)` 表示,形成的序列称为**斐波那契数列**。该数列由 `0` 和 `1` 开始,后面的每一项数字都是前面两项数字的和。也就是: 2357 | 2358 | ``` 2359 | F(0) = 0, F(1) = 1 2360 | F(N) = F(N - 1) + F(N - 2), 其中 N > 1. 2361 | 2362 | ``` 2363 | 2364 | 给定 `N`,计算 `F(N)`。 2365 | 2366 | **示例 1:** 2367 | 2368 | ``` 2369 | 输入:2 2370 | 输出:1 2371 | 解释:F(2) = F(1) + F(0) = 1 + 0 = 1. 2372 | 2373 | ``` 2374 | 2375 | **示例 2:** 2376 | 2377 | ``` 2378 | 输入:3 2379 | 输出:2 2380 | 解释:F(3) = F(2) + F(1) = 1 + 1 = 2. 2381 | 2382 | ``` 2383 | 2384 | **示例 3:** 2385 | 2386 | ``` 2387 | 输入:4 2388 | 输出:3 2389 | 解释:F(4) = F(3) + F(2) = 2 + 1 = 3. 2390 | 2391 | ``` 2392 | 2393 | **代码** 2394 | 2395 | `递归` 2396 | 2397 | ```java 2398 | public int fib(int N) { 2399 | if(N < 2) return N; 2400 | return fib(N - 1) + fib(N - 2); 2401 | } 2402 | 2403 | ``` 2404 | 2405 | `动态规划` 2406 | 2407 | ```java 2408 | public int fib(int N) { 2409 | //dp[N] = dp[N - 1] + dp[N - 2]; 2410 | int[] dp = new int[N + 1]; 2411 | if(N == 0) return 0; 2412 | dp[0] = 0; 2413 | dp[1] = 1; 2414 | for(int i = 2; i <= N; i ++){ 2415 | dp[i] = dp[i - 1] + dp[i - 2]; 2416 | } 2417 | return dp[N]; 2418 | } 2419 | 2420 | ``` 2421 | 2422 | `遍历` 2423 | 2424 | ```java 2425 | public int fib(int N) { 2426 | if(N < 2) return N; 2427 | int a = 0; 2428 | int b = 1; 2429 | int c = 0; 2430 | for(int i = 2; i <= N; i ++){ 2431 | c = a + b; 2432 | a = b; 2433 | b = c; 2434 | } 2435 | return c; 2436 | } 2437 | 2438 | ``` 2439 | 2440 | ------ 2441 | 2442 | #### [【动态规划】climbing-stairs(爬楼梯)](https://leetcode-cn.com/problems/climbing-stairs/) 2443 | 2444 | 假设你正在爬楼梯。需要 *n* 阶你才能到达楼顶。 2445 | 2446 | 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 2447 | 2448 | **注意:**给定 *n* 是一个正整数。 2449 | 2450 | **示例 1:** 2451 | 2452 | ``` 2453 | 输入: 2 2454 | 输出: 2 2455 | 解释: 有两种方法可以爬到楼顶。 2456 | 1. 1 阶 + 1 阶 2457 | 2. 2 阶 2458 | 2459 | ``` 2460 | 2461 | **示例 2:** 2462 | 2463 | ``` 2464 | 输入: 3 2465 | 输出: 3 2466 | 解释: 有三种方法可以爬到楼顶。 2467 | 1. 1 阶 + 1 阶 + 1 阶 2468 | 2. 1 阶 + 2 阶 2469 | 3. 2 阶 + 1 阶 2470 | 2471 | ``` 2472 | 2473 | **代码** 2474 | 2475 | ```java 2476 | class Solution { 2477 | public int climbStairs(int n) { 2478 | int[] dp = new int[n + 1]; 2479 | dp[0] = 1; 2480 | dp[1] = 1; 2481 | for (int i = 2; i <= n; i ++) { 2482 | dp[i] = dp[i - 1] + dp[i - 2]; 2483 | } 2484 | return dp[n]; 2485 | } 2486 | } 2487 | 2488 | ``` 2489 | 2490 | ------ 2491 | 2492 | #### [【动态规划】triangle(三角形最小路径和)](https://leetcode-cn.com/problems/triangle/) 2493 | 2494 | 给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。 2495 | 2496 | 例如,给定三角形: 2497 | 2498 | ``` 2499 | [ 2500 | [2], 2501 | [3,4], 2502 | [6,5,7], 2503 | [4,1,8,3] 2504 | ] 2505 | 2506 | ``` 2507 | 2508 | 自顶向下的最小路径和为 `11`(即,**2** + **3** + **5** + **1** = 11)。 2509 | 2510 | **说明:** 2511 | 2512 | 如果你可以只使用 *O*(*n*) 的额外空间(*n* 为三角形的总行数)来解决这个问题,那么你的算法会很加分。 2513 | 2514 | **代码** 2515 | 2516 | `二维数组` 2517 | 2518 | ```java 2519 | class Solution { 2520 | public int minimumTotal(List> triangle) { 2521 | //dp[i][j] = min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle.get(i).get(j) 2522 | int m = triangle.size(); 2523 | int[][] dp = new int[m][triangle.get(m - 1).size()]; 2524 | for (int i = 0; i < triangle.get(m - 1).size(); i ++) { 2525 | dp[m - 1][i] = triangle.get(m - 1).get(i); 2526 | } 2527 | 2528 | for (int i = m - 2; i >= 0; i --) { 2529 | for (int j = 0; j < triangle.get(i).size(); j ++) { 2530 | dp[i][j] = Math.min(dp[i + 1][j], 2531 | dp[i + 1][j + 1]) + triangle.get(i).get(j); 2532 | } 2533 | } 2534 | return dp[0][0]; 2535 | } 2536 | } 2537 | 2538 | ``` 2539 | 2540 | `一维数组` 2541 | 2542 | ```java 2543 | class Solution { 2544 | public int minimumTotal(List> triangle) { 2545 | //dp[j] = Math.min(dp[j], dp[j + 1]) + triangle.get(i).get(j); 2546 | int m = triangle.size(); 2547 | int n = triangle.get(m - 1).size(); 2548 | int[] dp = new int[n]; 2549 | for (int i = 0; i < n; i ++) { 2550 | dp[i] = triangle.get(m - 1).get(i); 2551 | } 2552 | 2553 | for (int i = m - 2; i >= 0; i --) { 2554 | for (int j = 0; j < triangle.get(i).size(); j ++) { 2555 | dp[j] = Math.min(dp[j], dp[j + 1]) + triangle.get(i).get(j); 2556 | } 2557 | } 2558 | return dp[0]; 2559 | } 2560 | } 2561 | 2562 | ``` 2563 | 2564 | ------ 2565 | 2566 | #### [【动态规划】maximum-product-subarray(乘积最大子序列)](https://leetcode-cn.com/problems/maximum-product-subarray/) 2567 | 2568 | 给定一个整数数组 `nums` ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。 2569 | 2570 | **示例 1:** 2571 | 2572 | ``` 2573 | 输入: [2,3,-2,4] 2574 | 输出: 6 2575 | 解释: 子数组 [2,3] 有最大乘积 6。 2576 | 2577 | ``` 2578 | 2579 | **示例 2:** 2580 | 2581 | ``` 2582 | 输入: [-2,0,-1] 2583 | 输出: 0 2584 | 解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。 2585 | 2586 | ``` 2587 | 2588 | **代码** 2589 | 2590 | `解法1` 2591 | 2592 | ```java 2593 | class Solution { 2594 | public int maxProduct(int[] nums) { 2595 | int[][] dp = new int[2][2]; 2596 | dp[0][1] = nums[0];//最小值 2597 | dp[0][0] = nums[0];//最大值 2598 | int res = nums[0]; 2599 | for (int i = 1; i < nums.length; i ++) { 2600 | int x = i % 2; 2601 | int y = (i - 1) % 2; 2602 | dp[x][0] = Math.max(Math.max(dp[y][0] * nums[i], dp[y][1] * nums[i]), nums[i]); 2603 | dp[x][1] = Math.min(Math.min(dp[y][0] * nums[i], dp[y][1] * nums[i]), nums[i]); 2604 | res = Math.max(res, dp[x][0]); 2605 | } 2606 | return res; 2607 | } 2608 | } 2609 | 2610 | ``` 2611 | 2612 | `解法2` 2613 | 2614 | ```java 2615 | class Solution { 2616 | public int maxProduct(int[] nums) { 2617 | int curMin = nums[0];//最小值 2618 | int curMax = nums[0];//最大值 2619 | int res = nums[0]; 2620 | for (int i = 1; i < nums.length; i ++) { 2621 | int tmpMax = curMax * nums[i]; 2622 | int tmpMin = curMin * nums[i]; 2623 | curMin = Math.min(Math.min(tmpMax, tmpMin), nums[i]); 2624 | curMax = Math.max(Math.max(tmpMax, tmpMin), nums[i]); 2625 | res = Math.max(res, curMax); 2626 | } 2627 | return res; 2628 | } 2629 | } 2630 | 2631 | ``` 2632 | 2633 | ------ 2634 | 2635 | #### [【动态规划】买卖股票的最佳时机](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/) 2636 | 2637 | 给定一个数组,它的第 *i* 个元素是一支给定股票第 *i* 天的价格。 2638 | 2639 | 如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。 2640 | 2641 | 注意你不能在买入股票前卖出股票。 2642 | 2643 | **示例 1:** 2644 | 2645 | ``` 2646 | 输入: [7,1,5,3,6,4] 2647 | 输出: 5 2648 | 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 2649 | 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。 2650 | 2651 | ``` 2652 | 2653 | **示例 2:** 2654 | 2655 | ``` 2656 | 输入: [7,6,4,3,1] 2657 | 输出: 0 2658 | 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 2659 | 2660 | ``` 2661 | 2662 | **代码** 2663 | 2664 | `循环` 2665 | 2666 | ```java 2667 | class Solution { 2668 | public int maxProfit(int[] prices) { 2669 | int tmp = 0; 2670 | int max = 0; 2671 | for (int i = 1; i < prices.length; i ++) { 2672 | int diff = prices[i] - prices[i - 1]; 2673 | //连续差值和 如 1 5 3 6 -- (6 - 1) = (5 - 1) + (3 - 5) + (6 - 3) 2674 | tmp = tmp + diff > 0 ? tmp + diff : 0; 2675 | max = Math.max(max, tmp); 2676 | } 2677 | return max; 2678 | } 2679 | } 2680 | 2681 | ``` 2682 | 2683 | `动态规划` 2684 | 2685 | ```java 2686 | class Solution { 2687 | public int maxProfit(int[] prices) { 2688 | if (prices == null || prices.length == 0) return 0; 2689 | int res = 0; 2690 | int[][] profit = new int[prices.length][3]; 2691 | profit[0][0] = 0;//没有买入股票 2692 | profit[0][1] = -prices[0];//买入股票没有卖出 2693 | profit[0][2] = 0;//之前买过股票,现在卖了 2694 | 2695 | for (int i = 1; i < prices.length; i ++) { 2696 | profit[i][0] = profit[i - 1][0]; 2697 | profit[i][1] = Math.max(profit[i - 1][1], profit[i - 1][0] - prices[i]);//前一天买入股票,或者之前没有股票今天刚买 2698 | profit[i][2] = profit[i - 1][1] + prices[i]; 2699 | res = Math.max(Math.max(res, profit[i][0]), Math.max(profit[i][1], profit[i][2])); 2700 | } 2701 | return res; 2702 | } 2703 | } 2704 | 2705 | ``` 2706 | 2707 | ------ 2708 | 2709 | #### [【动态规划】买卖股票的最佳时机 III](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/) 2710 | 2711 | 给定一个数组,它的第 *i* 个元素是一支给定的股票在第 *i* 天的价格。 2712 | 2713 | 设计一个算法来计算你所能获取的最大利润。你最多可以完成 *两笔* 交易。 2714 | 2715 | **注意:** 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 2716 | 2717 | **示例 1:** 2718 | 2719 | ``` 2720 | 输入: [3,3,5,0,0,3,1,4] 2721 | 输出: 6 2722 | 解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。 2723 | 随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。 2724 | 2725 | ``` 2726 | 2727 | **示例 2:** 2728 | 2729 | ``` 2730 | 输入: [1,2,3,4,5] 2731 | 输出: 4 2732 | 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 2733 | 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 2734 | 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 2735 | 2736 | ``` 2737 | 2738 | **示例 3:** 2739 | 2740 | ``` 2741 | 输入: [7,6,4,3,1] 2742 | 输出: 0 2743 | 解释: 在这个情况下, 没有交易完成, 所以最大利润为 0。 2744 | 2745 | ``` 2746 | 2747 | **代码** 2748 | 2749 | ```java 2750 | class Solution { 2751 | public int maxProfit(int[] prices) { 2752 | if (prices == null || prices.length == 0) return 0; 2753 | int res = 0; 2754 | //第一位代表天数,第二位代表交易次数,第三位0代表不持有股票,1代表持有股票 2755 | int[][][] profit = new int[prices.length][3][2]; 2756 | 2757 | for (int i = 0; i < 3; i ++) { 2758 | //初始化第1天的数据 2759 | profit[0][i][0] = 0; 2760 | profit[0][i][1] = -prices[0]; 2761 | } 2762 | 2763 | for (int i = 1; i < prices.length; i ++) { 2764 | for (int j = 0; j < 3; j ++) { 2765 | //不持有股票分两种情况 2766 | //1:前一天持有,今天卖了 profit[i - 1][j - 1][1] + prices[i] 如果操作数为0则不存在本情况 2767 | //2: 前一天就不持有,今天无操作 profit[i - 1][j][0] 2768 | profit[i][j][0] = j != 0 ? Math.max(profit[i - 1][j][0], profit[i - 1][j - 1][1] + prices[i]) : profit[i - 1][j][0]; 2769 | //持有股票分两种情况 2770 | //1: 前一天持有,今天无操作 profit[i - 1][j][1] 2771 | //2:前一天不持有,今天买入 profit[i - 1][j][0] - prices[i] 2772 | profit[i][j][1] = Math.max(profit[i - 1][j][1], profit[i - 1][j][0] - prices[i]); 2773 | res = Math.max(res, profit[i][j][0]); 2774 | } 2775 | 2776 | } 2777 | return res; 2778 | } 2779 | } 2780 | 2781 | ``` 2782 | 2783 | ------ 2784 | 2785 | #### [【动态规划】longest-increasing-subsequence(最长上升子序列)](https://leetcode-cn.com/problems/longest-increasing-subsequence/) 2786 | 2787 | 给定一个无序的整数数组,找到其中最长上升子序列的长度。 2788 | 2789 | **示例:** 2790 | 2791 | ``` 2792 | 输入: [10,9,2,5,3,7,101,18] 2793 | 输出: 4 2794 | 解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。 2795 | 2796 | ``` 2797 | 2798 | **说明:** 2799 | 2800 | - 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。 2801 | - 你算法的时间复杂度应该为 O(*n2*) 。 2802 | 2803 | **进阶:** 你能将算法的时间复杂度降低到 O(*n* log *n*) 吗? 2804 | 2805 | **代码** 2806 | 2807 | `动态规划` 2808 | 2809 | ```java 2810 | class Solution { 2811 | public int lengthOfLIS(int[] nums) { 2812 | if (nums == null || nums.length == 0) return 0; 2813 | int n = nums.length; 2814 | int[] dp = new int[n]; 2815 | Arrays.fill(dp, 1); 2816 | int res = 1; 2817 | for (int i = 1; i < n; i ++) { 2818 | for (int j = 0; j < i; j ++) { 2819 | if (nums[i] > nums[j]) { 2820 | dp[i] = Math.max(dp[j] + 1, dp[i]); 2821 | } 2822 | } 2823 | res = Math.max(res, dp[i]); 2824 | } 2825 | return res; 2826 | } 2827 | } 2828 | 2829 | ``` 2830 | 2831 | `二分` 2832 | 2833 | ```java 2834 | class Solution { 2835 | public int lengthOfLIS(int[] nums) { 2836 | if (nums == null || nums.length == 0) return 0; 2837 | List lis = new ArrayList<>(); 2838 | lis.add(nums[0]); 2839 | for (int i = 1; i < nums.length; i ++) { 2840 | int left = 0; 2841 | int right = lis.size() - 1; 2842 | int it = -1; 2843 | while (left <= right) { 2844 | int mid = (left + right) / 2; 2845 | if (lis.get(mid) >= nums[i]) { 2846 | it = mid; 2847 | right = mid - 1; 2848 | } else { 2849 | left = mid + 1; 2850 | } 2851 | } 2852 | if (it == -1) { 2853 | lis.add(nums[i]); 2854 | } else { 2855 | lis.set(it, nums[i]); 2856 | } 2857 | } 2858 | return lis.size(); 2859 | } 2860 | } 2861 | 2862 | ``` 2863 | 2864 | ------ 2865 | 2866 | #### [【动态规划】coin-change(零钱兑换)](https://leetcode-cn.com/problems/coin-change/) 2867 | 2868 | 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 `-1`。 2869 | 2870 | **示例 1:** 2871 | 2872 | ``` 2873 | 输入: coins = [1, 2, 5], amount = 11 2874 | 输出: 3 2875 | 解释: 11 = 5 + 5 + 1 2876 | 2877 | ``` 2878 | 2879 | **示例 2:** 2880 | 2881 | ``` 2882 | 输入: coins = [2], amount = 3 2883 | 输出: -1 2884 | 2885 | ``` 2886 | 2887 | **说明**: 2888 | 你可以认为每种硬币的数量是无限的。 2889 | 2890 | **代码** 2891 | 2892 | ```java 2893 | class Solution { 2894 | public int coinChange(int[] coins, int amount) { 2895 | int max = amount + 1; 2896 | int[] dp = new int[max]; 2897 | Arrays.fill(dp, max); 2898 | dp[0] = 0; 2899 | for (int i = 1; i < max; i ++) { 2900 | for (int j = 0; j < coins.length; j ++) { 2901 | if (coins[j] <= i) { 2902 | dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1); 2903 | } 2904 | } 2905 | } 2906 | return dp[amount] > amount ? -1 : dp[amount]; 2907 | } 2908 | } 2909 | 2910 | ``` 2911 | 2912 | ------ 2913 | 2914 | #### [【动态规划】edit-distance(编辑距离)](https://leetcode-cn.com/problems/edit-distance/) 2915 | 2916 | 给定两个单词 *word1* 和 *word2*,计算出将 *word1* 转换成 *word2* 所使用的最少操作数 。 2917 | 2918 | 你可以对一个单词进行如下三种操作: 2919 | 2920 | 1. 插入一个字符 2921 | 2. 删除一个字符 2922 | 3. 替换一个字符 2923 | 2924 | **示例 1:** 2925 | 2926 | ``` 2927 | 输入: word1 = "horse", word2 = "ros" 2928 | 输出: 3 2929 | 解释: 2930 | horse -> rorse (将 'h' 替换为 'r') 2931 | rorse -> rose (删除 'r') 2932 | rose -> ros (删除 'e') 2933 | 2934 | ``` 2935 | 2936 | **示例 2:** 2937 | 2938 | ``` 2939 | 输入: word1 = "intention", word2 = "execution" 2940 | 输出: 5 2941 | 解释: 2942 | intention -> inention (删除 't') 2943 | inention -> enention (将 'i' 替换为 'e') 2944 | enention -> exention (将 'n' 替换为 'x') 2945 | exention -> exection (将 'n' 替换为 'c') 2946 | exection -> execution (插入 'u') 2947 | 2948 | ``` 2949 | 2950 | **代码** 2951 | 2952 | ```java 2953 | class Solution { 2954 | public int minDistance(String word1, String word2) { 2955 | //dp[i][j] word1的前i个字符替换到word2的前j个字符最少需要的步数 2956 | int m = word1.length(); 2957 | int n = word2.length(); 2958 | int[][] dp = new int[m + 1][n + 1]; 2959 | 2960 | for (int i = 0; i < m + 1; i ++) dp[i][0] = i;//word2长度为0 逐个删除 2961 | for (int i = 0; i < n + 1; i ++) dp[0][i] = i;//word1长度为0 逐个添加 2962 | 2963 | for (int i = 1; i < m + 1; i ++) { 2964 | for (int j = 1; j < n + 1; j ++) { 2965 | if (word1.charAt(i - 1) == word2.charAt(j - 1)) { 2966 | dp[i][j] = dp[i - 1][j - 1]; 2967 | } else { 2968 | //dp[i - 1][j]代表word1插入一个字符 2969 | //dp[i][j - 1]代表word1删除一个字符 2970 | //dp[i - 1][j - 1]代表word1替换一个字符 2971 | dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1; 2972 | } 2973 | } 2974 | } 2975 | return dp[m][n]; 2976 | } 2977 | } 2978 | 2979 | ``` 2980 | 2981 | ------ 2982 | 2983 | #### 【并查集】代码模板 2984 | 2985 | ```java 2986 | public class UnionFind { 2987 | 2988 | /** 2989 | * 根数组 i = roots[j]代表j的根是i 2990 | * 规定根相同的元素属于同一集合 2991 | */ 2992 | private int[] roots; 2993 | /** 2994 | * 并查集的深度(从0开始) 2995 | */ 2996 | private int[] rank; 2997 | 2998 | /** 2999 | * 初始化根数组,每个元素的根为自己 3000 | * @param N 3001 | */ 3002 | public UnionFind(int N) { 3003 | roots = new int[N]; 3004 | for (int i = 0; i < N; i++) { 3005 | roots[i] = i; 3006 | } 3007 | } 3008 | 3009 | /** 3010 | * 判断两个元素是否属于同一个集合,即根元素是否相同 3011 | * @param p 3012 | * @param q 3013 | * @return 3014 | */ 3015 | public boolean connected(int p, int q) { 3016 | return findRoot(p) == findRoot(q); 3017 | } 3018 | 3019 | /** 3020 | * 将两个集合合并为1个集合 3021 | * @param p 3022 | * @param q 3023 | */ 3024 | public void union(int p, int q) { 3025 | int qroot = findRoot(q); 3026 | int proot = findRoot(p); 3027 | if (qroot != proot) { 3028 | //优化并查集,减少并查集的深度 3029 | if (rank[proot] > rank[qroot]) { 3030 | roots[qroot] = proot; 3031 | } else if (rank[proot] < rank[qroot]) { 3032 | roots[proot] = qroot; 3033 | } else { 3034 | roots[proot] = qroot; 3035 | rank[qroot] += 1; 3036 | } 3037 | } 3038 | } 3039 | 3040 | /** 3041 | * 寻找指定元素的根元素 3042 | * @param i 3043 | * @return 3044 | */ 3045 | private int findRoot(int i) { 3046 | int root = i; 3047 | while (root != roots[root]) { 3048 | root = roots[root]; 3049 | } 3050 | //优化并查集,将元素直接指向根元素,压缩路径 3051 | while (i != roots[i]) { 3052 | int tmp = roots[i]; 3053 | roots[i] = root; 3054 | i = tmp; 3055 | } 3056 | return root; 3057 | } 3058 | } 3059 | 3060 | 3061 | ``` 3062 | 3063 | ------ 3064 | 3065 | #### [【并查集】number-of-islands(岛屿数量)](https://leetcode-cn.com/problems/number-of-islands/) 3066 | 3067 | 给定一个由 `'1'`(陆地)和 `'0'`(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。 3068 | 3069 | **示例 1:** 3070 | 3071 | ``` 3072 | 输入: 3073 | 11110 3074 | 11010 3075 | 11000 3076 | 00000 3077 | 3078 | 输出: 1 3079 | 3080 | ``` 3081 | 3082 | **示例 2:** 3083 | 3084 | ``` 3085 | 输入: 3086 | 11000 3087 | 11000 3088 | 00100 3089 | 00011 3090 | 3091 | 输出: 3 3092 | 3093 | ``` 3094 | 3095 | **代码** 3096 | 3097 | `DFS` 3098 | 3099 | ```java 3100 | class Solution { 3101 | int count = 0; 3102 | int[] dx = {-1, 1, 0, 0}; 3103 | int[] dy = {0, 0, -1, 1}; 3104 | public int numIslands(char[][] grid) { 3105 | for (int i = 0; i < grid.length; i ++) { 3106 | for (int j = 0; j < grid[0].length; j ++) { 3107 | if (grid[i][j] == '1') { 3108 | count ++; 3109 | dfs(grid, i, j); 3110 | } 3111 | } 3112 | } 3113 | return count; 3114 | } 3115 | 3116 | private void dfs(char[][] grid, int i, int j) { 3117 | if (valid(grid, i, j)) { 3118 | grid[i][j] = '0'; 3119 | for (int k = 0; k < 4; k ++) { 3120 | dfs(grid, i + dx[k], j + dy[k]); 3121 | } 3122 | } 3123 | } 3124 | 3125 | private boolean valid(char[][] grid, int i, int j) { 3126 | return i >= 0 && i < grid.length && j >=0 && j < grid[0].length && grid[i][j] == '1'; 3127 | } 3128 | } 3129 | 3130 | ``` 3131 | 3132 | `并查集` 3133 | 3134 | ```java 3135 | class Solution { 3136 | 3137 | public int numIslands(char[][] grid) { 3138 | if (grid == null || grid.length == 0) return 0; 3139 | UnionFind uf = new UnionFind(grid); 3140 | int[] dx = {-1, 1, 0, 0}; 3141 | int[] dy = {0, 0, -1, 1}; 3142 | int m = grid.length; 3143 | int n = grid[0].length; 3144 | for (int i = 0; i < m; i ++) { 3145 | for (int j = 0; j < n; j ++) { 3146 | if (grid[i][j] == '0') { 3147 | continue; 3148 | } 3149 | for (int k = 0; k < 4; k ++) { 3150 | int a = i + dx[k]; 3151 | int b = j + dy[k]; 3152 | if (valid(grid, a, b)) { 3153 | uf.union(i * n + j, a * n + b); 3154 | } 3155 | } 3156 | } 3157 | } 3158 | return uf.getCount(); 3159 | } 3160 | 3161 | private boolean valid(char[][] grid, int i, int j) { 3162 | return i >= 0 && i < grid.length && j >=0 && j < grid[0].length && grid[i][j] == '1'; 3163 | } 3164 | } 3165 | 3166 | class UnionFind { 3167 | private int count; 3168 | private int[] roots; 3169 | private int[] rank; 3170 | 3171 | public UnionFind(char[][] grid) { 3172 | int m = grid.length; 3173 | int n = grid[0].length; 3174 | count = 0; 3175 | roots = new int[m * n]; 3176 | rank = new int[m * n]; 3177 | Arrays.fill(roots, -1); 3178 | for (int i = 0; i < m; i ++) { 3179 | for (int j = 0; j < n; j ++) { 3180 | if (grid[i][j] == '1') { 3181 | roots[i * n + j] = i * n + j; 3182 | count += 1; 3183 | } 3184 | } 3185 | } 3186 | } 3187 | 3188 | public int getCount() { 3189 | return this.count; 3190 | } 3191 | 3192 | public void union(int p, int q) { 3193 | int proot = find(p); 3194 | int qroot = find(q); 3195 | if (qroot != proot) { 3196 | if (rank[proot] > rank[qroot]) { 3197 | roots[qroot] = proot; 3198 | } else if (rank[proot] < rank[qroot]) { 3199 | roots[proot] = qroot; 3200 | } else { 3201 | roots[proot] = qroot; 3202 | rank[qroot] += 1; 3203 | } 3204 | count -= 1; 3205 | } 3206 | } 3207 | 3208 | private int find(int i) { 3209 | int root = i; 3210 | while (root != roots[root]) { 3211 | root = roots[root]; 3212 | } 3213 | while (i != roots[i]) { 3214 | int tmp = roots[i]; 3215 | roots[i] = root; 3216 | i = tmp; 3217 | } 3218 | return root; 3219 | } 3220 | } 3221 | 3222 | ``` 3223 | 3224 | ------ 3225 | 3226 | #### [【并查集】friend-circles(朋友圈)](https://leetcode-cn.com/problems/friend-circles/) 3227 | 3228 | 班上有 **N** 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。 3229 | 3230 | 给定一个 **N \* N** 的矩阵 **M**,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生**互为**朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。 3231 | 3232 | **示例 1:** 3233 | 3234 | ``` 3235 | 输入: 3236 | [[1,1,0], 3237 | [1,1,0], 3238 | [0,0,1]] 3239 | 输出: 2 3240 | 说明:已知学生0和学生1互为朋友,他们在一个朋友圈。 3241 | 第2个学生自己在一个朋友圈。所以返回2。 3242 | 3243 | ``` 3244 | 3245 | **示例 2:** 3246 | 3247 | ``` 3248 | 输入: 3249 | [[1,1,0], 3250 | [1,1,1], 3251 | [0,1,1]] 3252 | 输出: 1 3253 | 说明:已知学生0和学生1互为朋友,学生1和学生2互为朋友,所以学生0和学生2也是朋友,所以他们三个在一个朋友圈,返回1。 3254 | 3255 | ``` 3256 | 3257 | **注意:** 3258 | 3259 | 1. N 在[1,200]的范围内。 3260 | 2. 对于所有学生,有`M[i][i] = 1`。 3261 | 3. 如果有`M[i][j] = 1`,则有`M[j][i]= 1`。 3262 | 3263 | **代码** 3264 | 3265 | `DFS` 3266 | 3267 | ```java 3268 | class Solution { 3269 | int count = 0; 3270 | public int findCircleNum(int[][] M) { 3271 | int[] mark = new int[M.length];//用于存储朋友圈路径 3272 | for (int i = 0; i < M.length; i ++) { 3273 | if (mark[i] == 0) { 3274 | //0代表i没有加入到一个朋友圈,从该点开始,dfs找出他的所有朋友 3275 | count ++; 3276 | dfs(M, mark, i);//执行一次,构建一条朋友圈 3277 | } 3278 | } 3279 | return count; 3280 | } 3281 | private void dfs(int[][] M, int[] mark, int i) { 3282 | mark[i] = 1;//将i放到朋友圈 3283 | for (int j = 0; j < M[0].length; j ++) { 3284 | if (M[i][j] == 1 && mark[j] == 0) { 3285 | //如果i和j是朋友,但是j没有加入该朋友圈,dfs去构建朋友圈 3286 | dfs(M, mark, j); 3287 | } 3288 | } 3289 | } 3290 | } 3291 | 3292 | ``` 3293 | 3294 | `并查集` 3295 | 3296 | ```java 3297 | class Solution { 3298 | public int findCircleNum(int[][] M) { 3299 | UnionFind uf = new UnionFind(M); 3300 | int m = M.length; 3301 | int n = M[0].length; 3302 | for (int i = 0; i < m; i ++) { 3303 | for (int j = 0; j < n; j ++) { 3304 | if (M[i][j] == 1) { 3305 | //这里太暴力了,暂时没有优化,速度那是慢的一匹 3306 | //上面的dfs是优化过的,换了个思路,当然也可以按照下面暴力求解 3307 | for (int k = 0; k < m; k ++) { 3308 | if (M[k][j] == 1) { 3309 | uf.union(i * n + j, k * n + j); 3310 | } 3311 | } 3312 | for (int k = 0; k < n; k ++) { 3313 | if (M[i][k] == 1) { 3314 | uf.union(i * n + j, i * n + k); 3315 | } 3316 | } 3317 | } 3318 | } 3319 | } 3320 | return uf.getCount(); 3321 | } 3322 | } 3323 | 3324 | class UnionFind { 3325 | private int count; 3326 | private int[] roots; 3327 | private int[] rank; 3328 | 3329 | public UnionFind(int[][] M) { 3330 | int m = M.length; 3331 | int n = M[0].length; 3332 | count = 0; 3333 | roots = new int[m * n]; 3334 | rank = new int[m * n]; 3335 | Arrays.fill(roots, -1); 3336 | for (int i = 0; i < m; i ++) { 3337 | for (int j = 0; j < n; j ++) { 3338 | if (M[i][j] == 1) { 3339 | roots[i * n + j] = i * n + j; 3340 | count += 1; 3341 | } 3342 | } 3343 | } 3344 | } 3345 | 3346 | public int getCount() { 3347 | return this.count; 3348 | } 3349 | 3350 | public void union(int p, int q) { 3351 | int proot = find(p); 3352 | int qroot = find(q); 3353 | if (qroot != proot) { 3354 | if (rank[proot] > rank[qroot]) { 3355 | roots[qroot] = proot; 3356 | } else if (rank[proot] < rank[qroot]) { 3357 | roots[proot] = qroot; 3358 | } else { 3359 | roots[proot] = qroot; 3360 | rank[qroot] += 1; 3361 | } 3362 | count -= 1; 3363 | } 3364 | } 3365 | 3366 | private int find(int i) { 3367 | int root = i; 3368 | while (root != roots[root]) { 3369 | root = roots[root]; 3370 | } 3371 | while (i != roots[i]) { 3372 | int tmp = roots[i]; 3373 | roots[i] = root; 3374 | i = tmp; 3375 | } 3376 | return root; 3377 | } 3378 | } 3379 | 3380 | ``` 3381 | 3382 | ------ 3383 | 3384 | #### [【LRU】lru-cache(LRU缓存机制)](https://leetcode-cn.com/problems/lru-cache/) 3385 | 3386 | 运用你所掌握的数据结构,设计和实现一个 [LRU (最近最少使用) 缓存机制](https://baike.baidu.com/item/LRU)。它应该支持以下操作: 获取数据 `get` 和 写入数据 `put` 。 3387 | 3388 | 获取数据 `get(key)` - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。 3389 | 写入数据 `put(key, value)` - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。 3390 | 3391 | **进阶:** 3392 | 3393 | 你是否可以在 **O(1)** 时间复杂度内完成这两种操作? 3394 | 3395 | **示例:** 3396 | 3397 | ``` 3398 | LRUCache cache = new LRUCache( 2 /* 缓存容量 */ ); 3399 | 3400 | cache.put(1, 1); 3401 | cache.put(2, 2); 3402 | cache.get(1); // 返回 1 3403 | cache.put(3, 3); // 该操作会使得密钥 2 作废 3404 | cache.get(2); // 返回 -1 (未找到) 3405 | cache.put(4, 4); // 该操作会使得密钥 1 作废 3406 | cache.get(1); // 返回 -1 (未找到) 3407 | cache.get(3); // 返回 3 3408 | cache.get(4); // 返回 4 3409 | 3410 | ``` 3411 | 3412 | **代码** 3413 | 3414 | ```java 3415 | class LRUCache { 3416 | 3417 | private LinkedHashMap map; 3418 | private int remain; 3419 | public LRUCache(int capacity) { 3420 | this.map = new LinkedHashMap<>(capacity); 3421 | this.remain = capacity; 3422 | } 3423 | 3424 | public int get(int key) { 3425 | if (!map.containsKey(key)) return -1; 3426 | int v = map.remove(key); 3427 | map.put(key, v); 3428 | return v; 3429 | } 3430 | 3431 | public void put(int key, int value) { 3432 | if (map.containsKey(key)) { 3433 | map.remove(key); 3434 | } else { 3435 | if (this.remain > 0) this.remain --; 3436 | else removeOldestEntry(); 3437 | } 3438 | map.put(key, value); 3439 | } 3440 | 3441 | private void removeOldestEntry() { 3442 | Iterator it = this.map.keySet().iterator(); 3443 | int oldestKey = -1; 3444 | if (it.hasNext()) { 3445 | oldestKey = it.next(); 3446 | } 3447 | map.remove(oldestKey); 3448 | } 3449 | } 3450 | 3451 | /** 3452 | * Your LRUCache object will be instantiated and called as such: 3453 | * LRUCache obj = new LRUCache(capacity); 3454 | * int param_1 = obj.get(key); 3455 | * obj.put(key,value); 3456 | */ 3457 | 3458 | ``` 3459 | 3460 | ------ 3461 | 3462 | --------------------------------------------------------------------------------