一、Array

两数之和(#1)

TODO 只有一个有效答案,可进阶实现_ 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。 示例1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]
示例2:
输入:nums = [3,3,4,15], target = 7
输出:
TODO 是否考虑要满足条件的第一个数值 [0,2] or [1,2]

  1. // 返回值
  2. public static int[] twoSumGetValue(int[] nums, int target) {
  3. Map<Integer, Integer> map = new HashMap<>();
  4. map.put(nums[0], 0);
  5. for (int i = 1; i < nums.length; i++) {
  6. if (map.containsKey(target - nums[i])) {
  7. return new int[]{target - nums[i], nums[i]};
  8. }
  9. map.put(nums[i], i);
  10. }
  11. return new int[]{-1, -1};
  12. }
  13. // 返回索引
  14. public static int[] twoSumGetIndex(int[] nums, int target) {
  15. Map<Integer, Integer> map = new HashMap<>();
  16. for (int i = 0; i < nums.length; i++) {
  17. if (map.containsKey(target - nums[i])) {
  18. return new int[]{map.get(target - nums[i]), i};
  19. }
  20. // 处理:{3, 3, 11, 15} target:6 的情况 因为 map 会覆盖相同的key的内容,放在后面 put 就不需要考虑这一点了 !!!
  21. map.put(nums[i], i);
  22. }
  23. return new int[]{-1, -1};
  24. }

三数之和(#15)

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1: 输入:nums = [-1,0,1,2,-1,-4] 输出:[[-1,-1,2],[-1,0,1]]

示例 2: 输入:nums = [] 输出:[]

示例 3: 输入:nums = [0] 输出:[]

前提:

  • sum > target -> right—
  • sum < target -> left++
  • left >= right -> 结束内循环i++ left = i + 1
  • 判断核心数是否重复
  • 判断左右指针是否重复

�代码:

  1. private static List<List<Integer>> threeSum(int[] nums, int target) {
  2. List<List<Integer>> result = new ArrayList<>();
  3. // 快速排序
  4. Arrays.sort(nums);
  5. // 定义当前循环的num[i] 为核心匹配对象
  6. for (int i = 0; i < nums.length; i++) {
  7. // 如果核心大于targetNumbe 则结束循环
  8. if (nums[i] > target) break;
  9. // 第二次循环后,判断设置的核心数是否与上一个重复
  10. if (i > 0 && nums[i] == nums[i - 1]) continue;
  11. int left = i + 1;
  12. int right = nums.length - 1;
  13. // 只要左右不重叠,就继续移动指针
  14. while (left < right) {
  15. int sum = nums[i] + nums[left] + nums[right];
  16. if (sum == target) {
  17. result.add(Arrays.asList(nums[i], nums[left], nums[right]))
  18. left++;
  19. right--;
  20. // 移动时left与前一个重复了,则继续移动
  21. while (left < right && nums[left] == nums[left - 1]) { left++;
  22. }
  23. while (right > left && nums[right] == nums[right + 1]) { right--;
  24. }
  25. } else if (sum > target) {
  26. right--;
  27. } else {
  28. left++;
  29. }
  30. }
  31. }
  32. return result;
  33. }

复杂度分析:

  • 时间复杂度依然为O(n^2)
  • 空间复杂度仅为常数级O(1)

下一个子序列(#31)

时间复杂度:O(n)

例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。
如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。

  • 例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。
  • 类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。
  • 而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。

给你一个整数数组 nums ,找出 nums 的下一个排列。
必须 原地 修改,只允许使用额外常数空间。

示例 1: 输入:nums = [1,2,3] 输出:[1,3,2]

示例 2: 输入:nums = [3,2,1] 输出:[1,2,3]

示例 3: 输入:nums = [1,1,5] 输出:[1,5,1]

提示: 1 <= nums.length <= 100 0 <= nums[i] <= 100

思路:
image.png
代码:

  1. public static void nextPermutation(int[] nums) {
  2. // 从后往前遍历找到第一个k < k+1 的位置
  3. int k = nums.length - 2;
  4. while (k >= 0 && nums[k] >= nums[k + 1])
  5. k--;
  6. // 如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)
  7. if (k < 0) {
  8. convert(nums, 0, nums.length -1);
  9. }
  10. int i = k + 2;
  11. while (i < nums.length && nums[i] > nums[k])
  12. i++;
  13. int temp = nums[k];
  14. nums[k] = nums[i-1];
  15. nums[i - 1] = temp;
  16. // 1,5,8,6,(7,4,4,3,1)处理最后的排序(确保只是更大的下一个排列) 因为已经是降序
  17. convert(nums, k + 1, nums.length -1);
  18. }
  19. private static void convert(int[] nums, int start, int end) {
  20. while (start < end) {
  21. int temp2 = nums[start];
  22. nums[start] = nums[end];
  23. nums[end] = temp2;
  24. start++;
  25. end--;
  26. }
  27. }

二、Find查找

搜索二维矩阵(#74)

TODO 返回索引

  1. 编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。<br />该矩阵具有如下特性:s
  • 每行中的整数从左到右按升序排列
  • 每行的第一个整数大于前一行的最后一个整数。

示例1:
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,50]], target = 3
输出:true
示例 2:
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,50]], target = 13
输出:false
示例 3:
输入:matrix = [], target = 0
输出:false

