查找问题

704-二分查找

image.png

  1. // 盲写通过
  2. public int search(int[] nums, int target) {
  3. int left = 0, right = nums.length - 1;
  4. while (left <= right) {
  5. int mid = left + (right - left) / 2;
  6. if (nums[mid] == target) {
  7. return mid;
  8. } else if (nums[mid] > target) {
  9. right = mid - 1;
  10. } else if (nums[mid] < target) {
  11. left = mid + 1;
  12. }
  13. }
  14. return -1;
  15. }

215-数组中的第 K 个最大元素

image.png
代码:

  1. public int findKthLargest(int[] nums, int k) {
  2. int len = nums.length;
  3. int idx = len - k;
  4. return quicksort(nums, 0, len - 1, idx);
  5. }
  6. public int quicksort(int[] nums, int start, int end, int idx) {
  7. if (start > end) {
  8. return -1;
  9. }
  10. int pivotIdx = partition(nums, start, end, idx);
  11. if (pivotIdx == idx) {
  12. return nums[pivotIdx];
  13. } else if (pivotIdx < idx) { // 基准pivotIdx小于idx时,说明预期结果在idx右侧,则对右侧进行递归遍历
  14. return quicksort(nums, pivotIdx + 1, end, idx);
  15. } else {
  16. return quicksort(nums, start, pivotIdx - 1, idx);
  17. }
  18. }
  19. public int partition(int[] nums, int start, int end, int idx) {
  20. int pviot = nums[start];
  21. int left = start, right = end;
  22. // 左右开始扫描,将基准值pviot移动到合适的位置
  23. while (left != right) {
  24. while (left < right && nums[right] >= pviot) {
  25. right--;
  26. }
  27. while (left < right && nums[left] <= pviot) {
  28. left++;
  29. }
  30. // 交换 left 和 right 的值
  31. if (left < right) {
  32. int tmp = nums[left];
  33. nums[left] = nums[right];
  34. nums[right] = tmp;
  35. }
  36. }
  37. //一次排序结束后,left和right抵达重合点,将基准值移动到这里
  38. int p = nums[start];
  39. nums[start] = nums[left];
  40. nums[left] = p;
  41. return left;
  42. }

33-搜索旋转排序数组

image.png
代码:

  1. public int search1(int[] nums, int target) {
  2. Map<Integer, Integer> idxMap = new HashMap<>();
  3. for (int i = 0; i< nums.length; i++) {
  4. idxMap.put(nums[i], i);
  5. }
  6. if (idxMap.containsKey(target)) {
  7. return idxMap.get(target);
  8. }
  9. return -1;
  10. }
  11. public int search2(int[] nums, int target) {
  12. for (int i = 0; i < nums.length; i++) {
  13. if (nums[i] == target) {
  14. return i;
  15. }
  16. }
  17. return -1;
  18. }
  19. public int search(int[] nums, int target) {
  20. if (nums.length == 0) {
  21. return -1;
  22. }
  23. if (nums.length == 1) {
  24. return nums[0] == target ? 0 : -1;
  25. }
  26. int left = 0, right = nums.length -1;
  27. int mid = 0;
  28. while (left <= right) {
  29. mid = (left + right) / 2;
  30. if (nums[mid] == target) {
  31. return mid;
  32. }
  33. // 总有一半数据是有序的
  34. if (nums[left] <= nums[mid]) {
  35. //左边数据有序则查找左边数据
  36. if (target >= nums[left] && target < nums[mid]){
  37. right = mid - 1;
  38. } else {
  39. left = mid + 1;
  40. }
  41. } else {
  42. // 右边数据有序则查找右边数据
  43. if (target > nums[mid] && target <= nums[right]) {
  44. left = mid + 1;
  45. } else {
  46. right = mid -1;
  47. }
  48. }
  49. }
  50. return -1;
  51. }

81-搜索旋转排序数组II

