ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

leetcode之并查集+记忆化搜索+回溯+最小生成树刷题总结1

2022-02-28 22:01:17  阅读:145  来源: 互联网

标签:return parent get int 查集 leetcode 树刷题 new public


leetcode之并查集+记忆化搜索+回溯+最小生成树刷题总结1

1-连接所有点的最小费用
题目连接:题目连接戳这里!!!

思路:cruskal+并查集
根据题意,我们得到了一张 n 个节点的完全图,任意两点之间的距离均为它们的曼哈顿距离。现在我们需要在这个图中取得一个子图,恰满足子图的任意两点之间有且仅有一条简单路径,且这个子图的所有边的总权值之和尽可能小。能够满足任意两点之间有且仅有一条简单路径只有树,且这棵树包含 n 个节点。我们称这棵树为给定的图的生成树,其中总权值最小的生成树,我们称其为最小生成树。
最小生成树有一个非常经典的解法:Kruskal。

我们首先将这张完全图中的边全部提取到边集数组中,然后对所有边进行排序,从小到大进行枚举,每次贪心选边加入答案。使用并查集维护连通性,若当前边两端不连通即可选择这条边。

class Solution {
    public int minCostConnectPoints(int[][] points) {
        int n = points.length ;
        DisjointSetUnion dsu = new DisjointSetUnion(n) ;
        List<Edge> edges = new ArrayList<Edge>() ;
        for(int i=0; i<n; i++){
            for(int j=0; j<n; j++){
                edges.add(new Edge(dist(points,i,j),i,j)) ;
            }
        }

        Collections.sort(edges, new Comparator<Edge>(){ //对所有边进行由小到大排序
            public int compare(Edge edge1, Edge edge2){
                return edge1.len - edge2.len ;
            }
        }) ;

        int ans = 0, num = 1 ;
        for(Edge edge : edges){
            int len = edge.len, x = edge.x, y = edge.y ;
            if(dsu.unionSet(x,y)){
                ans += len;
                num ++ ;
                if(num == n){
                    break ;
                }
            }
        }
        return ans ;
    }
    public int dist(int [][] points, int x, int y){
        return Math.abs(points[x][0]-points[y][0])+Math.abs(points[x][1]-points[y][1]) ;
    }
}
//使用并查集检查是否联通

class DisjointSetUnion{
    int n ;
    int [] f ;
    public DisjointSetUnion(int n){
        this.n = n ;
        this.f = new int [n] ;
        for(int i=0; i<n; i++){
            f[i] = i ;
        }
    }
    public int find(int x){
        if(x!=f[x]){
            f[x] = find(f[x]) ;
        }
        return f[x] ;
    }
    public boolean unionSet(int x, int y){
        int fx = find(x), fy = find(y) ;
        if(fx==fy){
            return false ;
        }
        f[fx] = fy ;
        return true ;
    }
}

class Edge{ //定义一个边类
    int len,  x, y ;
    public Edge(int len, int x, int y){
        this.len = len ;
        this.x = x ;
        this.y = y ;
    }
}


在这里插入图片描述

2-最少体力消耗路径
题目链接:题目连接戳这里!!!

思路:并查集
我们将这 m*n 个节点放入并查集中,实时维护它们的连通性。

由于我们需要找到从左上角到右下角的最短路径,因此我们可以将图中的所有边按照权值从小到大进行排序,并依次加入并查集中。当我们加入一条权值为 x 的边之后,如果左上角和右下角从非连通状态变为连通状态,那么 x 即为答案。

class Solution {
    public int minimumEffortPath(int[][] heights) {
        int m = heights.length ;
        int n = heights[0].length ;
        List<int[]> edges = new ArrayList<int[]>() ;
        for(int i=0; i<m; i++){
            for(int j=0; j<n; j++){
                int id = i * n + j ;
                if(i>0){
                    edges.add(new int [] {id-n, id, Math.abs(heights[i][j]-heights[i-1][j])}) ;
                }
                if(j>0){
                    edges.add(new int [] {id-1, id, Math.abs(heights[i][j]-heights[i][j-1])}) ;
                }
            }
        }
        Collections.sort(edges, new Comparator<int[]>(){ //按照边的权值由小到大排序
            public int compare(int [] edge1, int [] edge2){
                return edge1[2] - edge2[2] ;
            }
        }) ;

        UnionFind uf = new UnionFind(m*n) ;
        int ans = 0 ;
        for(int [] edge : edges){
            int x = edge[0], y = edge[1], v = edge[2] ;
            uf.unite(x,y) ;
            if(uf.connected(0,m*n-1)){
                ans = v ;
                break ;
            }
        }
        return ans ;
    }
}

// 并查集模板
class UnionFind {
    int[] parent;
    int n ;

    public UnionFind(int n) {
        this.n = n;
        this.parent = new int[n];

        for (int i = 0; i < n; ++i) {
            parent[i] = i;
        }
    }
    
    public int findset(int x) {
        return parent[x] == x ? x : (parent[x] = findset(parent[x]));
    }
    