提示:
m == matrix.length
n == matrix[i].length
0 <= m, n <= 100
-104 <= matrix[i][j], target <= 104
int[][] nums = {
{1, 3, 5, 7},
{10, 11, 16, 20},
{23, 30, 34, 50}
}; �

  • 先假设将二维数组转换成一个一维数组
  • 此时 index = x * n + y
  • x = index / n(列)
  • y = index % n(列)

代码:�

  1. // 时间复杂度:Olog(m*n) 空间复杂度:O(1)
  2. public static boolean searchMatrix(int[][] matrix, int target) {
  3. int row = matrix.length;
  4. int col = matrix[0].length;
  5. if (row == 0) return false;
  6. int left = 0;
  7. int right = row * col -1;
  8. while (left <= right) {
  9. int midIdx = (left + right) / 2;
  10. int midValue = matrix[midIdx / col][midIdx % col]; // --> index = x * n + y
  11. if (midValue < target) {
  12. left = midIdx + 1;
  13. } else if (midValue > target) {
  14. right = midIdx - 1;
  15. } else {
  16. System.out.println(midIdx + " x: " + midIdx / col + " y: " + midIdx % col);
  17. return true;
  18. }
  19. }
  20. return false;
  21. }

寻找重复数(#287)

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。

  • 假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。
  • 你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。

    示例 1: 输入:nums = [1,3,4,2,2] 输出:2

    示例 2: 输入:nums = [3,1,3,4,2] 输出:3

进阶:

  • 如何证明 nums 中至少存在一个重复的数字?
  • 你可以设计一个线性级时间复杂度 O(n) 的解决方案吗?

实现方法:

  • hashMap
  • hashSet
  • 快慢指针法

快慢指法:
这是一种比较特殊的思路。把nums看成是顺序存储的链表,nums中每个元素的值是下一个链表节点的地址。
那么如果nums有重复值,说明链表存在环,本问题就转化为了找链表中环的入口节点,因此可以用快慢指针解决。

比如数组:
[3,6,1,4,6,6,2]
示例:
image.png
整体思路如下:

  • 第一阶段,寻找环中的相遇节点
    • 初始时,都指向链表第一个节点nums[0];
    • 慢指针每次走一步,快指针走两步;
    • 如果有环,那么快指针一定会再次追上慢指针;相遇时,相遇节点必在环中
  • 第二阶段,寻找环的入口节点(重复的地址值)
    • 重新定义两个指针,让before,after分别指向链表开始节点,相遇节点
    • before与after相遇时,相遇点就是环的入口节点

代码:

  1. public int findDuplicate(int[] nums) {
  2. int low = 0;
  3. int fast = 0;
  4. do {
  5. low = nums[low];
  6. fast = nums[nums[fast]];
  7. } while (low != fast);
  8. int before = 0;
  9. int after = fast;
  10. while (before != after){
  11. before = nums[before];
  12. after = nums[after];
  13. }
  14. return before;
  15. }

复杂度分析:

  • 时间复杂度:O(n),不管是寻找环上的相遇点,还是环的入口,访问次数都不会超过数组长度。
  • 空间复杂度:O(1),我们只需要定义几个指针就可以了。