1.两数之和

image.png

解法1:暴力解法【线性查找】

  1. //空间复杂度O(1) 空间复杂度O(n^2)
  2. class Solution {
  3. public int[] twoSum(int[] nums, int target) {
  4. if(nums==null||nums.length==0){return new int[0];}
  5. for(int i=0;i<nums.length;i++){
  6. int x=nums[i];
  7. for(int j=i+1;j<nums.length;j++){
  8. if(nums[j]==target-x){
  9. return new int[]{i,j};
  10. }
  11. }
  12. }
  13. return new int[0];
  14. }
  15. }

解法2:二分查找

由于该题目要求给出值的索引,二分查找的前提是数组有序的,这样就打乱了原来数组的索引,如果硬要这么做,需要开辟额外的空间来记住原有的值的索引,所以在这里不适合用二分查找!

解法3:哈希查找优化

  1. // 时间复杂度O(n) 空间复杂度O(n) -->开辟了一个map,最大是数组长度
  2. class Solution {
  3. public int[] twoSum(int[] nums, int target) {
  4. if(nums==null||nums.length==0){return new int[0];}
  5. HashMap<Integer,Integer> map=new HashMap<>();
  6. for(int i=0;i<nums.length;i++){ //O(n)
  7. map.put(nums[i],i);
  8. }
  9. for(int i=0;i<nums.length;i++){ //O(n)
  10. int x=nums[i];
  11. if(map.containsKey(target-x)){
  12. int index=map.get(target-x);
  13. //防止使用同一元素两次
  14. if(i!=index){
  15. return new int[]{i,index};
  16. }
  17. }
  18. }
  19. return new int[0];
  20. }
  21. }

再次优化

  1. // 时间复杂度:O(n)
  2. // 空间复杂度:O(n)
  3. public int[] twoSum(int[] nums, int target) {
  4. if (nums == null || nums.length == 0) return new int[0];
  5. int n = nums.length;
  6. Map<Integer, Integer> map = new HashMap<>();
  7. // O(n)
  8. for (int i = 0; i < n; i++) {
  9. int x = nums[i];
  10. // 哈希查找
  11. if (map.containsKey(target - x)) {
  12. int index = map.get(target - x);
  13. return new int[]{i, index};
  14. }
  15. map.put(x, i); //核心思想:有可能在数组的前几个元素就有答案了,后面的元素就没必要加 进map
  16. }
  17. return new int[0];
  18. }

2.两数之和 II - 输入有序数组

image.png

这道题可以用暴力解法,也可以用hash解法,但是hash解法有一个缺点,需要开辟额外一个map配合使用。
这道题给的数组是有序的,那么我们可以使用二分查找,快且不需要开辟空间。

解法1:二分查找

  1. //时间复杂度O(nlogn)
  2. class Solution {
  3. public int[] twoSum(int[] nums, int target) {
  4. if (nums == null || nums.length == 0) return new int[0];
  5. for(int i=0;i<nums.length;i++){
  6. int x=nums[i];
  7. //在i+1~nums.length-1区间二分查找
  8. int index=binarySerach(nums,i+1,nums.length-1,target-x);
  9. if(index!=-1){
  10. return new int[]{i+1,index+1};
  11. }
  12. }
  13. return new int[0];
  14. }
  15. private int binarySerach(int[] nums,int left,int right,int target){
  16. while(left<=right){
  17. //防止数值溢出int_Max
  18. int mid=left+(right-left)/2;
  19. if(nums[mid]==target)
  20. return mid;
  21. else if(nums[mid]>target)
  22. right=mid-1;
  23. else
  24. left=mid+1;
  25. }
  26. return -1;
  27. }
  28. }

解法2:双指针

