leetcode 查找算法(二)

205 同构字符串

题目描述

给定两个字符串 s 和 t,判断它们是否是同构的。

如果 s 中的字符可以被替换得到 t ,那么这两个字符串是同构的。

所有出现的字符都必须用另一个字符替换,同时保留字符的顺序。两个字符不能映射到同一个字符上,但字符可以映射自己本身。

示例 1:

输入: s = "egg", t = "add"
输出: true
示例 2:

输入: s = "foo", t = "bar"
输出: false
示例 3:

输入: s = "paper", t = "title"
输出: true
说明:
你可以假设 s 和 t 具有相同的长度。

解题思路

方法与290 单词规律一样

方法一:采用hashMap,s--每个字符做key t--每个字符做value。第一次遇到的 key 就加入到 HashMap 中,第二次遇到同一个 key,那就判断它的value 和当前单词是否一致。用set存放value的值,可提高访问效率。

public boolean isIsomorphic(String s, String t) {
    if (s.length() != t.length())
        return false;

    HashMap<Character, Character> map = new HashMap<>();
    HashSet<Character> set = new HashSet<>();

    for (int i = 0; i < s.length(); i++) {
        char key = s.charAt(i);
        char value = t.charAt(i);

        if (map.containsKey(key)) {
            if (!map.get(key).equals(value))
                return false;
        } else {
            // 判断是否存在value
            if (set.contains(value)) {
                return false;
            }
            map.put(key, value);
            set.add(value);
        }
    }
    return true;
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

方法二:s--->t 与 t ---> s 映射 以此确定两者一一对应关系

public boolean isIsomorphic2(String s, String t) {
    if (s.length() != t.length())
        return false;

    String[] array1 = s.split("");
    String[] array2 = t.split("");

    return isomorphicHelp(array1, array2) && isomorphicHelp(array2, array1);
}

private boolean isomorphicHelp(String[] array1, String[] array2) {
    HashMap<String, String> map = new HashMap<>();
    for (int i = 0; i < array1.length; i++) {
        String key = array1[i];
        if (map.containsKey(key)) {
            if (!map.get(key).equals(array2[i])) {
                return false;
            }
        } else {
            map.put(key, array2[i]);
        }
    }
    return true;

}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

tips:方法一的效率高于方法二的

方法二运行结果

方法一运行结果

对撞指针

1. 两数之和

题目描述

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

解题思路

采用hashmap存储数组,在进行迭代并将元素插入到表中的同时,我们还会回过头来检查表中是否已经存在当前元素所对应的目标元素。如果它存在,那我们已经找到了对应解,并立即将其返回。

代码

public int[] twoSum(int[] nums, int target) {
    HashMap<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 null;
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

3 三数之和

题目描述

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

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

示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

解题思路

1、先对原数组排序,这样方便后续去重
2、采用双指针,固定一个元素num[i],让second和third分别指向当前遍历元素的下一个节点(左边)和数组末端位置
3、去重 如果访问到相同的元素也即num[second] = num[second-1],则让second往后移
或者 num[third] = num[third+1],让third往左移
当满足num[first] + num[second] + num[third] = 0时,则 second++.third--; 继续重复上述操作

代码

public List<List<Integer>> threeSum(int[] nums) {
    List<List<Integer>> ans = new ArrayList<>();
    if (nums == null || nums.length < 3) return ans;

    // 1 排序
    Arrays.sort(nums);

    // 2 采用双指针,固定一个元素num[i],让second和third分别指向当前遍历元素的下一个节点(左边)和数组末端位置
    int n = nums.length;
    for (int i = 0; i < n; i++) {
        // 第一个元素都大于0  后面就无需再考虑了
        if (nums[i] > 0) return ans;

        // 第一个元素 去掉重复情况
        if (i > 0 && nums[i] == nums[i - 1]) 
            continue;

        int target = -nums[i];
        int second = i + 1;   // 指向当前元素的下一个位置
        int third = n - 1;   //  指向数组最后一个位置,两边都往中间靠拢

        while (second < third) {
            // 当满足条件时,添加到list中
            if (nums[second] + nums[third] == target) {
                List<Integer> list = new ArrayList<>();
                list.add(nums[i]);
                list.add(nums[second]);
                list.add(nums[third]);
                ans.add(list);

                // 双指针向中间靠拢
                second++;
                third--;

                // 第二、三个位置 去重
                while (second < third && nums[second] == nums[second - 1]) second++;
                while (second < third && nums[third] == nums[third + 1]) third--;
            } else if (nums[second] + nums[third] < target) {
                second++;
            } else {
                third--;
            }

        }
    }

    return ans;
}

复杂度分析

  • 时间复杂度:O(n*n) ,排序需要O(nlogn)的时间复杂度,后续的双重循环时间复杂度为o(n*n),第二三位置去重的时间损耗可以忽略,因为该循环个上一层循环会执行同一条数组相同元素序列,时间损耗只需计算一次即可。
  • 空间复杂度:O(logn) + O(n),其中 O(logn) ---- 排序需要的空间复杂度,O(n) ---- 存储答案的复杂度

18 四数之和

题目描述

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

注意:

答案中不可以包含重复的四元组。

示例:

给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。

满足要求的四元组集合为:
[
  [-1,  0, 0, 1],
  [-2, -1, 1, 2],
  [-2,  0, 0, 2]
]

解题方法

在三数之和的基础上,外加一层循环,方法同上

代码

public List<List<Integer>> fourSum(int[] nums, int target) {
    List<List<Integer>> ans = new ArrayList<>();
    if (nums.length < 4) return ans;

    // 1 排序
    Arrays.sort(nums);

    // 2 采用双指针,固定一个元素num[i],让second和third分别指向当前遍历元素的下一个节点(左边)和数组末端位置
    int n = nums.length;
    for (int i = 0; i < n; i++) {
        if (i > 0 && nums[i] == nums[i - 1]) continue;

        // 区别于三数之和 这里增加一重循环
        for (int j = i + 1; j < n; j++) {
            // 第一个元素 去掉重复情况
            if (j > i + 1 && nums[j] == nums[j - 1])
                continue;

            int second = j + 1;   // 指向当前元素的下一个位置
            int third = n - 1;   //  指向数组最后一个位置,两边都往中间靠拢

            while (second < third) {
                int temp = nums[i] + nums[j] + nums[second] + nums[third];
                if (temp == target) {
                    List<Integer> list = new ArrayList<>();
                    list.add(nums[i]);
                    list.add(nums[j]);
                    list.add(nums[second]);
                    list.add(nums[third]);
                    ans.add(list);

                    // 双指针向中间靠拢
                    second++;
                    third--;

                 while (second < third && nums[second] == nums[second - 1]) second++;
                 while (second < third && nums[third] == nums[third + 1]) third--;
                } else if (temp < target) {
                    second++;
                } else {
                    third--;
                }
            }
        }

    }
    return ans;
}

复杂度分析

  • 时间复杂度:O(n*n*n)
  • 空间复杂度:与三数之和的一样 O(logn) + O(n),其中 O(logn) ---- 排序需要的空间复杂度,O(n) ---- 存储答案的复杂度

16 最接近的三数之和

题目描述

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

示例:

输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
 

提示:

3 <= nums.length <= 10^3
-10^3 <= nums[i] <= 10^3
-10^4 <= target <= 10^4

解题思路

与三数之和类似,不同之处在于找出离给定数值,最接近的三数之和。具体思路如下

1、排序
2、与三数之和类似,双重循环遍历
2.1 当三数之和等于给定的数值时,直接返回即可
2.2 当三数之和小于给定的数值时,左边的指针往右移 (即second++)
2.3 当三数之和大于给定的数值时,右边的指针往左移(即third--)
3、根据插值的绝对值来更新答案,设置临时全局来保存最终的结果

代码

public int threeSumClosest(int[] nums, int target) {
    if (nums.length < 3) return 0;

    // 1 排序
    Arrays.sort(nums);

    int sum;
    int best = 10000;  // 千万别写Integer.MAX_VALUE; 不然[-3,-2,-5,3,-4] 通过不了
    int n = nums.length;

    // 2 双重遍历
    for (int i = 0; i < n; i++) {
        if (i > 0 && nums[i] == nums[i - 1]) continue;

        int second = i + 1;
        int third = n - 1;

        while (second < third) {
            sum = nums[i] + nums[second] + nums[third];
            if (sum == target) return sum;

            // 根据差值的绝对值来更新答案
            if (Math.abs(sum - target) < Math.abs(best - target)) {
                best = sum;
            }

            if (sum > target) {
                third--;
                while (second < third && nums[third] == nums[third + 1]) third--;
            }

            if (sum < target){
                second++;
                while (second < third && nums[second] == nums[second - 1]) second++;
            }

        }
    }

    return best;
}

复杂度分析

  • 时间复杂度:O(n*n)

  • 空间复杂度:O(logn), 用来存放最终结果的空间复杂度为O(1),而排序带来的空间复杂度为O(logn)

454 四数相加II

题目描述

给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。

为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。

例如:

输入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]

输出:
2

解释:
两个元组如下:
1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0

解题思路

本题可以采用巧妙的方法解决,四个数组可以分为两类,这样时间复杂度为O(n*n),具体如下:

1、采用分为两组,HashMap存一组,另一组和HashMap进行比对。
2、HashMap存两个数组之和,如AB。然后计算两个数组之和,如CD。时间复杂度为:O(n2)+O(n2),得到O(n2).
3、我们以存AB两数组之和为例。首先求出A和B任意两数之和sumAB,以sumAB为key,sumAB出现的次数为value,存入hashmap中。

代码

public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
    HashMap<Integer, Integer> map = new HashMap<>();
    int sum = 0;

    for (int i = 0; i < A.length; i++) {
        for (int j = 0; j < B.length; j++) {
            int temp = A[i] + B[j];
            map.put(temp, map.getOrDefault(temp,0) + 1);
        }
    }

    for (int i = 0; i < C.length; i++) {
        for (int j = 0; j < D.length; j++) {
            int temp = -(C[i] + D[j]);
            if (map.containsKey(temp))
                sum += map.get(temp);
        }
    }

    return sum;
}

