算法-图算法总结

1 图的搜索

1.1 广度搜索

// 剑指 Offer II 105. 岛屿的最大面积
// 给定一个由 0 和 1 组成的非空二维数组 grid ,用来表示海洋岛屿地图。一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。找到给定的二维数组中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。
class Solution {
    public int maxAreaOfIsland(int[][] grid) {
        int maxArea = 0;
        boolean[][] visited = new boolean[grid.length][grid[0].length];
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                if (grid[i][j] == 1 && !visited[i][j]) {
                    int area = getArea(grid,visited,i,j);
                    maxArea = Math.max(maxArea,area);
                }
            }
        }
        return maxArea;
    }

    private int getArea(int[][] grid,boolean[][] visited,int oldI,int oldJ) {
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(new int[]{oldI,oldJ});
        visited[oldI][oldJ] = true;
        int[][] dicts = new int[][] {{0,1},{1,0},{0,-1},{-1,0}};
        int area = 0;
        while (!queue.isEmpty()) {
            int[] tmp = queue.poll();
            area++;
            for(int i = 0; i < 4; i++) {
                int newI = tmp[0] + dicts[i][0];
                int newJ = tmp[1] + dicts[i][1];
                if (newI >= 0 && newI < grid.length && newJ >= 0 && newJ < grid[0].length && 
                        grid[newI][newJ] == 1 && !visited[newI][newJ]) {
                    queue.offer(new int[]{newI,newJ});
                    visited[newI][newJ] = true;
                }
            }
        }
        return area;
    }
}
// 剑指 Offer II 106. 二分图
// 存在一个 无向图 ,图中有 n 个节点。其中每个节点都有一个介于 0 到 n - 1 之间的唯一编号。给定一个二维数组 graph ,表示图,其中 graph[u] 是一个节点数组,由节点 u 的邻接节点组成。形式上,对于 graph[u] 中的每个 v ,都存在一条位于节点 u 和节点 v 之间的无向边。该无向图同时具有以下属性:不存在自环(graph[u] 不包含 u)。不存在平行边(graph[u] 不包含重复值)。如果 v 在 graph[u] 内,那么 u 也应该在 graph[v] 内(该图是无向图)这个图可能不是连通图,也就是说两个节点 u 和 v 之间可能不存在一条连通彼此的路径。二分图 定义:如果能将一个图的节点集合分割成两个独立的子集 A 和 B ,并使图中的每一条边的两个节点一个来自 A 集合,一个来自 B 集合,就将这个图称为 二分图 。如果图是二分图,返回 true ;否则,返回 false 。
class Solution {
    public boolean isBipartite(int[][] graph) {
        int[] colors = new int[graph.length];
        Arrays.fill(colors,-1);
        for (int i = 0; i < graph.length; i++) {
            if (colors[i] == -1) {
                if (!setColor(graph,colors,i)) {
                    return false;
                }
            }
        }
        return true;
    }