因为该数组有序,所以可以在两边设置指针,让最小的指针和最大的指针相加,如果还是小于target,那么最小指针往右移动,大于同理。就可以实现时间复杂度低于二分查找,空间复杂度不变的优化。

  1. // 时间复杂度:O(n)
  2. // 空间复杂度:O(1)
  3. class Solution {
  4. public int[] twoSum(int[] nums, int target) {
  5. if (nums == null || nums.length == 0) return new int[0];
  6. int left=0;
  7. int right=nums.length-1;
  8. while(left<=right){
  9. if(nums[left]+nums[right]==target)
  10. return new int[]{left+1,right+1};
  11. else if(nums[left]+nums[right]<target)
  12. left++;
  13. else
  14. right--;
  15. }
  16. return new int[0];
  17. }
  18. }

两数之和 IV - 输入 BST

image.png
BST二叉查找树中根结点大于左子节点,小于右子节点。刚好中序遍历二叉查找树生成一条有序的数组,可以根据这条数组进行双指针解决。

方案1:双指针

  1. /**
  2. * Definition for a binary tree node.
  3. * public class TreeNode {
  4. * int val;
  5. * TreeNode left;
  6. * TreeNode right;
  7. * TreeNode() {}
  8. * TreeNode(int val) { this.val = val; }
  9. * TreeNode(int val, TreeNode left, TreeNode right) {
  10. * this.val = val;
  11. * this.left = left;
  12. * this.right = right;
  13. * }
  14. * }
  15. */
  16. //时间复杂度O(n)
  17. class Solution {
  18. public boolean findTarget(TreeNode root, int k) {
  19. if(root==null) return false;
  20. List<Integer> nums=new ArrayList<>();
  21. inOrder(root,nums);
  22. int left=0;
  23. int right=nums.size()-1;
  24. while(left<right){
  25. int sum=nums.get(left)+nums.get(right);
  26. if(sum==k)
  27. return true;
  28. else if(sum>k)
  29. right--;
  30. else
  31. left++;
  32. }
  33. return false;
  34. }
  35. //中序遍历 左中右
  36. private void inOrder(TreeNode node,List<Integer> nums){
  37. if(node==null) return;
  38. inOrder(node.left,nums);
  39. nums.add(node.val);
  40. inOrder(node.right,nums);
  41. }
  42. }

解法2:哈希查找

将每一次遍历的结点,与set集合中的元素进行比较,如果该结点+集合中元素==target,那么直接返回true,不用再进行遍历。

  1. class Solution {
  2. public boolean findTarget(TreeNode root, int k) {
  3. if(root==null) return false;
  4. return find(root,k,new HashSet<>());
  5. }
  6. private boolean find(TreeNode node, int target, Set<Integer> set) {
  7. //前序遍历 中左右
  8. if(node==null) return false;
  9. if(set.contains(target-node.val)) return true;
  10. set.add(node.val);
  11. //一直遍历左结点 直到左节点为null才开始遍历右节点
  12. return find(node.left,target,set)||find(node.right,target,set);
  13. }
  14. }

补充:
image.png
DLR—前序遍历(根在前,从左往右,一棵树的根永远在左子树前面,左子树又永远在右子树前面 )
LDR—中序遍历(根在中,从左往右,一棵树的左子树永远在根前面,根永远在右子树前面)
LRD—后序遍历(根在后,从左往右,一棵树的左子树永远在右子树前面,右子树永远在根前面)

三数之和

解法1:暴力解法

  1. class Solution {
  2. public List<List<Integer>> threeSum(int[] nums) {
  3. if(nums==null||nums.length<3)
  4. return new ArrayList<>();
  5. //使用hashSet避免集合重复
  6. Set<List<Integer>> set=new HashSet<>();
  7. //因为需要集合中的元素不重复,所以进行排序
  8. Arrays.sort(nums); //O(nlogn)
  9. //O(n^3)
  10. for(int i=0;i<nums.length;i++){
  11. for(int j=i+1;j<nums.length;j++){
  12. for(int k=j+1;k<nums.length;k++){
  13. if(nums[i] + nums[j] + nums[k] == 0){
  14. set.add(Arrays.asList(nums[i],nums[j],nums[k]));
  15. }
  16. }
  17. }
  18. }
  19. return new ArrayList<>(set);
  20. }
  21. }