复杂度分析

  • 时间复杂度:O(n2)
  • 空间复杂度:O(n) --- 用来存储答案所需空间

49 字母异位词分组

题目描述

给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。

示例:

输入: ["eat", "tea", "tan", "ate", "nat", "bat"]
输出:
[
  ["ate","eat","tea"],
  ["nat","tan"],
  ["bat"]
]
说明:

所有输入均为小写字母。
不考虑答案输出的顺序。

解题思路

首先想到的就是暴力解法,遍历字符串数组,比较两两字符串是否具有相同的元素,若相同则将其归为一组,否则单独为一组,继续遍历直到结束。具体如下:

1、比较两两字符串是否相等,可采用hashmap,其中一个字符串输入统计每个字符出现的次数,字符作为key,出现次数作为value;另一个字符串输入时,如果包含该字符,则将该字符对应的次数减1,否则返回false;最后查看hashmap存放的元素是否为空(初始化状态),若满足,则返回true,否则返回false

2、为了防止重复计算,可以使用boolean类型的数组user[]作为访问的标记,减少无谓的遍历

代码

public List<List<String>> groupAnagrams(String[] strs) {
    List<List<String>> ans = new ArrayList<>();
    boolean[] used = new boolean[strs.length];

    for (int i = 0; i < strs.length; i++) {
        List<String> list = null;
        if (!used[i]) {
            list = new ArrayList<>();
            list.add(strs[i]);

            // 判断两两字符串是否相等
            for (int j = i + 1; j < strs.length; j++) {
                if (!used[j] && isEquals(strs[j],strs[i])) {
                    list.add(strs[j]);
                    used[j] = true;
                }
            }
        }
        if (list!=null)
            ans.add(list);
    }
    return ans;
}