    private boolean setColor(int[][] graph,int[] colors,int i) {
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(i);
        colors[i] = 0;
        while(!queue.isEmpty()) {
            int v = queue.poll();
            for(int item : graph[v]) {
                if (colors[item] >= 0) {
                    if (colors[item] == colors[v]) {
                        return false;
                    }
                } else {
                    colors[item] = 1 - colors[v];
                    queue.offer(item);
                }
            }
        }
        return true;
    }
}
// 剑指 Offer II 107. 矩阵中的距离
// 给定一个由 0 和 1 组成的矩阵 mat ,请输出一个大小相同的矩阵,其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。两个相邻元素间的距离为 1 。
class Solution {
    public int[][] updateMatrix(int[][] mat) {
        int[][] res = new int[mat.length][mat[0].length];
        Queue<int[]> queue = new LinkedList<>();
        for (int i = 0; i < mat.length; i++) {
            for (int j = 0; j < mat[0].length; j++) {
                if (mat[i][j] == 0) {
                    queue.offer(new int[]{i,j});
                    res[i][j] = 0;
                } else {
                    res[i][j] = Integer.MAX_VALUE;
                }
            }
        }
        while(!queue.isEmpty()) {
            int[] tmp = queue.poll();
            int curVal = res[tmp[0]][tmp[1]];
            int[][] dicts = new int[][] {{0,1},{0,-1},{1,0},{-1,0}};
            for (int[] dict : dicts) {
                int newI = tmp[0] + dict[0];
                int newJ = tmp[1] + dict[1];
                if (newI >= 0 && newI < mat.length && newJ >=0 && newJ < mat[0].length) {
                    if (res[newI][newJ] > curVal + 1) {
                        res[newI][newJ] = curVal + 1;
                        queue.offer(new int[] {newI,newJ});
                    }
                }
            }
        }
        return res;
    }
}
// 剑指 Offer II 108. 单词演变
// 在字典(单词列表) wordList 中,从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列:序列中第一个单词是 beginWord 。序列中最后一个单词是 endWord 。每次转换只能改变一个字母。转换过程中的中间单词必须是字典 wordList 中的单词。给定两个长度相同但内容不同的单词 beginWord 和 endWord 和一个字典 wordList ,找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。
class Solution {
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        Queue<String> queue = new LinkedList<>();
        Set<String> set = new HashSet<>(wordList);
        int len = 1;
        queue.offer(beginWord);
        while(!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                String curWord = queue.poll();
                if (curWord.equals(endWord)) {
                    return len;
                }
                List<String> neighbors = getNeighbors(curWord);
                for (String neighbor : neighbors) {
                    if (set.contains(neighbor)) {
                        queue.offer(neighbor);
                        set.remove(neighbor);
                    }
                }
            }
            len++;
        }
        return 0;
    }

    private List<String> getNeighbors(String word) {
        List<String> neighbors = new ArrayList<>();
        char[] chs = word.toCharArray();
        for (int i = 0; i < chs.length; i++) {
            char oldCh = chs[i];
            for (char j = 'a'; j <= 'z'; j++) {
                if (j != oldCh) {
                    chs[i] = j;
                    neighbors.add(new String(chs));
                }
            }
            chs[i] = oldCh;
        }
        return neighbors;
    }
}
// 剑指 Offer II 109. 开密码锁
// 一个密码锁由 4 个环形拨轮组成,每个拨轮都有 10 个数字: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' 。每个拨轮可以自由旋转:例如把 '9' 变为 '0','0' 变为 '9' 。每次旋转都只能旋转一个拨轮的一位数字。锁的初始数字为 '0000' ,一个代表四个拨轮的数字的字符串。列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。字符串 target 代表可以解锁的数字,请给出解锁需要的最小旋转次数,如果无论如何不能解锁,返回 -1 。
class Solution {
    public int openLock(String[] deadends, String target) {
        Queue<String> queue = new LinkedList<>();
        Set<String> dead = new HashSet<>(Arrays.asList(deadends));
        Set<String> visited = new HashSet<>();
        int len = 0;
        if (dead.contains("0000") || dead.contains(target)) {
            return -1;
        }
        queue.offer("0000");
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                String curStr = queue.poll();
                if (curStr.equals(target)) {
                    return len;
                }
                List<String> nextCodes = getNextCodes(curStr);
                for (String nextCode : nextCodes) {
                    if (!dead.contains(nextCode) && !visited.contains(nextCode)) {
                        queue.offer(nextCode);
                        visited.add(nextCode);
                    }
                }
            }
            len++;
        }
        return -1;
    }

    private List<String> getNextCodes(String str) {
        List<String> nextCodes = new ArrayList<>();
        char[] chs = str.toCharArray();
        for (int i = 0; i < chs.length; i++) {
            char oldCh = chs[i];
            chs[i] = oldCh == '0' ? '9' : (char)(oldCh - 1); //向上转锁
            nextCodes.add(new String(chs));
            chs[i] = oldCh == '9' ? '0' : (char)(oldCh + 1); //向下转锁
            nextCodes.add(new String(chs));
            chs[i] = oldCh;
        }
        return nextCodes;
    }
}

1.2 深度搜索

