哈希

两数之和

  • 方法一

    • 最容易想到的方法就是枚举 x,然后去从数组中去遍历去查找是否存在 target - x

    • class Solution {
          public int[] twoSum(int[] nums, int target) {
              Map<Integer,Integer> map = new HashMap<>();
      
              for(int i = 0; i < nums.length; i ++) {
                  if(map.containsKey(target - nums[i])) {
                      return new int[] {map.get(target - nums[i]), i};
                  }
                  map.put(nums[i], i);
              }
              return new int[] {-1, -1};
          }
      }
  • 方法二

    • 通过遍历数组来存入Map中元素所对应的位置,通过使用哈希表来降低在查找target - x的时间复杂度,从O(n)降到O(1)

    • class Solution {
          public int[] twoSum(int[] nums, int target) {
              Map<Integer,Integer> map = new HashMap<>();
      
              for(int i = 0; i < nums.length; i ++) {
                  if(map.containsKey(target - nums[i])) {
                      return new int[] {map.get(target - nums[i]), i};
                  }
                  map.put(nums[i], i);
              }
              return new int[] {-1, -1};
          }
      }

字母异位词分组

  • 方法一

    • 因为作为字母异位词的两个单词的所含有的字母是一样的,那这样的话它们的排序结果也是一样的,所以我们可以使用这个排序结果作为哈希表的键

    • class Solution {
          public List<List<String>> groupAnagrams(String[] strs) {
              Map<String, List<String>> map = new HashMap<>();
      
              for(String str: strs) {
                  char[] ch = str.toCharArray();
                  Arrays.sort(ch);
                  String ss = Arrays.toString(ch);
      
                  if(!map.containsKey(ss)) {
                      map.put(ss, new ArrayList());
                  }
                  map.get(ss).add(str);
              }
              return new ArrayList(map.values());
          }
      }

最长连续序列

  • 方法一

    • 这个题简单来说就是去查找每个数的前一个数是否存在,如果存在,说明该数不是最开始的那个数,直接跳过。如果不存在,那就直接去往后延伸,找到最大可以延伸即可。

    • class Solution {
          public int longestConsecutive(int[] nums) {
              Set<Integer> set = new HashSet<>();
      
              for(int i = 0; i < nums.length; i ++) {
                  set.add(nums[i]);
              }
      
              int longestSteck = 0, current = 0;
              for(int i: set) {
                  if(!set.contains(i - 1)) {
                      int steck = 1;
                      current = i;
                      while(set.contains(current + 1)) {
                          current ++;
                          steck ++;
                      }
                      longestSteck = Math.max(longestSteck, steck);
                  }
              }
              return longestSteck;
          }
      }

双指针

移动零

  • 方法一

    • 应题目要求,必须在不复制数组的情况下原地对数组进行操作,这样的话,我们可以初始化一个指针,这个指针是指向非零元素存放的位置,然后我们遍历数组,遇到非零元素就存放到指针指向的位置,之后指针加一,最后将指针以后的位置填充零即可,只需要遍历两次数组,时间复杂度是O(1)

    • class Solution {
          public void moveZeroes(int[] nums) {
              int nonZeroIndex = 0;
      
              for(int i = 0; i < nums.length; i ++) {
                  if(nums[i] != 0) {
                      nums[nonZeroIndex ++] = nums[i]; 
                  }
              }
      
              for(int i = nonZeroIndex; i < nums.length; i ++) {
                  nums[i] = 0;
              }
          }
      }
  • 方法二

    • 直接交换,通过双指针,一个指针指向的是下一个非零元素应该放的位置,然后另一个指针去遍历数组,遇到非零元素就和前一个指针交换,这样就可以保证不会该换非零元素的相对顺序了

    • class Solution {
          public void moveZeroes(int[] nums) {
              int nonZeroIndex = 0;
      
              for(int i = 0; i < nums.length; i ++) {
                  if(nums[i] != 0) {
                      int temp = nums[nonZeroIndex];
                      nums[nonZeroIndex] = nums[i];
                      nums[i] = temp;
                      nonZeroIndex ++;    
                  }
              }
          }
      }