解法2:双指针

固定一个元素,然后设置两个指针指向剩余的元素,将该两个指针指向的元素相加,如果等于固定的元素的符数,那就是其中一个答案。由于是有序的,所以这两个指针可以配合二分查找。

  1. class Solution {
  2. public List<List<Integer>> threeSum(int[] nums) {
  3. if(nums==null||nums.length<3)
  4. return new ArrayList<>();
  5. //使用hashSet避免集合重复
  6. Set<List<Integer>> set=new HashSet<>();
  7. //因为需要集合中的元素不重复,所以进行排序
  8. Arrays.sort(nums); //O(nlogn)
  9. //O(n^2)
  10. for(int i=0;i<nums.length;i++){
  11. int target=-nums[i];
  12. int left=i+1;
  13. int right=nums.length-1;
  14. while(left<right){
  15. int sum=nums[left]+nums[right];
  16. if(sum==target){
  17. set.add(Arrays.asList(nums[i],nums[left],nums[right]));
  18. left++;
  19. right--;
  20. }else if(sum <target){
  21. left++;
  22. }else{
  23. right--;
  24. }
  25. }
  26. }
  27. return new ArrayList<>(set); //O(n)
  28. }
  29. }

对Set转List进行优化

使用HashSet的目的是为了去重,那么如果不利用HashSet的去重特性,该怎么去重?

  1. left和right指针的下一个/前一个元素跟当前指向的元素值相同,则跳过下一个/前一个元素,指向下下个元素或前前个元素
  2. 固定元素i的下一个元素如果同当前元素值相同,那么下一次循环的结果肯定是相同的,所以应该跳过下一个元素。

    1. class Solution {
    2. public List<List<Integer>> threeSum(int[] nums) {
    3. if(nums==null||nums.length<3)
    4. return new ArrayList<>();
    5. List<List<Integer>> list=new ArrayList<>();
    6. Arrays.sort(nums);
    7. for(int i=0;i<nums.length;i++){
    8. //当前元素跟上一个元素相同则跳过,i+1和i比较的话,就会导致还没拿到答案就跳过了
    9. if(i>0&&nums[i]==nums[i-1]) continue;
    10. int target=-nums[i];
    11. int left=i+1;
    12. int right=nums.length-1;
    13. while(left<right){
    14. int sum=nums[left]+nums[right];
    15. if(sum==target){
    16. list.add(Arrays.asList(nums[i],nums[left],nums[right]));
    17. //++left:先让left往后移 然后才拿到left+1后的值再进行判断 而不是先拿left进行判断再往后移
    18. while(left<right&&nums[left]==nums[++left]);
    19. while(left<right&&nums[right]==nums[--right]);
    20. }else if(sum <target){
    21. left++;
    22. }else{
    23. right--;
    24. }
    25. }
    26. }
    27. return list;
    28. }
    29. }

四数之和

image.png

  1. class Solution {
  2. public List<List<Integer>> fourSum(int[] nums, int target) {
  3. if (nums == null || nums.length < 4)
  4. return new ArrayList<>();
  5. Arrays.sort(nums);
  6. List<List<Integer>> res = new ArrayList<>();
  7. for(int i=0;i<nums.length;i++){
  8. if(i>0&&nums[i]==nums[i-1]) continue;
  9. for(int j=i+1;j<nums.length;j++){
  10. if(j>i+1&&nums[j]==nums[j-1]) continue;
  11. int partSum=nums[i]+nums[j];
  12. int left=j+1;
  13. int right=nums.length-1;
  14. while(left<right){
  15. int sum=partSum +nums[left]+nums[right];
  16. if(sum==target){
  17. res.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
  18. while(left<right&&nums[left]==nums[++left]);
  19. while(left<right&&nums[right]==nums[--right]);
  20. }else if(sum<target)
  21. left++;
  22. else
  23. right--;
  24. }
  25. }
  26. }
  27. return res;
  28. }
  29. }

解法同三数之和一样,区别只不过是固定两个数

两数之和 II - 输入有序数组