// 比较两个字符串是否相等
private boolean isEquals(String str1, String str2) {
    if (str1.length() != str2.length()) return false;

    HashMap<Character, Integer> map = new HashMap<>();
    // 统计str1中个字符的个数
    for (int i = 0; i < str1.length(); i++) {
        map.put(str1.charAt(i), map.getOrDefault(str1.charAt(i),0)+1);
    }

    //  在str2中 若存在就自减,否则不相等
    for (int i = 0; i < str2.length(); i++) {
        if (map.containsKey(str2.charAt(i))) {
            map.put(str2.charAt(i),map.get(str2.charAt(i)) - 1);
        } else {
            return false;
        }
    }

    // 判断最后两字符串是否相等 map为空 说明相等
    //判断每个字符的次数是不是 0 ,不是的话直接返回 false
    Set<Character> set = map.keySet();
    for (char c : set) {
        if (map.get(c) != 0) {
            return false;
        }
    }
    return true;
}

复杂度分析

  • 时间复杂度:虽然看起来外层用了两个 for 循环,但是我们通过 used 数组保证了每个字符串只会访问 1 次,所以外层的复杂度是字符串数组的长度 O(n),判断两个字符串相等的函数 equal 函数,时间复杂度是字符串的最长长度 O(K)。所以总共就是 O(n*k)。
  • 空间复杂度:O(n*k),用来存储结果

47 回旋镖的数量

题目描述

给定平面上 n 对不同的点,“回旋镖” 是由点表示的元组 (i, j, k) ,其中 i 和 j 之间的距离和 i 和 k 之间的距离相等(需要考虑元组的顺序)。

找到所有回旋镖的数量。你可以假设 n 最大为 500,所有点的坐标在闭区间 [-10000, 10000] 中。

示例:

输入:
[[0,0],[1,0],[2,0]]

输出:
2

解释:
两个回旋镖为 [[1,0],[0,0],[2,0]] 和 [[1,0],[2,0],[0,0]]

解题思路

计算某个点到剩余其他点的距离,将距离作为hashmpa的key,将同一距离出现的次数作为value,这样就可以通过hashmap中的value值找到满足题意的点的对数啦。