    public boolean unite(int x, int y) {
        x = findset(x);
        y = findset(y);
        if (x == y) {
            return false;
        }
        parent[y] = x;
    
        return true;
    }
    
    
    public boolean connected(int x, int y) {
        x = findset(x);
        y = findset(y);
        return x == y;
    }
}

在这里插入图片描述
3-交换字符串中的元素
题目链接:题目链接戳这里!!!

思路:并查集
1-并查集对每个元素索引,实现属于同一联通分量元素的合并
2-遍历字符串s,找出每个索引在并查集中的代表元,同属于一个代表元的元素放到一起。
3-对同属于每一个联通分量中的字符按照ASCII码进行升序排序。
4-重新生成一个长度和s相同的字符串,对于每一个索引,查询索引在并查集中的代表元,再从哈希表中获得这个代表元对应的字符集列表,从中移除 ASCII 值最小的字符依次拼接起来。



public class Solution {

    public String smallestStringWithSwaps(String s, List<List<Integer>> pairs) {
        if (pairs.size() == 0) {//没有交换
            return s;
        }
        int n = s.length() ;
        UnionFind uf = new UnionFind(n) ;
        for(int i=0; i<pairs.size(); i++){ //根据索引构建并查集
            int index1 = pairs.get(i).get(0) ;
            int index2 = pairs.get(i).get(1) ;
            uf.union(index1,index2) ;
        }
        char [] c = s.toCharArray() ;
        Map<Integer,PriorityQueue<Character>> map = new HashMap<>(n) ;
        for(int i=0; i<n; i++){ //遍历元素,找到代表元,并代表元相同的放到一个集合,并按字典序排序
        int root = uf.find(i) ;
        if(map.containsKey(root)){
            map.get(root).add(c[i])  ;
        }else{
            PriorityQueue<Character> minHeap = new PriorityQueue<>() ;
            minHeap.add(c[i]) ;
            map.put(root,minHeap) ;
        }
        }

        StringBuilder ans = new StringBuilder() ;
        for(int i=0; i<n; i++){
            int root = uf.find(i) ;
            ans.append(map.get(root).poll()) ;
        }
        return ans.toString() ;

    }
}
class UnionFind{
    int n ;
    int [] parent ;
    public UnionFind(int n){
        this.n = n ;
        this.parent = new int [n] ;
        for(int i=0; i<n; i++){
            parent[i] = i ;
        }
    }
    public int find(int x){
        if(x != parent[x]){
            parent[x] = find(parent[x]) ;
        }
        return parent[x] ;
    }
    public void union(int x, int y){
        int root1 = find(x) ;
        int root2 = find(y) ;
        if(root1==root2){
            return ;
        }
        parent[root1] = root2 ;
    }
}

在这里插入图片描述
4-可能的二分法
题目链接:题目链接戳这里!!!

思路1:邻接表+并查集
1-对于每个不喜欢的建立双向邻接表,将每个人,不喜欢的放到一个集合里。
2-对每个集合的元素进行并查集合并。
3-如果对于每个元素,其邻接元素与自己在同一个集合,则返回false,否则返回true.

class Solution {
    public boolean possibleBipartition(int n, int[][] dislikes) {
        if(dislikes.length==0){
            return true ;
        }
        Map<Integer, List<Integer>> edges = new HashMap<>() ;
        UnionFind uf = new UnionFind(n) ;
        for(int [] dislike : dislikes){
          List<Integer> temp1 = edges.getOrDefault(dislike[0],new ArrayList<>()) ;
          temp1.add(dislike[1]) ;
          edges.put(dislike[0],temp1) ;
          List<Integer> temp2 = edges.getOrDefault(dislike[1], new ArrayList<>()) ;
          temp2.add(dislike[0]) ;
          edges.put(dislike[1],temp2) ;
        }
        for(int i : edges.keySet()){
            List<Integer> list = edges.get(i) ;
            for(int neighbors : list){
                if(uf.find(neighbors) == uf.find(i)){
                    return false ;
                }
                uf.union(neighbors,list.get(0)) ;
            }
        }
        return true ;
    }
}
 class UnionFind{
        int n ;
        int [] parent ;
        public UnionFind(int n){
            this.n = n ;
            this.parent = new int [n+1] ;
            for(int i=1; i<=n; i++){
                parent[i] = i ;
            }
        }
        public int find(int x){
            if(x != parent[x]){
                parent[x] = find(parent[x]) ;
            }
            return parent[x] ;
        }
        public void union(int x, int y){
            int root1 = find(x) ;
            int root2 = find(y) ;
            if(root1==root2){
                return ;
            }
            parent[root1] = root2 ;
        }
    }

在这里插入图片描述
思路2:邻接表+dfs+染色法

尝试将每个人分配到一个组是很自然的想法。假设第一组中的人是红色,第二组中的人是蓝色。

如果第一个人是红色的,那么这个人不喜欢的人必须是蓝色的。然后,任何不喜欢蓝色人的人都是红色的,那么任何不喜欢红色人的人都是蓝色的,依此类推。