// 剑指 Offer II 110. 所有路径
// 给定一个有 n 个节点的有向无环图,用二维数组 graph 表示,请找到所有从 0 到 n-1 的路径并输出(不要求按顺序)。graph 的第 i 个数组中的单元都表示有向图中 i 号节点所能到达的下一些结点(译者注:有向图是有方向的,即规定了 a→b 你就不能从 b→a ),若为空,就是没有下一个节点了。
class Solution {

    private List<List<Integer>> res = new ArrayList<>();
    private List<Integer> path = new ArrayList<>();

    public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
        path.add(0);
        traversal(graph,0);
        return res;
    }

    private void traversal(int[][] graph,int curPoint) {
        if (curPoint == graph.length - 1) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (int nextPoint : graph[curPoint]) {
            path.add(nextPoint);
            traversal(graph,nextPoint);
            path.remove(path.size() - 1);
        }
    }
}
// 剑指 Offer II 111. 计算除法
// 给定一个变量对数组 equations 和一个实数值数组 values 作为已知条件,其中 equations[i] = [Ai, Bi] 和 values[i] 共同表示等式 Ai / Bi = values[i] 。每个 Ai 或 Bi 是一个表示单个变量的字符串。另有一些以数组 queries 表示的问题,其中 queries[j] = [Cj, Dj] 表示第 j 个问题,请你根据已知条件找出 Cj / Dj = ? 的结果作为答案。返回 所有问题的答案 。如果存在某个无法确定的答案,则用 -1.0 替代这个答案。如果问题中出现了给定的已知条件中没有出现的字符串,也需要用 -1.0 替代这个答案。注意:输入总是有效的。可以假设除法运算中不会出现除数为 0 的情况,且不存在任何矛盾的结果。
class Solution {
    public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
        Map<String,Map<String,Double>> map = buildGrid(equations,values);
        double[] res = new double[queries.size()];
        for (int i = 0; i < queries.size(); i++) {
            String from = queries.get(i).get(0);
            String to = queries.get(i).get(1);
            if (!map.containsKey(from) || !map.containsKey(to)) {
                res[i] = -1.0;
            } else {
                Set<String> visited = new HashSet<>();
                visited.add(from);
                res[i] = traversal(map,from,to,visited);
            }
        }
        return res;
    }

    private double traversal( Map<String,Map<String,Double>> map,String from,String to,Set<String> visited) {
        if (from.equals(to)) {
            return 1.0;
        }
        for(Map.Entry<String,Double> entry : map.get(from).entrySet()) {
            String next = entry.getKey();
            if (!visited.contains(next)) {
                visited.add(next);
                double nextVal = traversal(map,next,to,visited);
                visited.remove(next);
                if (nextVal > 0) {
                    return entry.getValue() * nextVal;
                }
            }
        }
        return -1.0;
    }

    private Map<String,Map<String,Double>> buildGrid(List<List<String>> equations,double[] values) {
        Map<String,Map<String,Double>> res = new HashMap<>();
        for (int i = 0; i < equations.size(); i++) {
            String var1 = equations.get(i).get(0);
            String var2 = equations.get(i).get(1);
            res.putIfAbsent(var1,new HashMap<>());
            res.get(var1).put(var2,values[i]);
            res.putIfAbsent(var2,new HashMap<>());
            res.get(var2).put(var1,1.0/values[i]);
        }
        return res;
    }
}
// 剑指 Offer II 112. 最长递增路径
// 给定一个 m x n 整数矩阵 matrix ,找出其中 最长递增路径 的长度。对于每个单元格,你可以往上,下,左,右四个方向移动。 不能 在 对角线 方向上移动或移动到 边界外(即不允许环绕)。
class Solution {
    public int longestIncreasingPath(int[][] matrix) {
        int maxLen = 0;
        int[][] lengths = new int[matrix.length][matrix[0].length];
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[0].length; j++) {
                int len = traversal(matrix,lengths,i,j);
                maxLen = Math.max(maxLen,len);
            }
        }
        return maxLen;
    }

    private int traversal(int[][] arr,int[][] lengths,int i,int j) {
        if (lengths[i][j] != 0) {
            return lengths[i][j];
        }
        int len = 1;
        int[][] dicts = new int[][] {{0,1},{0,-1},{1,0},{-1,0}};
        for (int[] dict : dicts) {
            int newI = i + dict[0];
            int newJ = j + dict[1];
            if (newI >= 0 && newI < arr.length && newJ >= 0 && newJ < arr[0].length && arr[newI][newJ] > arr[i][j]) {
                int path = traversal(arr,lengths,newI,newJ);
                len = Math.max(len,path + 1);
            }
        }
        lengths[i][j] = len;
        return len;
    }
}