image.png
代码:

  1. public boolean search(int[] nums, int target) {
  2. if (nums.length == 0) {
  3. return false;
  4. }
  5. if (nums.length == 1) {
  6. return nums[0] == target ? true : false;
  7. }
  8. int left = 0, right = nums.length -1;
  9. int mid = 0;
  10. while (left <= right) {
  11. mid = (left + right) / 2;
  12. if (nums[mid] == target) {
  13. return true;
  14. }
  15. // 如果数组中元素有重复的,那旋转后可能无法判断哪一半有序
  16. if (nums[left] == nums[mid] ){
  17. left++;
  18. } else if (nums[mid] == nums[right]) {
  19. right--;
  20. } else{
  21. if (nums[left] <= nums[mid]) {
  22. //左边数据有序则查找左边数据
  23. if (target >= nums[left] && target < nums[mid]){
  24. right = mid - 1;
  25. } else {
  26. left = mid + 1;
  27. }
  28. } else {
  29. // 右边数据有序则查找右边数据
  30. if (target > nums[mid] && target <= nums[right]) {
  31. left = mid + 1;
  32. } else {
  33. right = mid -1;
  34. }
  35. }
  36. }
  37. }
  38. return false;
  39. }

34-排序数组查元素第一个/最后一个idx(二分)

image.png
代码:

  1. public int[] searchRange(int[] nums, int target) {
  2. if (nums.length == 0) {
  3. return new int[]{-1, -1};
  4. }
  5. int firstPosition = findFirstPosition(nums, target);
  6. if (firstPosition == -1 ) {
  7. return new int[]{-1, -1};
  8. }
  9. int lastPosition = findLastPosition(nums, target);
  10. return new int[]{firstPosition, lastPosition};
  11. }
  12. /**
  13. * 找到第一次出现的位置
  14. * @param nums
  15. * @param target
  16. * @return
  17. */
  18. public int findFirstPosition(int[] nums, int target) {
  19. int left = 0, right = nums.length - 1;
  20. while (left < right) {
  21. int mid = (left + right) / 2;
  22. if (nums[mid] < target) {
  23. left = mid + 1;
  24. } if (nums[mid] == target) {
  25. right = mid;
  26. } else if (nums[mid] > target) {
  27. right = mid - 1;
  28. }
  29. }
  30. if (nums[left] == target) {
  31. return left;
  32. }
  33. return -1;
  34. }
  35. /**
  36. * 向右查找最后一次出现的位置
  37. * @param nums
  38. * @param target
  39. * @return
  40. */
  41. public int findLastPosition(int[] nums, int target) {
  42. int left = 0, right = nums.length - 1;
  43. while (left < right) {
  44. // 比如:left=4,right=5,此时 mid = 4,nums[mid]=target,left=mid 操作一直是 left=4,right=5,进入死循环
  45. int mid = (left + right + 1) / 2;
  46. if (nums[mid] < target) {
  47. left = mid + 1;
  48. } if (nums[mid] == target) {
  49. left = mid;
  50. } else if (nums[mid] > target) {
  51. right = mid - 1;
  52. }
  53. }
  54. return left;
  55. }

35-搜索插入位置

image.png
题意分析:

  • 查找和插入两个条件,根据二分查找判断数组中是否存在target元素,存在直接返回,不存在则返回元素插入位置(保证数组整体有序)

解题思路:

  • 二分查找,判断条件记录什么时候更新索引位置。

注意点:

  • 插入时需要记录元素要插入的位置。

扩展:
代码:

  1. public int searchInsert(int[] nums, int target) {
  2. return binarySearch(nums, target);
  3. }
  4. public int binarySearch(int[] nums, int target) {
  5. int n = nums.length;
  6. int left = 0, right = n - 1;
  7. int ans = n;
  8. while (left <= right) {
  9. int mid = left + (right - left) / 2;
  10. System.out.println("mid = " + mid);
  11. if (nums[mid] == target) {
  12. return mid;
  13. } else if (nums[mid] > target) {
  14. ans = mid;
  15. right = mid - 1;
  16. } else if (nums[mid] < target){
  17. left = mid + 1;
  18. }
  19. }
  20. return ans;
  21. }

去重问题

26-删除有序数组中的重复项

image.png
代码:

  1. public int removeDuplicates(int[] nums) {
  2. if (nums.length == 0) {
  3. return nums.length;
  4. }
  5. int slow = 1, fast = 1;
  6. while (fast < nums.length){
  7. if (nums[fast] == nums[slow-1]) {
  8. fast++;
  9. } else {
  10. nums[slow++] = nums[fast++];
  11. }
  12. }
  13. return slow;
  14. }

80-删除有序数组中的重复项II