盛最多水的容器

  • 方法一

    • 我们可以通过双指针来计算最大容量,初始化两个指针,一个在数组开头,一个在数组末尾,不断向中间逼近,通过移动较短柱子一边的指针以求更大的容量

    • class Solution {
          public int maxArea(int[] height) {
              int n = height.length;
              int left = 0, right = n - 1;
              int maxx = 0;
              while(left < right) {
                  int area = Math.min(height[left], height[right]) * (right - left);
                  maxx = Math.max(maxx, area);
      
                  if(height[left] < height[right]) left ++;
                  else right --;
              }
              return maxx;
          }
      }

三数之和

  • 方法一

    • 应题目要求,需要满足i < j < k 并且答案中不能有重复的三元组,比如说[-1, 0, 1][-1, 1, 0] 属于一个三元组,先对数组进行排序,之后对i进行枚举,需要预留j和k,所以枚举的范围是[0, n-3] ,当遇到nums[i]nums[i-1]相等就跳过,因为题目要求不能有重复的三元组,之后就是从剩下的里面去选择两个数,这里就和两数之和Ⅱ 这个题类似了,然后我们就去计算三数之和,当total > 0,我们去调整右边的指针,total < 0去调整左边的指针,不断重复直到找到满足条件的为止,当然,nums[left]nums[right]也会有重复的问题,也需要跳过。

    • class Solution {
          public List<List<Integer>> threeSum(int[] nums) {
              int n = nums.length;
              List<List<Integer>> result = new ArrayList();
              Arrays.sort(nums);
      
              for(int i = 0; i < n - 2; i ++) {
                  if(i > 0 && nums[i] == nums[i - 1])continue;
                  
                  if(nums[i] + nums[i + 1] + nums[i + 2] > 0) break;
                  if(nums[i] + nums[n - 2] + nums[n - 1] < 0) continue;
                  int left = i + 1, right = n - 1;
                  while(left < right) {
                      int total = nums[i] + nums[left] + nums[right];
                      if(total == 0) {
                          result.add(Arrays.asList(nums[i], nums[left], nums[right]));
                          left ++;
                          while(left < right && nums[left] == nums[left - 1])left ++;
                          right --;
                          while(left < right && nums[right] == nums[right + 1])right --;
                      } else if(total < 0) {
                          left ++;
                      } else right --;
                  }
              }
              return result;
          }
      }

接雨水

  • 方法一

滑动窗口

无重复字符的最长字串

  • 方法一

    • 这个题我们依次递增地枚举子串的起始位置,那么子串的结束位置也是递增的,这样的话就可以求得最大字串长度

    • class Solution {
          public int lengthOfLongestSubstring(String s) {
              Set<Character> set = new HashSet<>();
              int n = s.length();
          
              int l = 0, r = 0, max = 0;
              while(r < n) {
                  if(!set.contains(s.charAt(r))) {
                      set.add(s.charAt(r++));
                      max = Math.max(max, r - l);
                  } else {
                      set.remove(s.charAt(l++));
                  }
              }
              return max;
          }
      }

找到字符串中所有字母异位词

  • 方法一

    • 这个题和上一个题类似,但是求异位词我们并不需要对字母的顺序关注,只要关注p串中各个字母出现了几次,保存下来,然后取遍历s串,不断向滑窗内添加字母,当滑窗大小与p串大小相等时比较内容,不满足滑动窗口即可

    • class Solution {
          public List<Integer> findAnagrams(String s, String p) {
              List<Integer> result = new ArrayList();
              int n = s.length(), m = p.length();
              int []pc = new int[27];
              int []sc = new int[27];
      
              for(int i = 0; i < m; i++) {
                  pc[p.charAt(i) - 'a'] ++;
              }
      
              int l = 0, r = 0;
              while(r < n) {
                  sc[s.charAt(r++) - 'a'] ++;
      
                  if(r - l == m) {
                      if(Arrays.equals(sc, pc)) {
                          result.add(l);
                      }
                      sc[s.charAt(l++) - 'a'] --;
                  }
              }
              return result;
          }
      }

字串