2 拓扑排序

// 剑指 Offer II 113. 课程顺序
// 现在总共有 numCourses 门课需要选,记为 0 到 numCourses-1。给定一个数组 prerequisites ,它的每一个元素 prerequisites[i] 表示两门课程之间的先修顺序。 例如 prerequisites[i] = [ai, bi] 表示想要学习课程 ai ,需要先完成课程 bi 。请根据给出的总课程数  numCourses 和表示先修顺序的 prerequisites 得出一个可行的修课序列。可能会有多个正确的顺序,只要任意返回一种就可以了。如果不可能完成所有课程,返回一个空数组。
class Solution {
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        Map<Integer,List<Integer>> map = new HashMap<>();
        for (int i = 0; i < numCourses; i++) {
            map.put(i,new ArrayList<>());
        }
        int[] inDegree = new int[numCourses];
        for (int[] prerequisite : prerequisites) {
            map.get(prerequisite[1]).add(prerequisite[0]);
            inDegree[prerequisite[0]]++;
        }
        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < inDegree.length; i++) {
            if (inDegree[i] == 0) {
                queue.offer(i);
            }
        }
        List<Integer> resList = new ArrayList<>();
        while (!queue.isEmpty()) {
            int preCourse = queue.poll();
            resList.add(preCourse);
            for (int next : map.get(preCourse)) {
                inDegree[next]--;
                if (inDegree[next] == 0) {
                    queue.offer(next);
                }
            }
        }
        if (resList.size() == numCourses) {
            int[] res = new int[numCourses];
            int index = 0;
            for (int i = 0; i < resList.size(); i++) {
                res[index++] = resList.get(i);
            }
            return res;
        } 
        return new int[0];
    }
}
// 剑指 Offer II 114. 外星文字典
// 现有一种使用英语字母的外星文语言,这门语言的字母顺序与英语顺序不同。给定一个字符串列表 words ,作为这门语言的词典,words 中的字符串已经 按这门新语言的字母顺序进行了排序 。请你根据该词典还原出此语言中已知的字母顺序,并 按字母递增顺序 排列。若不存在合法字母顺序,返回 "" 。若存在多种可能的合法字母顺序,返回其中 任意一种 顺序即可。字符串 s 字典顺序小于 字符串 t 有两种情况:在第一个不同字母处,如果 s 中的字母在这门外星语言的字母顺序中位于 t 中字母之前,那么 s 的字典顺序小于 t 。如果前面 min(s.length, t.length) 字母都相同,那么 s.length < t.length 时,s 的字典顺序也小于 t 。
class Solution {
    public String alienOrder(String[] words) {
        Map<Character,Set<Character>> map = new HashMap<>();
        Map<Character,Integer> inDegree = new HashMap<>();
        for (String word : words) {
            for (Character ch : word.toCharArray()) {
                map.putIfAbsent(ch,new HashSet<>());
                inDegree.putIfAbsent(ch,0);
            }
        }
        for (int i = 1; i < words.length; i++) {
            String preWord = words[i - 1];
            String curWord = words[i];
            if (preWord.startsWith(curWord) && !preWord.equals(curWord)) {
                return "";
            }
            for (int j = 0; j < preWord.length() && j < curWord.length(); j++) {
                char c1 = preWord.charAt(j);
                char c2 = curWord.charAt(j);
                if (c1 != c2) {
                    if (!map.get(c1).contains(c2)) {
                        map.get(c1).add(c2);
                        inDegree.put(c2,inDegree.get(c2) + 1);
                    }
                    break;
                }
            }
        }
        Queue<Character> queue = new LinkedList<>();
        for (char ch : inDegree.keySet()) {
            if (inDegree.get(ch) == 0) {
                queue.offer(ch);
            }
        }
        StringBuilder sb = new StringBuilder();
        while(!queue.isEmpty()) {
            char ch = queue.poll();
            sb.append(ch);
            for(Character c : map.get(ch)) {
                inDegree.put(c,inDegree.get(c) - 1);
                if (inDegree.get(c) == 0) {
                    queue.offer(c);
                }
            }
        }
        return sb.length() == map.size() ? sb.toString() : "";
    }
}
// 剑指 Offer II 115. 重建序列
// 请判断原始的序列 org 是否可以从序列集 seqs 中唯一地 重建 。序列 org 是 1 到 n 整数的排列,其中 1 ≤ n ≤ 104。重建 是指在序列集 seqs 中构建最短的公共超序列,即  seqs 中的任意序列都是该最短序列的子序列。
class Solution {
    public boolean sequenceReconstruction(int[] org, List<List<Integer>> seqs) {
        Map<Integer,Set<Integer>> map = new HashMap<>();
        Map<Integer,Integer> inDegree = new HashMap<>();
        for (List<Integer> seq : seqs) {
            for (Integer num : seq) {
                if (num < 1 || num > org.length) {
                    return false;
                }
                map.putIfAbsent(num,new HashSet<>());
                inDegree.putIfAbsent(num,0);
            }
            for (int i = 1; i < seq.size(); i++) {
                int preNum = seq.get(i - 1);
                int curNum = seq.get(i);
                if (!map.get(preNum).contains(curNum)) {
                    map.get(preNum).add(curNum);
                    inDegree.put(curNum,inDegree.get(curNum) + 1);
                }
            }
        }
        Queue<Integer> queue = new LinkedList<>();
        for (Integer num : inDegree.keySet()) {
            if (inDegree.get(num) == 0) {
                queue.offer(num);
            }
        }
        List<Integer> resList = new ArrayList<>();
        while (queue.size() == 1) {
            Integer num = queue.poll();
            resList.add(num);
            for (Integer next : map.get(num)) {
                inDegree.put(next,inDegree.get(next) - 1);
                if (inDegree.get(next) == 0) {
                    queue.offer(next);
                }
            }
        }
        int[] res = new int[resList.size()];
        int index = 0;
        for (int i = 0; i < resList.size(); i++) {
            res[index++] = resList.get(i);
        }
        return Arrays.equals(res,org);
    }
}