image.png
代码:

  1. public int removeDuplicates(int[] nums) {
  2. if (nums.length == 0 || nums.length == 1) {
  3. return nums.length;
  4. }
  5. int slow = 2, fast = 2;
  6. while (fast < nums.length){
  7. if (nums[fast] == nums[slow-2]) {
  8. fast++;
  9. } else {
  10. nums[slow++] = nums[fast++];
  11. }
  12. }
  13. return slow;
  14. }

710-黑名单中的随机数

image.png
题意分析:

  • [0, n-1] 中去掉一些数,访问剩下的数。

解题思路:

  1. 直接用集合去重。(存储 [0, n-1] 个元素,内存超出限制)
  2. 黑名单元素重定向。构建 [whitesize, n-1] 长度的数组(和黑名单数组元素相同),然后将黑名单的元素直接映射到这个数组。

注意点:

  • 黑名单中大于 whitesize 索引的元素直接过滤,比较随机数索引不到,只需要对黑名单中小于 whitesize 的元素映射即可。
  • 求解返回时,如果是黑名单中数据重定向,白名单数据直接返回,会用到 Map 的 getOrDefault() 方法。

代码:

  1. public class RandomBlackList_710 {
  2. Set<Integer> set = new HashSet<>();
  3. Map<Integer, Integer> map = new HashMap<>();
  4. int whitesize;
  5. /**
  6. * 解题思路:
  7. * 1、构建白名单set,初始元素和黑名单一致。
  8. * 2、将黑名单中大于 whitesize 的元素从 set 移除,将小于 whitesize 的元素映射到 set 的剩余元素
  9. * @param n
  10. * @param blacklist
  11. */
  12. public RandomBlackList_710(int n, int[] blacklist) {
  13. whitesize = n - blacklist.length;
  14. for (int i = whitesize; i < n; i++) set.add(i);
  15. for (int val : blacklist) set.remove(val);
  16. Iterator<Integer> iter = set.iterator();
  17. // 将黑名单数据请求到白名单其他数据
  18. for (int val : blacklist) {
  19. if (val < whitesize) {
  20. map.put(val, iter.next());
  21. }
  22. }
  23. }
  24. public int pick() {
  25. int rand = new Random().nextInt(whitesize);
  26. return map.getOrDefault(rand, rand);
  27. }
  28. public static void main(String[] args) {
  29. int n = 10;
  30. int[] blacklist = {1,2,4,8};
  31. RandomBlackList_710 obj = new RandomBlackList_710(n, blacklist);
  32. System.out.println("size = " + obj.set.toString());
  33. System.out.println("map key = " + obj.map.keySet().toString() + "; map val = " + obj.map.values().toString());
  34. for (int i = 0; i < 10; i++)
  35. System.out.println(obj.pick());
  36. }
  37. }

岛屿问题

200-岛屿数量

image.png
题意分析:

  • 当前节点可达的节点集合作为一个岛屿,问题可以归结为图的遍历。

解题思路:

  • dfs 遍历。一次 dfs 遍历所有节点解决不了问题,需要对每个可行节点执行 dfs 逻辑,每一次找到一个岛屿。

注意点:
扩展:
代码:

  1. public class IslandsQuestion_200 {
  2. int[][] direction = {{-1,0},{1,0},{0,-1},{0,1}};
  3. char[][] visited;
  4. char[][] grid;
  5. int m,n;
  6. int num;
  7. public int numIslands(char[][] grid) {
  8. this.grid = grid;
  9. m = grid.length;
  10. n = grid[0].length;
  11. visited = new char[m][n];
  12. for (int i = 0; i < m; i++) {
  13. Arrays.fill(visited[i], '0');
  14. }
  15. for (int i = 0; i < m; i++) {
  16. for (int j = 0; j < n; j++) {
  17. if (grid[i][j] == '1' && visited[i][j] == '0') {
  18. num++;
  19. dfs(i, j);
  20. }
  21. }
  22. }
  23. return num;
  24. }
  25. /**
  26. * dfs 遍历:将 '1' 周围关联的 '1' 全部设置为 visited
  27. * @param x
  28. * @param y
  29. */
  30. private void dfs(int x, int y) {
  31. if (x < 0 || y < 0 || x >= m || y >= n) {
  32. return;
  33. }
  34. // '0' 不访问, visited-1 表示已经访问
  35. if ( grid[x][y] == '0' || visited[x][y] == '1') {
  36. return;
  37. }
  38. // 每一轮 dfs 遍历,将 '1' 周围节点的 '1' 都设置为 visited
  39. visited[x][y] = '1';
  40. // 将 dfs 可达节点都置为 1,表示访问过
  41. for (int i = 0; i < direction.length; i++) {
  42. int tx = x + direction[i][0];
  43. int ty = y + direction[i][1];
  44. dfs(tx, ty);
  45. }
  46. }
  47. public static void main(String[] args) {
  48. IslandsQuestion_200 obj = new IslandsQuestion_200();
  49. char[][] grid = {
  50. {'1','1','0','0','0'},
  51. {'1','1','0','0','0'},
  52. {'0','0','1','0','0'},
  53. {'0','0','0','1','1'}
  54. };
  55. int res = obj.numIslands(grid);
  56. System.out.println("res = " + res);
  57. }
  58. }