和为K的子数组

  • 方法一

    • 最容易想到的做法就是前缀和数组,通过计算前缀和数组 s 来储存从数组开始到当前索引的所有元素的和。然后,通过两层嵌套循环检查所有可能的子数组并计算其和是否等于 k

    • class Solution {
          public int subarraySum(int[] nums, int k) {
              int []s = new int[nums.length + 1];
      
              for(int i = 1; i < s.length; i ++) {
                  s[i] = s[i - 1] + nums[i - 1];
              }
      
              int cnt = 0;
              for(int i = 0; i < s.length; i ++) {
                  for(int j = i + 1; j < s.length; j ++) {
                      if(s[j] - s[i] == k) {
                          cnt ++;
                      }
                  }
              }
              return cnt;
          }
      }
  • 方法二

滑动窗口的最大值

最小覆盖子串

普通数组

最大子数组和

  • 方法一

    • 动态规划,我们只需要求每个位置的f(i),这里的f(i)是指以第i个数结尾的最大的连续子数组和。对于f(i),我们可以考虑nums[i]单独为一段还是加上f(i-1),这取决于它们的大小,这样状态转移方程就有了。f(i)=max{f(i−1)+nums[i],nums[i]}

    • class Solution {
          public int maxSubArray(int[] nums) {
              int maxx = nums[0], res = 0;
      
              for(int i = 0; i < nums.length; i++) {
                  res = Math.max(res + nums[i], nums[i]);
                  maxx = Math.max(res,maxx);
              }
              return maxx;
          }
      }

合并区间

  • 方法一

    • 对于这个题,我们只需要按照左边界进行排序,然后以此比较,只要左区间被上一个的右区间要小,说明可以覆盖,我们只要更新保存即可,比他大的话,那就单独成一个区间,再以它的右区间向后比较。

    • class Solution {
          public int[][] merge(int[][] intervals) {
              List<int[]> result = new ArrayList();
      
              Arrays.sort(intervals, (x, y) -> Integer.compare(x[0], y[0]));
              int start = intervals[0][0];
              int maxEnd = intervals[0][1];
      
              for(int i = 1; i < intervals.length; i ++) {
                  if(intervals[i][0] > maxEnd) {
                      result.add(new int[] {start, maxEnd});
                      start = intervals[i][0];
                      maxEnd = intervals[i][1];
                  } else {
                      maxEnd = Math.max(maxEnd, intervals[i][1]);
                  }
              }
              result.add(new int[] {start, maxEnd});
      
              return result.toArray(new int[result.size()][]);
          }
      }

轮转数组

  • 方法一

    • 这个题比较简单,简单说下思路,一共翻转三次,整体翻转、前部翻转、后部翻转

    • class Solution {
          public void rotate(int[] nums, int k) {
              int n = nums.length;
              k = k % n;
              reverse(nums, 0, n - 1);
              reverse(nums, 0, k - 1);
              reverse(nums, k, n - 1);
          }
      
          public void reverse(int[] nums, int i, int j) {
              while(i < j) {
                  int temp = nums[i];
                  nums[i] = nums[j];
                  nums[j] = temp;
                  i ++; j --;
              }
          }
      }

除自身以外数组的乘积

  • 方法一

    • 这个题我们只需要求对应位置前后的乘积列表,最后乘起来就可以了

    • class Solution {
          public int[] productExceptSelf(int[] nums) {
              int n = nums.length;
              int[] result = new int[n];
      
              int l[] = new int[n];
              int r[] = new int[n];
      
              l[0] = 1;
              for(int i = 1; i < n; i ++) {
                  l[i] = l[i - 1] * nums[i - 1];
              }
      
              r[n - 1] = 1;
              for(int i = n - 2; i >= 0; i --) {
                  r[i] = r[i + 1] * nums[i + 1];
              }
      
              for(int i = 0; i < n; i ++) {
                  result[i] = l[i] * r[i];
              }
              return result;
          }
      }

缺失的第一个正数

  • 方法一

矩阵

矩阵置零

  • 方法一

    • 该题不用说

    • class Solution {
          public void setZeroes(int[][] matrix) {
              int n = matrix.length;
              int m = matrix[0].length;
      
              boolean[] a = new boolean[n];
              boolean[] b = new boolean[m];
      
              for(int i = 0; i < n; i ++) {
                  for(int j = 0; j < m; j ++) {
                      if(matrix[i][j] == 0) {
                          a[i] = true;
                          b[j] = true;
                      }
                  }
              }
      
              for(int i = 0; i < n; i ++) {
                  for(int j = 0; j < m; j ++) {
                      if(a[i] || b[j]) {
                          matrix[i][j] = 0;
                      }
                  }
              }
          }
      }