3 并查集

// 剑指 Offer II 116. 省份数量
// 有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。返回矩阵中 省份 的数量。
class Solution {
    public int findCircleNum(int[][] isConnected) {
        int[] father = new int[isConnected.length];
        for (int i = 0; i < father.length; i++) {
            father[i] = i;
        }
        for (int i = 0; i < isConnected.length; i++) {
            for (int j = i + 1; j < isConnected[0].length; j++) {
                if (isConnected[i][j] == 1) {
                    union(father,i,j);
                }
            }
        }
        int count = 0;
        for (int i = 0; i < father.length; i++) {
            if (father[i] == i) {
                count++;
            }
        }
        return count;
    }

    private int findFather(int[] father,int i) {
        if (father[i] != i) {
            father[i] = findFather(father,father[i]);
        }
        return father[i];
    }

    private void union(int[] father,int i,int j) {
        int fatherI = findFather(father,i);
        int fatherJ = findFather(father,j);
        if (fatherI != fatherJ) {
            father[fatherI] = fatherJ;
        }
    }
}
// 剑指 Offer II 117. 相似的字符串
// 如果交换字符串 X 中的两个不同位置的字母,使得它和字符串 Y 相等,那么称 X 和 Y 两个字符串相似。如果这两个字符串本身是相等的,那它们也是相似的。例如,"tars" 和 "rats" 是相似的 (交换 0 与 2 的位置); "rats" 和 "arts" 也是相似的,但是 "star" 不与 "tars","rats",或 "arts" 相似。总之,它们通过相似性形成了两个关联组:{"tars", "rats", "arts"} 和 {"star"}。注意,"tars" 和 "arts" 是在同一组中,即使它们并不相似。形式上,对每个组而言,要确定一个单词在组中,只需要这个词和该组中至少一个单词相似。给定一个字符串列表 strs。列表中的每个字符串都是 strs 中其它所有字符串的一个 字母异位词 。请问 strs 中有多少个相似字符串组?字母异位词(anagram),一种把某个字符串的字母的位置(顺序)加以改换所形成的新词。
class Solution {
    public int numSimilarGroups(String[] strs) {
        int[] father = new int[strs.length];
        for (int i = 0; i < father.length; i++) {
            father[i] = i;
        }
        for (int i = 0; i < strs.length; i++) {
            for (int j = i + 1; j < strs.length; j++) {
                if (similar(strs[i],strs[j])) {
                    union(father,i,j);
                }
            }
        }
        int count = 0;
        for (int i = 0; i < father.length; i++) {
            if (father[i] == i) {
                count++;
            }
        }
        return count;
    }