1254-统计封闭岛屿的数量

image.png
题意分析:

  • 和题 200 题目类似,无非就是要过滤掉四个边边上的节点。

解题思路:

  • 判断是先判断边边的节点,过滤掉。

注意点:
扩展:
代码:

  1. public class IslandsClosed_1254 {
  2. int[][] grid;
  3. int[][] direction = {{0,-1},{0,1},{-1,0},{1,0}};
  4. int m,n;
  5. int res;
  6. public int closedIsland(int[][] grid) {
  7. this.grid = grid;
  8. m = grid.length;
  9. n = grid[0].length;
  10. // 对四周节点过滤
  11. for (int i = 0; i < m; i++) {
  12. dfs(i,0);
  13. dfs(i, n - 1);
  14. }
  15. for (int i = 0; i < n; i++) {
  16. dfs(0, i);
  17. dfs(m - 1, i);
  18. }
  19. // 对所以 0(陆地)节点遍历,判断是否能形成一个岛屿
  20. for (int i = 0; i < m; i++) {
  21. for (int j = 0; j < n; j++) {
  22. if (grid[i][j] == 0) {
  23. res++;
  24. dfs(i,j);
  25. }
  26. }
  27. }
  28. return res;
  29. }
  30. public void dfs(int x, int y) {
  31. if (x < 0 || y < 0 || x >= m || y >= n) {
  32. return;
  33. }
  34. if (grid[x][y] == 1) {
  35. return;
  36. }
  37. // 将访问过的 0(陆地) 设置为 1(海水)
  38. grid[x][y] = 1;
  39. for (int i = 0; i < direction.length; i++) {
  40. int tx = x + direction[i][0];
  41. int ty = y + direction[i][1];
  42. dfs(tx, ty);
  43. }
  44. }
  45. }

未分类

581-最短无序连续子数组

image.png
代码:

  1. /**
  2. * 排序 + 遍历(时间复杂度:O(N * logN)
  3. *
  4. * 复制一个数组并排序,遍历对比left和right区间
  5. * @param nums
  6. * @return
  7. */
  8. public int findUnsortedSubarray2(int[] nums) {
  9. int len = nums.length;
  10. int[] sorted = Arrays.copyOf(nums, nums.length);
  11. Arrays.sort(sorted);
  12. System.out.println("nums = " + Arrays.toString(nums));
  13. System.out.println("sorted = " + Arrays.toString(sorted));
  14. int left = 0, right = len - 1;
  15. int l = -1, r = -1;
  16. for (; left < len; left++) {
  17. if (nums[left] != sorted[left]) {
  18. l = left;
  19. break;
  20. }
  21. }
  22. for (; right >= 0; right--) {
  23. if (nums[right] != sorted[right]) {
  24. r = right;
  25. break;
  26. }
  27. }
  28. System.out.println("l = " + l + "; r = " + r);
  29. return l != r ? r - l + 1 : 0;
  30. }
  31. /**
  32. * 双指针
  33. * @param nums
  34. * @return
  35. */
  36. public int findUnsortedSubarray(int[] nums) {
  37. int len = nums.length;
  38. int left = 0, right = 0;
  39. int max = Integer.MIN_VALUE;
  40. int min = Integer.MAX_VALUE;
  41. // 双指针左右扫描,找出 left 和 right
  42. for (int i = 0; i < len; i++) {
  43. // 找 right。从左到右遍历,如果某个元素小于前面所有元素的最大值,则无序,更新 right
  44. if (nums[i] >= max) {
  45. max = nums[i];
  46. } else {
  47. right = i;
  48. }
  49. // 找left。从右往左遍历,如果某个元素大于前面所有元素的最小值,则无序,更新left
  50. if (nums[len - i -1] <= min) {
  51. min = nums[len - i - 1];
  52. } else {
  53. left = len - i - 1;
  54. }
  55. }
  56. System.out.println("left = " + left + "; right = " + right);
  57. return left == right ? 0 : right - left + 1;
  58. }