螺旋矩阵

  • 方法一

    • 该题只需要按层模拟即可,可以将矩阵看成若干层,首先输出最外层的元素,其次输出次外层的元素,直到输出最内层的元素。

    • class Solution {
          public List<Integer> spiralOrder(int[][] matrix) {
              int n = matrix.length, m = matrix[0].length;
              
              List<Integer> result = new ArrayList();
              int top = 0, bottom = n - 1, left = 0, right = m - 1;
              while(top <= bottom && left <= right) {
                  for(int i = left; i <= right; i ++) {
                      result.add(matrix[top][i]);
                  }
                  top ++;
      
                  for(int i = top; i <= bottom; i ++) {
                      result.add(matrix[i][right]);
                  }
                  right --;
      
                  if(top <= bottom && left <= right) {
                      for(int i = right; i >= left; i --) {
                          result.add(matrix[bottom][i]);
                      }
                      bottom --;
      
                      for(int i = bottom; i >= top; i --) {
                          result.add(matrix[i][left]);
                      }
                      left ++;
                  }
              }
              return result;
          }
      }

旋转图像

  • 方法一

    • 先按照对角线交换二维数组的数,然后将每一行的数再左右交换即可

    • class Solution {
          public void rotate(int[][] matrix) {
              int n = matrix.length;
              int m = matrix[0].length;
      
              for(int i = 0; i < n; i ++) {
                  for(int j = 0; j < m; j ++) {
                      if(i < j) {
                          int temp = matrix[i][j];
                          matrix[i][j] = matrix[j][i];
                          matrix[j][i] = temp;
                      }
                  }
              } 
      
              for(int i = 0; i < n; i ++) {
                  for(int j = 0; j < m/2; j ++) {
                      int temp = matrix[i][j];
                      matrix[i][j] = matrix[i][m - j - 1];
                      matrix[i][m - j - 1] = temp;
                  }
              }
          }
      }

搜索二维矩阵 II

  • 方法一

    • 这个题的话,我们可以从右上角来看,因为它满足向左依次减小,向下一次增大。这样的话我们只要判断目标值和当前值的关系移动即可。

    • class Solution {
          public boolean searchMatrix(int[][] matrix, int target) {
              int n = matrix.length;
              int m = matrix[0].length;
      
              int row = 0, col = m - 1;
              while(row < n && col >= 0) {
                  if(target == matrix[row][col]) return true;
                  else if(target < matrix[row][col]) col --;
                  else row ++; 
              }
              return false;
          }
      }

链表

相交链表

  • 方法一

    • 题目已保证不会有环,那只要我们指针去遍历A,B就可以,因为如果存在交点,那么它们这样遍历的话到达交点的步数是一样的

    • /**
       * Definition for singly-linked list.
       * public class ListNode {
       *     int val;
       *     ListNode next;
       *     ListNode(int x) {
       *         val = x;
       *         next = null;
       *     }
       * }
       */
      public class Solution {
          public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
              ListNode pa = headA, pb = headB;
              while(pa != pb) {
                  pa = pa != null ? pa.next : headB;
                  pb = pb != null ? pb.next : headA;
              }
              return pa;
          }
      }

反转链表

  • 方法一

    • 题目要求我们去反转链表,定义两个指针,一个指向head,一个指向空

    • /**
       * Definition for singly-linked list.
       * public class ListNode {
       *     int val;
       *     ListNode next;
       *     ListNode() {}
       *     ListNode(int val) { this.val = val; }
       *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
       * }
       */
      class Solution {
          public ListNode reverseList(ListNode head) {
              ListNode cur = head, pre = null;
              while(cur != null) {
                  ListNode tmp = cur.next;
                  cur.next = pre;
                  pre = cur;
                  cur = tmp;
              }
              return pre;
          }
      }