如果在任何时候存在冲突,那么这个任务是不可能的完成的,因为从第一步开始每一步都符合逻辑。如果没有冲突,那么着色是有效的,所以答案是 true。

class Solution {
    Map<Integer,Integer> map ;
    List<List<Integer>> list = new ArrayList<>() ;
    public boolean possibleBipartition(int n, int[][] dislikes) {
        if(dislikes.length==0){
            return true ;
        }
        //dfs+染色法
        for(int i=0; i<=n; i++){
            list.add(new ArrayList<>()) ;
        }
        for(int [] dislike : dislikes){
            list.get(dislike[0]).add(dislike[1]) ;
            list.get(dislike[1]).add(dislike[0]) ;
        }
        map = new HashMap<>() ;
        for(int i=1; i<=4; i++){
            if(!map.containsKey(i) && !dfs(i,0)){
                return false ;
            }
        }
        return true ;
        }
        public boolean dfs(int i, int color){
            if(map.containsKey(i)){
                return map.get(i) == color ;
            }
            map.put(i,color) ;
            for(int neighbors : list.get(i)){
                if(!dfs(neighbors,color^1)){
                    return false ;
                }
            }
            return true ;
        }
    }

在这里插入图片描述
5-将整数按权重进行排列
题目链接:题目链接戳这里!!!

思路1:递归法求出每个数字的权重,权重不同时候,根据权重升序排序,权重相同根据整数升序排序。

class Solution {
    public int getKth(int lo, int hi, int k) {
        //递归法
        List<Integer> list = new ArrayList<>() ;
        for(int i=lo; i<=hi; i++){
            list.add(i) ;
        }
        Collections.sort(list, new Comparator<Integer>(){
            public int compare(Integer u, Integer v){
            if(f(u) != f(v)){
                return f(u) - f(v) ;
            }else{
                return u - v ;
            }
            }
        }) ;
        return list.get(k-1) ;
    }

    public int f(int x){
        if(x==1){
            return 0 ;
        }else if(x%2==0){
            return 1 + f(x/2) ;
        }else{
            return 1 + f(3*x+1) ;
        }
    }
}

在这里插入图片描述
思路2:记忆化搜索
用hashmap去存储已经搜索过的,防止重复搜索。

class Solution {
    Map<Integer, Integer> map = new HashMap<>() ;
    public int getKth(int lo, int hi, int k) {
        //递归法
        List<Integer> list = new ArrayList<>() ;
        for(int i=lo; i<=hi; i++){
            list.add(i) ;
        }
        Collections.sort(list, new Comparator<Integer>(){
            public int compare(Integer u, Integer v){
            if(f(u) != f(v)){
                return f(u) - f(v) ;
            }else{
                return u - v ;
            }
            }
        }) ;
        return list.get(k-1) ;
    }

    public int f(int x){
     if(!map.containsKey(x)){
         if(x==1){
             map.put(x,0) ;
         }else if(x%2==0){
             map.put(x,f(x/2)+1) ;
         }else{
             map.put(x,f(3*x+1)+1) ;
         }
     }
     return map.get(x) ;
  
    }
}

在这里插入图片描述
6-将数组拆分成斐波那契数列
题目链接:题目链接戳这里!!!

思路:回溯+剪支

对于这道题也一样,我们先把字符串不断的截取,看一下能不能构成斐波那契序列,如果不能就回到上一步,如果能就继续往下走。

class Solution {
    public List<Integer> splitIntoFibonacci(String num) {
       //回溯+剪支
       List<Integer> res = new ArrayList<>() ;
       char [] c = num.toCharArray() ;
       backtrace(res,c,0) ; 
       return res ;
    }
    public boolean backtrace(List<Integer> res, char [] c, int index){
        if(index==c.length && res.size()>=3){ //找到满足的组合
            return true ;
        }
        for(int i=index; i<c.length; i++){
            if(c[index]=='0' && i>index){ //两位数以上,0不能作首位
                break ;
            }
            long num = sub(c, index, i) ;
            if(num>Integer.MAX_VALUE){ //超出最大值
                break ;
            }
            int size = res.size() ;
            if(size>=2 && num > res.get(size-1)+res.get(size-2)){ //后面截取的更大,不可能满足要求
                break ;
            }
            if(size<=1 || num == res.get(size-1) + res.get(size-2)){//满足要求
                res.add((int)num) ;
                if(backtrace(res,c,i+1)){
                    return true ;
                }
                res.remove(res.size()-1) ;
            }
        }
        return false ;
    }
    public long sub(char [] c, int start, int end){ //截取字符串的数值
        long ans = 0 ;
        for(int i=start; i<=end; i++){
            ans = ans * 10 + c[i] -'0' ;
        }
        return ans ;
    }
}

在这里插入图片描述

标签:return,parent,get,int,查集,leetcode,树刷题,new,public
来源: https://blog.csdn.net/nuist_NJUPT/article/details/123174259

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有