896-单调数列

image.png
代码:

  1. public boolean isMonotonic(int[] nums) {
  2. int len = nums.length;
  3. boolean isInc = true, isDec = true;
  4. // 如果不是单调递增或者单调递减,isInc 和 isDec 都为 false
  5. for (int i = 0; i < len - 1; i++) {
  6. // 一对递增数据,isDec = false;
  7. if (nums[i] < nums[i+1]) {
  8. isDec = false;
  9. }
  10. if (nums[i] > nums[i+1]) {
  11. isInc = false;
  12. }
  13. }
  14. return isInc || isDec;
  15. }

1337-矩阵中战斗力最弱的 K 行

image.png
代码:

  1. public int[] kWeakestRows(int[][] mat, int k) {
  2. int m = mat.length, n = mat[0].length;
  3. for(int i = 0; i < m; i++) {
  4. for (int j =0; j< n; j++) {
  5. System.out.print(mat[i][j] + " ");
  6. }
  7. System.out.println();
  8. }
  9. // 记录行号和1的个数
  10. int[][] cntArr = new int[m][2];
  11. for (int i = 0; i < m; i++) {
  12. // 对每一行执行二分查找,找出 1 的个数
  13. int[] arr = mat[i];
  14. int pos = binarySearch(arr);
  15. cntArr[i][0] = i;
  16. cntArr[i][1] = pos + 1;
  17. }
  18. for (int i = 0; i < cntArr.length; i++) {
  19. System.out.println("行:1数量 == " + cntArr[i][0] + ":" + cntArr[i][1]);
  20. }
  21. // 自定义比较器排序,先按1的个数升序排序,再按行号升序排序
  22. Arrays.sort(cntArr, new Comparator<int[]>() {
  23. @Override
  24. public int compare(int[] o1, int[] o2) {
  25. if (o1[1] != o2[1]) {
  26. return (o1[1] - o2[1]);
  27. } else {
  28. return (o1[0] - o2[0]);
  29. }
  30. }
  31. });
  32. System.out.println("-------排序后——--------");
  33. for (int i = 0; i < cntArr.length; i++) {
  34. System.out.println("行:1数量 == " + cntArr[i][0] + ":" + cntArr[i][1]);
  35. }
  36. // 输出前 k 行行号
  37. int[] ret = new int[k];
  38. for(int i = 0; i < k; i++) {
  39. ret[i] = cntArr[i][0];
  40. System.out.println(ret[i]);
  41. }
  42. return ret;
  43. }
  44. /**
  45. * 二分查找每一行 1 的数量
  46. * @param arr
  47. * @return
  48. */
  49. public int binarySearch(int[] arr) {
  50. int left = 0, right = arr.length - 1;
  51. int pos = -1;
  52. while (left <= right) {
  53. int mid = left + (right - left) / 2;
  54. if (arr[mid] == 0) {
  55. right = mid - 1;
  56. } else {
  57. pos = mid;
  58. left = mid + 1;
  59. }
  60. }
  61. return pos;
  62. }

88-合并两个有序数组

image.png
代码:

  1. /**
  2. * 申请长度为 m+n 的额外数组
  3. * @param nums1
  4. * @param m
  5. * @param nums2
  6. * @param n
  7. */
  8. public void merge2(int[] nums1, int m, int[] nums2, int n) {
  9. int[] tmp = new int[m+n];
  10. int k = 0;
  11. int i = 0, j = 0;
  12. while (i < m && j < n) {
  13. tmp[k++] = nums1[i] < nums2[j] ? nums1[i++] : nums2[j++];
  14. }
  15. while(i < m) {
  16. tmp[k++] = nums1[i++];
  17. }
  18. while (j < n) {
  19. tmp[k++] = nums2[j++];
  20. }
  21. for(int p = 0 ;p < tmp.length; p++) nums1[p] = tmp[p];
  22. System.out.println("tmp = " + Arrays.toString(nums1));
  23. }
  24. /**
  25. * 原地复制,从尾部空位置开始填充
  26. * @param nums1
  27. * @param m
  28. * @param nums2
  29. * @param n
  30. */
  31. public void merge(int[] nums1, int m, int[] nums2, int n) {
  32. int i = m - 1;
  33. int j = n - 1;
  34. int k = m + n - 1;
  35. while (i >= 0 && j >= 0) {
  36. nums1[k--] = nums1[i] > nums2[j] ? nums1[i--] : nums2[j--];
  37. }
  38. System.arraycopy(nums2, 0 , nums1, 0, j + 1);
  39. // System.out.println("nums = " + Arrays.toString(nums1));
  40. }