回文链表

  • 方法一

    • 题目要求我们去判断是否为回文链表,可以用快慢指针来解决,可以帮助我们找到链表的中间位置,关键点在于fast指针循环跳出的条件

      if(fast == null || fast.next == null) break;

      这里对奇数链表和偶数链表做一个简单的举例,以fast 2j/s slow 1j/s,奇数链表,比如1 2 2 ,fast指针是可以正好跳完整个链表的,此时fast.next = null,这时的slow指针指向的就是中间节点;对于偶数链表,fast指针跳到最后是会有一个的剩余,那么fast.next = null,就是slow指向链表后半部分的head

    • /**
       * Definition for singly-linked list.
       * public class ListNode {
       *     int val;
       *     ListNode next;
       *     ListNode() {}
       *     ListNode(int val) { this.val = val; }
       *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
       * }
       */
      class Solution {
          public boolean isPalindrome(ListNode head) {
              if(head == null || head.next == null) return true;
      
              ListNode fast = head, slow = head;
              while(true) {
                  slow = slow.next;
                  fast = fast.next.next;
                  if(fast == null || fast.next == null) break;
              }
      
              ListNode sec = reverse(slow);
      
              while(sec != null) {
                  if(head.val != sec.val) return false;
                  head = head.next;
                  sec = sec.next;
              }
              return true;
          }
      
          public ListNode reverse(ListNode head) {
              ListNode cur = head, pre = null;
              while(cur != null) {
                  ListNode tmp = cur.next;
                  cur.next = pre;
                  pre = cur;
                  cur = tmp;
              }
              return pre;
          }
      }

环形链表

  • 方法一

    • 判断是否为环形链表,只需要判断slow和fast能不能相遇(套圈)即可。

    • /**
       * Definition for singly-linked list.
       * class ListNode {
       *     int val;
       *     ListNode next;
       *     ListNode(int x) {
       *         val = x;
       *         next = null;
       *     }
       * }
       */
      public class Solution {
          public boolean hasCycle(ListNode head) {
              if(head == null || head.next == null) return false;
      
              ListNode fast = head, slow = head;
              while(true) {
                  fast = fast.next.next;
                  slow = slow.next;
                  if(fast == null || fast.next == null) return false;
                  if(slow == fast)break;
              }
              return true;
          }
      }

环形链表 II

  • 方法一

    • 该题与环形链表类似,在环形链表的基础上添加了获取环形链表的入环的第一个节点这一条件

    • 快慢指针相遇

      假设我们有一个环形链表,起点为 head,环的入口节点为 entry。我们使用两个指针 slowfast

      • slow 每次移动一步

      • fast 每次移动两步

      假设 slowfast 在节点 meet 处相遇。此时 slow 走了 s 步,fast 走了 2s 步。

      数学推导

      由于 fast 每次比 slow 多走一步,因此 fast 走的步数是 slow 的两倍。设环的长度为 Lentrymeet 节点的距离为 mheadentry 节点的距离为 a。那么我们可以列出以下等式:

      • slow 走了 a + m

      • fast 走了 2(a + m)

      由于 fast 在环内走了 k 圈(假设走了 k 圈),我们可以得到:

      2(a+m)=a+m+kL2(a + m) = a + m + kL2(a+m)=a+m+kL

      简化得到:

      a+m=kLa + m = kLa+m=kL

      这意味着 a + m 是环的整数倍。

      相遇节点与环入口节点

      为了找到环的入口节点,我们需要将其中一个指针(例如 slow)移回起点(head),同时另一个指针(fast)保持在相遇点(meet)。然后两个指针都以相同的速度前进。

      由于 a + m = kL,即从 headentry 的距离加上从 entrymeet 的距离等于环的整数倍,所以:

      • slowhead 开始走 a 步会到达 entry

      • fastmeet 开始走 a 步(因为 a + m 是环的整数倍,绕过若干圈后会回到 entry

      这样,两个指针会在 entry 处再次相遇。

    • /**
       * Definition for singly-linked list.
       * class ListNode {
       *     int val;
       *     ListNode next;
       *     ListNode(int x) {
       *         val = x;
       *         next = null;
       *     }
       * }
       */
      public class Solution {
          public ListNode detectCycle(ListNode head) {
              if(head == null || head.next == null) return null;
      
              ListNode fast = head, slow = head;
              while(true) {
                  slow = slow.next;
                  fast = fast.next.next;
                  if(fast == null || fast.next == null) return null;
                  if(fast == slow) break;
              }
      
              slow = head;
              while(fast != slow) {
                  fast = fast.next;
                  slow = slow.next;
              }
              return fast;
          }
      }

合并两个有序链表

  • 方法一

    • 题解:

    • /**
       * Definition for singly-linked list.
       * public class ListNode {
       *     int val;
       *     ListNode next;
       *     ListNode() {}
       *     ListNode(int val) { this.val = val; }
       *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
       * }
       */
      class Solution {
          public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
              ListNode dummy = new ListNode(-1);
              ListNode current = dummy;
      
              while(l1 != null && l2 != null) {
                  if(l1.val < l2.val ) {
                      current.next = l1;
                      l1 = l1.next;
                  } else {
                      current.next = l2;
                      l2 = l2.next;
                  }
                  current = current.next;
              }
              if(l1 != null) current.next = l1;
              if(l2 != null) current.next = l2;
      
              return dummy.next;
          }
      }

两数相加

  • 方法一

    • 题解:

    • /**
       * Definition for singly-linked list.
       * public class ListNode {
       *     int val;
       *     ListNode next;
       *     ListNode() {}
       *     ListNode(int val) { this.val = val; }
       *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
       * }
       */
      class Solution {
          public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
              ListNode dummy = new ListNode(-1);
              ListNode current = dummy;
      
              int res = 0, carry = 0;
              while(l1 != null || l2 != null) {
                  res = carry;
                  if(l1 != null) {
                      res += l1.val;
                      l1 = l1.next;
                  }
                  if(l2 != null) {
                      res += l2.val;
                      l2 = l2.next;
                  }
      
                  current.next = new ListNode(res%10);
                  carry = res / 10;
                  current = current.next;
              }
              if(carry > 0) {
                  current.next = new ListNode(carry);
              }
              return dummy.next;
          }
      }

删除链表的倒数第 N 个结点

  • 方法一

    • 题解:

    • /**
       * Definition for singly-linked list.
       * public class ListNode {
       *     int val;
       *     ListNode next;
       *     ListNode() {}
       *     ListNode(int val) { this.val = val; }
       *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
       * }
       */
      class Solution {
          public ListNode removeNthFromEnd(ListNode head, int n) {
              ListNode dummy = new ListNode(-1);
              ListNode current = dummy;
              dummy.next = head;
      
              for(int i = 0; i <= n; i ++) {
                  if(current != null) {
                      current = current.next;
                  } else {
                      return null;
                  }
              }
      
              ListNode pre = dummy;
              while(current != null) {
                  pre = pre.next;
                  current = current.next;
              }
              pre.next = pre.next.next;
              return dummy.next;
          }
      }

两两交换链表中的节点

K 个一组翻转链表

随机链表的复制

排序链表

合并 K 个升序链表

LRU 缓存

二叉树

二叉树的中序遍历

二叉树的最大深度

翻转二叉树

对称二叉树

二叉树的直径

二叉树的层序遍历

将有序数组转换为二叉搜索树

验证二叉搜索树

二叉搜索树中第K小的元素

二叉树的右视图

二叉树展开为链表

从前序与中序遍历序列构造二叉树

路径总和 III

二叉树的最近公共祖先

二叉树中的最大路径和

图论

岛屿数量

腐烂的橘子

课程表

实现 Trie (前缀树)

回溯

全排列

子集

电话号码的字母组合

组合总和

括号生成

单词搜索

分割回文串

N 皇后

二分查找

搜索插入位置

搜索二维矩阵

在排序数组中查找元素的第一个和最后一个位置

搜索旋转排序数组

寻找旋转排序数组中的最小值

寻找两个正序数组的中位数

有效的括号

最小栈

字符串解码

每日温度

柱状图中最大的矩形

数组中的第K个最大元素

前 K 个高频元素

数据流的中位数

贪心算法

买卖股票的最佳时机

跳跃游戏

跳跃游戏 II

划分字母区间

动态规划

爬楼梯

杨辉三角

打家劫舍

完全平方数

零钱兑换

单词拆分

最长递增子序列

乘积最大子数组

分割等和子集

最长有效括号

多维动态规划

不同路径

最小路径和

最长回文子串

最长公共子序列

编辑距离

技巧

只出现一次的数字

多数元素

颜色分类

下一个排列

寻找重复数