对于每一个点,都计算其它点与它的距离,然后把这个距离放入map中作为key,满足这一距离的其它点的个数作为value。比如与点A相距10的点有5个,那么可能的组合就有5*4种方式。

遍历一遍关于点A的map,对于所有的距离下的个数都计算出可能的组合方式再相加,就得到对于一个点来说满足要求的所有组合。
在外面再套一层循环,就可以获得所有点相对于其它点的map,也就可以获得所有的组合个数,也就是我们需要的返回值 更多>>

代码

public int numberOfBoomerangs(int[][] points) {
    //思路: 计算某个点与剩余其他点的距离,用hashmap存放,key为 两点之间的距离,value为此距离的次数,也就是有几条,
    // 这样就可以通过hashmap中的value值找到满足题意的点的对数啦
    // 最后在外层套一层循环即可
    int res = 0;
    for (int i = 0; i < points.length; i++) {
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int j = 0; j < points.length; j++) {
            if (i != j) {
                map.put(distance(points[i], points[j]), map.getOrDefault(distance(points[i], points[j]),0) + 1);
            }
        }

        for (int k : map.values()) {
            if (k >= 2)
                res += k*(k-1);   // 排列组合
        }
    }

    return res;
}

private int distance(int[] x, int[] y) {
    return (x[0] - y[0]) * (x[0] - y[0]) + (x[1] - y[1]) * (x[1] - y[1]);
}

复杂度分析

  • 时间复杂度:O(n2)
  • 空间复杂度:O(n) --- 存储两点间的距离

149 直线上最多的点数

题目描述

给定一个二维平面,平面上有 n 个点,求最多有多少个点在同一条直线上。

示例 1:

输入: [[1,1],[2,2],[3,3]]
输出: 3
解释:
^
|
|        o
|     o
|  o  
+------------->
0  1  2  3  4
示例 2:

输入: [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]]
输出: 4
解释:
^
|
|  o
|     o        o
|        o
|  o        o
+------------------->
0  1  2  3  4  5  6

解题思路

暴力破解:大体思路就是

1、异常处理
1.1 当点的个数不超过2时,肯定共线,直接返回点的数量即可
1.2 考虑所有点相等的情况,这样就可以看作所有点都在一条直线上
2、因为我们两点组成一条直线,必须保证这两个点不重合。所以我们进入第三层循环之前,如果两个点相等就可以直接跳过
3、采用化简等式两边分数的形式,找出最大公约数,比较分子分母是否相等,来判断三点是否共线

代码

// 暴力算法
public int maxPoints(int[][] points) {
    // 还有就是点的数量只有两个,或者一个,零个的时候,直接返回点的数量即可。
    if (points.length < 3) {
        return points.length;
    }
    // 我们还需要考虑所有点都相等的情况,这样就可以看做所有点都在一条直线上。
    int i = 0;
    for (; i < points.length - 1; i++) {
        if (points[i][0] != points[i + 1][0] || points[i][1] != points[i + 1][1]) {
            break;
        }

    }
    if (i == points.length - 1) {
        return points.length;
    }
    // 因为我们两点组成一条直线,必须保证这两个点不重合。所以我们进入第三层循环之前,如果两个点相等就可以直接跳过。
    int max = 0;
    for (i = 0; i < points.length; i++) {
        for (int j = i + 1; j < points.length; j++) {
            if (points[i][0] == points[j][0] && points[i][1] == points[j][1]) {
                continue;
            }
            int tempMax = 0;
            for (int k = 0; k < points.length; k++) {
                if (k != i && k != j) {
                    if (test(points[i][0], points[i][1], points[j][0], points[j][1], points[k][0], points[k][1])) {
                        tempMax++;
                    }
                }

            }
            if (tempMax > max) {
                max = tempMax;
            }
        }
    }
    //加上直线本身的两个点
    return max + 2;
}

//判断是否共线 此时采用的是最大公约数,约到最简形式,比较等号两侧分字分母是否相等即可
private boolean test(int x1, int y1, int x2, int y2, int x, int y) {
    int g1 = gcd(y2 - y1, x2 - x1);
    if(y == y2 && x == x2){
        return true;
    }
    int g2 = gcd(y - y2, x - x2);
    return (y2 - y1) / g1 == (y - y2) / g2 && (x2 - x1) / g1 == (x - x2) / g2;
}
// 求最大公约数
private int gcd(int a, int b) {
    while (b != 0) {
        int temp = a % b;
        a = b;
        b = temp;
    }
    return a;
}

复杂度分析

  • 时间复杂度:O(n3)
  • 空间复杂度:O(1)

效率比较低,毕竟是暴力算法。更多解法见 更多>>

posted @ 2020-08-27 22:39  BMDACM  阅读(212)  评论(0编辑  收藏  举报