611-有效三角形的个数

image.png
代码:

  1. /**
  2. * 排序 + 二分查找 (时间复杂度 O(N^2 * logN)
  3. *
  4. * 注意:System.out 会增加统计时间,造成时间超时
  5. *
  6. * @param nums
  7. * @return
  8. */
  9. public int triangleNumber(int[] nums) {
  10. int len = nums.length;
  11. if (len <= 2) {
  12. return 0;
  13. }
  14. int ans = 0;
  15. Arrays.sort(nums);
  16. for (int i = 0; i < len-1; i++) {
  17. for (int j = i+1; j < len; j++) {
  18. int left = j + 1, right = len - 1;
  19. int k = j;
  20. while (left <= right) {
  21. int mid = left + (right - left) / 2;
  22. if (nums[i] + nums[j] > nums[mid]) {
  23. // System.out.println(nums[i] + "-" + nums[j] + "-" + nums[mid]);
  24. k = mid;
  25. left = mid + 1;
  26. } else {
  27. right = mid - 1;
  28. }
  29. }
  30. ans += k - j;
  31. }
  32. }
  33. return ans;
  34. }

912-排序数组

image.png
代码:

  1. public int[] sortArray(int[] nums) {
  2. quicksort(nums, 0, nums.length - 1);
  3. return nums;
  4. }
  5. public void quicksort(int[] nums, int startIdx, int endIdx) {
  6. if (startIdx < endIdx) {
  7. int pivot = partition(nums, startIdx, endIdx);
  8. quicksort(nums, startIdx, pivot);
  9. quicksort(nums, pivot + 1, endIdx);
  10. }
  11. }
  12. /**
  13. * 一次快排操作
  14. * @param nums
  15. * @param startIdx
  16. * @param endIdx
  17. * @return
  18. */
  19. public int partition(int[] nums, int startIdx, int endIdx) {
  20. int pivot = startIdx;
  21. int left = startIdx, right = endIdx;
  22. while (left != right) {
  23. while (left < right && nums[right] >= nums[pivot]) right--;
  24. while (left < right && nums[left] <= nums[pivot]) left++;
  25. // 交换left 和 right 的值
  26. if (left < right) {
  27. int tmp = nums[left];
  28. nums[left] = nums[right];
  29. nums[right] = tmp;
  30. }
  31. }
  32. // left/right 重合,交换 pivot 和 left 的值,一次快排完成
  33. int tmp = nums[pivot];
  34. nums[pivot] = nums[left];
  35. nums[left] = tmp;
  36. return left;
  37. }

189-旋转数组

image.png
代码:

  1. public void rotate(int[] nums, int k) {
  2. k = k % nums.length;
  3. reverse(nums, 0, nums.length - 1);
  4. reverse(nums, 0, k - 1);
  5. reverse(nums, k, nums.length - 1);
  6. printArray(nums);
  7. }
  8. // 倒序数组
  9. public void reverse(int[] nums, int start, int end) {
  10. while (start < end) {
  11. int tmp = nums[start];
  12. nums[start] = nums[end];
  13. nums[end] = tmp;
  14. start++;
  15. end--;
  16. }
  17. }

915-分隔数组

image.png
题意分析:

  • 分隔数组,使得left数组数据都小于right数组数据,且保证left数组长度最小。

解题思路:

  • 多次遍历数组。判断条件 max(left) < min(right),满足这个条件的最小 idx 值。
    • 时间复杂度:O(n)
    • 空间复杂度:O(n)

注意点:

  • 最后条件判断的时候要考虑 <= 相等的情况。

扩展:

  • 能否减少数组遍历次数。