    private boolean similar(String str1,String str2) {
        int diffCount = 0;
        for (int i = 0; i < str1.length(); i++) {
            if (str1.charAt(i) != str2.charAt(i)) {
                diffCount++;
            }
        }
        return diffCount == 2 || diffCount == 0;
    }

    private int findFather(int[] father,int i) {
        if (father[i] != i) {
            father[i] = findFather(father,father[i]);
        }
        return father[i];
    }

    private void union(int[] father,int i,int j) {
        int fatherI = findFather(father,i);
        int fatherJ = findFather(father,j);
        if (fatherI != fatherJ) {
            father[fatherI] = fatherJ;
        }
    }
}
// 剑指 Offer II 118. 多余的边
// 树可以看成是一个连通且 无环 的 无向 图。给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 表示图中在 ai 和 bi 之间存在一条边。请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的边。
class Solution {
    public int[] findRedundantConnection(int[][] edges) {
        int maxVal = 0;
        for (int[] edge : edges) {
            maxVal = Math.max(maxVal,Math.max(edge[0],edge[1]));
        }
        int[] father = new int[maxVal + 1];
        for (int i = 1; i < father.length; i++) {
            father[i] = i;
        }
        for (int[] edge : edges) {
            int father1 = findFather(father,edge[0]);
            int father2 = findFather(father,edge[1]);
            if (father1 == father2) {
                return edge;
            }
            union(father,father1,father2);
        }
        return new int[2];
    }

    private int findFather(int[] father, int i) {
        if (father[i] != i) {
            father[i] = findFather(father,father[i]);
        }
        return father[i];
    }

    private void union(int[] father,int i,int j) {
        int fatherI = findFather(father,i);
        int fatherJ = findFather(father,j);
        if (fatherI != fatherJ) {
            father[fatherI] = fatherJ;
        }
    }
}
// 剑指 Offer II 119. 最长连续序列
// 给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
class Solution {
    public int longestConsecutive(int[] nums) {
        Map<Integer,Integer> father = new HashMap<>();
        Map<Integer,Integer> counts = new HashMap<>();
        Set<Integer> allNums = new HashSet<>();
        for (int num : nums) {
            father.put(num,num);
            counts.put(num,1);
            allNums.add(num);
        }
        for (int num : nums) {
            if (allNums.contains(num - 1)) {
                union(father,counts,num,num - 1);
            }
            if (allNums.contains(num + 1)) {
                union(father,counts,num,num + 1);
            }
        }
        int maxLen = 0;
        for(int len : counts.values()) {
            maxLen = Math.max(maxLen,len);
        }
        return maxLen;
    }

    private int findFather(Map<Integer,Integer> father,int i) {
        if (father.get(i) != i) {
            father.put(i,findFather(father,father.get(i)));
        }
        return father.get(i);
    }

    private void union(Map<Integer,Integer> father,Map<Integer,Integer> counts,int i,int j) {
        int fatherI = findFather(father,i);
        int fatherJ = findFather(father,j);
        if (fatherI != fatherJ) {
            father.put(fatherI,fatherJ);
            int countI = counts.get(fatherI);
            int countJ = counts.get(fatherJ);
            counts.put(fatherJ,countI + countJ);
        }
    } 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

攻城老湿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值