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

2022/2/28 23:26:52

本文主要是介绍leetcode之并查集+记忆化搜索+回溯+最小生成树刷题总结1,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

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 ;
    }
}

在这里插入图片描述



这篇关于leetcode之并查集+记忆化搜索+回溯+最小生成树刷题总结1的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程