代码:

  1. /**
  2. * 解题思路:
  3. * 判断条件:max(left) < min(right),满足这个条件的最小 idx 值。
  4. * 1、统计 left 数组每个分隔点的最大值
  5. * 2、统计 right 数组每个分隔点的最小值
  6. * @param nums
  7. * @return
  8. */
  9. public int partitionDisjoint(int[] nums) {
  10. int n = nums.length;
  11. int[] maxleft = new int[n];
  12. int[] minright = new int[n];
  13. // left 数组每个位置的最大值
  14. int max = Integer.MIN_VALUE;
  15. for (int i = 0; i < n; i++) {
  16. max = Math.max(max, nums[i]);
  17. maxleft[i] = max;
  18. }
  19. System.out.println("maxleft = " + Arrays.toString(maxleft));
  20. // right 数组每个位置的最小值
  21. int min = Integer.MAX_VALUE;
  22. for (int j = n-1; j >= 0; j--) {
  23. min = Math.min(min, nums[j]);
  24. minright[j] = min;
  25. }
  26. System.out.println("minright = " + Arrays.toString(minright));
  27. // 比较切分后 left 和 right 数组的值
  28. for (int i = 1; i < n; i++) {
  29. if (maxleft[i-1] <= minright[i]) {
  30. return i;
  31. }
  32. }
  33. return n;
  34. }

27-移除元素

image.png
image.png
题意分析:

  • 原地修改数组,删除数组中值为 val 的元素。

解题思路:

  • 左右指针。左指针维护更新后数组元素,右指针扫描数组。

注意点:

  • 右指针何时扫描。

扩展:
代码:

  1. /**
  2. * 解题思路:双指针
  3. * @param nums
  4. * @param val
  5. * @return
  6. */
  7. public int removeElement(int[] nums, int val) {
  8. System.out.println(Arrays.toString(nums));
  9. int left = 0, right = 0;
  10. while (right != nums.length) {
  11. if (nums[right] != val) {
  12. nums[left++] = nums[right];
  13. }
  14. right++;
  15. }
  16. // System.out.println(Arrays.toString(nums));
  17. return left;
  18. }

1909-删除一个元素使数组严格递增

image.png
题意分析:

  • 删除一个元素,数组严格递增。

解题思路:

  • 贪心算法。当遇到当前小于前一个数时,则比较当前数和前两个数大小,如果当前数小于前两个数,则删除当前数,如果当前数小于前两个数中的一个(即小于前一个数),则删除前一个数。

注意点:
扩展:
代码:

1-两数之和

image.png
题意分析:

  • 数组中两个元素之和等于 target。

解题思路:

  • 思路一:HashMap 保存 target-nums[i] 元素。
  • 思路二:排序+双指针。

注意点:
扩展:

  • 返回所有和为 target 的元素对,且不存在重复项。

代码:

  1. /**
  2. * 思路一:HashMap 保存遍历过的元素和索引
  3. * @param nums
  4. * @param target
  5. * @return
  6. */
  7. public int[] twoSum(int[] nums, int target) {
  8. Map<Integer, Integer> map = new HashMap<>();
  9. int[] res = new int[2];
  10. for (int i = 0; i < nums.length; i++) {
  11. if (map.containsKey(nums[i])) {
  12. res[0] = map.get(nums[i]);
  13. res[1] = i;
  14. return res;
  15. } else {
  16. map.put(target - nums[i], i);
  17. }
  18. }
  19. return res;
  20. }
  21. /**
  22. * 思路二:双指针(求所有两数之和为 sum 的元素对,且不重复)
  23. * @param nums
  24. * @param target
  25. * @return
  26. */
  27. public List<List<Integer>> twoSum2(int[] nums, int target) {
  28. List<List<Integer>> res = new ArrayList<>();
  29. // 对数组升序排序
  30. Arrays.sort(nums);
  31. int low = 0, high = nums.length - 1;
  32. while (low < high) {
  33. int low_val = nums[low];
  34. int high_val = nums[high];
  35. int sum = low_val + high_val;
  36. if (sum < target) {
  37. low++;
  38. } else if (sum > target) {
  39. high--;
  40. } else if (sum == target) {
  41. List<Integer> list = new ArrayList();
  42. list.add(nums[low++]); list.add(nums[high--]);
  43. // 如果 low 往右走出现重复元素,则继续右走,high 逻辑也一样
  44. while (nums[low] == low_val) low++;
  45. while (nums[high] == high_val) high--;
  46. res.add(list);
  47. }
  48. }
  49. return res;
  50. }

15-三数之和

image.png
题意分析:

  • 数组中三个元素和为 target。

解题思路:

  • 排序+双指针。排序数组,遍历数组中每个元素,调用 2Sum 求解,遇到重复遍历元素跳过。

注意点:
扩展:
代码:

  1. public List<List<Integer>> threeSum(int[] nums) {
  2. List<List<Integer>> res = new ArrayList<>();
  3. if (nums.length <= 2) {
  4. return res;
  5. }
  6. Arrays.sort(nums);
  7. // System.out.println(Arrays.toString(nums));
  8. for (int i = 0; i < nums.length - 2; i++) {
  9. List<List<Integer>> twoRes = twoSum(nums,i + 1, 0 - nums[i]);
  10. // System.out.println("nums[i] = " + nums[i] + "; twoRes = " + twoRes);
  11. for (int j = 0; j < twoRes.size(); j++) {
  12. twoRes.get(j).add(nums[i]);
  13. res.add(twoRes.get(j));
  14. }
  15. while (i < nums.length -1 && nums[i+1] == nums[i]) i++;
  16. }
  17. return res;
  18. }
  19. /**
  20. * 两数只和
  21. * @param nums
  22. * @param start
  23. * @param target
  24. * @return
  25. */
  26. private List<List<Integer>> twoSum(int[] nums, int start, int target) {
  27. List<List<Integer>> twoRes = new ArrayList<>();
  28. int low = start, high = nums.length - 1;
  29. while (low < high) {
  30. int low_val = nums[low], high_val = nums[high];
  31. int sum = low_val + high_val;
  32. if (sum < target) {
  33. // 优化
  34. while (low < high && nums[low] == low_val) low++;
  35. } else if (sum > target) {
  36. while (low < high && nums[high] == high_val)high--;
  37. } else if (sum == target) {
  38. List<Integer> list = new ArrayList<>();
  39. list.add(low_val); list.add(high_val);
  40. while (low < high && nums[low] == low_val) low++;
  41. while (low < high && nums[high] == high_val) high--;
  42. twoRes.add(list);
  43. }
  44. }
  45. return twoRes;
  46. }

18-四数之和(nSum问题)

image.png
题意分析:

  • 数组中四个数或者 n 个数和为 target。

解题思路:

  • 思路一:调用 3Sum 求解,没意义。
  • 思路二:通用 nSum 求解,递归调用 (n-1)Sum 求解。

注意点:
扩展:
代码:

  1. public List<List<Integer>> fourSum(int[] nums, int target) {
  2. int n = 4;
  3. Arrays.sort(nums);
  4. return nSumTarget(nums, n, 0, target);
  5. }
  6. /**
  7. *
  8. * @param nums
  9. * @param n 表示几个数之和,比如 n=2,表示两个数之和
  10. * @param start 数组的开始元素
  11. * @param target
  12. * @return
  13. */
  14. public List<List<Integer>> nSumTarget(int[] nums, int n, int start, int target) {
  15. int size = nums.length;
  16. List<List<Integer>> res = new ArrayList<>();
  17. // 至少 2Sum,且数组大小不应该小于 n
  18. if (n < 2 || size < n) return res;
  19. int low = start, high = nums.length - 1;
  20. if (n == 2) {
  21. // 双指针
  22. while (low < high) {
  23. int low_val = nums[low], high_val = nums[high];
  24. int sum = low_val + high_val;
  25. if (sum < target) {
  26. // 优化
  27. while (low < high && nums[low] == low_val) low++;
  28. } else if (sum > target) {
  29. // 优化
  30. while (low < high && nums[high] == high_val)high--;
  31. } else if (sum == target) {
  32. List<Integer> list = new ArrayList<>();
  33. list.add(low_val); list.add(high_val);
  34. while (low < high && nums[low] == low_val) low++;
  35. while (low < high && nums[high] == high_val) high--;
  36. res.add(list);
  37. }
  38. }
  39. } else {
  40. // n > 2 时,递归计算 (n-1)Sum 结果
  41. for (int i = start; i < size; i++) {
  42. List<List<Integer>> nRes = nSumTarget(nums, n - 1, i + 1, target - nums[i]);
  43. for (List<Integer> list : nRes) {
  44. list.add(nums[i]);
  45. res.add(list);
  46. }
  47. while (i < size - 1 && nums[i] == nums[i+1]) i++;
  48. }
  49. }
  50. return res;
  51. }

模版

题意分析:
解题思路:
注意点:
扩展:
代码: