Java&C++题解与拓展——leetcode433.最小基因变化【A star学习、双向BFS(C++超时)、链式前向星(报错)】

每日一题做题记录,参考官方和三叶的题解

题目要求

在这里插入图片描述

思路一:BFS

  • 找从 s t a r t start start状态到 e n d end end状态的最小步数;
  • 用哈希表存每个状态和步数;
  • 维护一个遍历队列,取队头元素 c u r cur cur,依次替换每一位得到下一个可能状态,合法(出现在bank中)即加入队列并更新步数;
  • 直到下一个可能状态与 e n d end end相等,返回 s t e p + 1 step+1 step+1

Java

class Solution {
    static char[] items = new char[]{'A', 'C', 'G', 'T'};
    public int minMutation(String start, String end, String[] bank) {
        Set<String> bankSet = new HashSet<>();
        for(String s : bank)
            bankSet.add(s);

        Deque<String> que = new ArrayDeque<>();
        Map<String, Integer> map = new HashMap<>();
        que.addLast(start);
        map.put(start, 0); // 步数为0

        while(!que.isEmpty()) {
            String cur = que.pollFirst();
            char[] cs = cur.toCharArray();
            int step = map.get(cur);
            for(int i = 0; i < 8; i++) {
                for(char c : items) {
                    if(cs[i] == c)
                        continue;
                    char[] clone = cs.clone();
                    clone[i] = c; // 换第i位为c
                    String nxt = String.valueOf(clone);
                    if(!bankSet.contains(nxt)) // 是否合法出现在bank里
                        continue;
                    if(map.containsKey(nxt)) // 是否记录过
                        continue;
                    if(nxt.equals(end)) // 是否到结尾
                        return step + 1;
                    map.put(nxt, step + 1); // 更新步数
                    que.addLast(nxt); // 压入队列
                }
            }
        }
        return -1;
    }
}
  • 时间复杂度: O ( C × n ) O(C\times n) O(C×n),其中 C = 32 = 8 × 4 C=32=8\times4 C=32=8×4,即四个基因分别替换八个位置
  • 空间复杂度: O ( n ) O(n) O(n)

C++

static char items[4] = {'A', 'C', 'G', 'T'};
class Solution {
public:
    int minMutation(string start, string end, vector<string>& bank) {
        queue<string> que;
        unordered_map<string, int> map;
        que.push(start);
        map[start] = 0; // 步数为0

        while(!que.empty()) {
            string cur = que.front();
            que.pop();
            int step = map[cur];
            for(int i = 0; i < 8; i++) {
                for(char c :items) {
                    if(cur[i] == c)
                        continue;
                    string nxt = cur;
                    nxt[i] = c; // 换第i位为c
                    if(find(bank.begin(), bank.end(), nxt) == bank.end()) // 是否合法出现在bank里
                        continue;
                    if(map.count(nxt)) // 是否记录过
                        continue;
                    if(nxt == end) // 是否到结尾
                        return step + 1;
                    map[nxt] = step + 1;  // 更新步数
                    que.push(nxt);
                    
                }
            }
        }
        return -1;
    }
};
  • 时间复杂度: O ( C × n ) O(C\times n) O(C×n),其中 C = 32 = 8 × 4 C=32=8\times4 C=32=8×4,即四个基因分别替换八个位置
  • 空间复杂度: O ( n ) O(n) O(n)

思路二:双向BFS

  • BFS可能出现搜索空间爆炸问题,即当前层宽度超级大;
  • 从头尾分别开始搜索,直至当前遍历值出现在对面。

Java

class Solution {
    String start, end;
    Set<String> bankSet = new HashSet<>();
    static char[] items = new char[]{'A', 'C', 'G', 'T'};
    public int minMutation(String start, String end, String[] bank) {
        this.start = start;
        this.end = end;
        for(String s : bank)
            bankSet.add(s);
        return bfs();
    }

    int bfs() {
        Deque<String> que1 = new ArrayDeque<>(), que2 = new ArrayDeque<>();
        Map<String, Integer> map1 = new HashMap<>(), map2 = new HashMap<>();
        que1.addLast(start);
        map1.put(start, 0);
        que2.addLast(end);
        map2.put(end, 0);
        while(!que1.isEmpty() && !que2.isEmpty()) {
            int step = -1;
            if(que1.size() <= que2.size()) // 平均二者空间
                step = update(que1, map1, map2);
            else
                step = update(que2, map2, map1);
            if(step != -1)
                return step;
        }
        return -1;
    }

    int update(Deque<String> que, Map<String, Integer> thisMap, Map<String, Integer> thatMap) {
        int size = que.size();
        while(size-- > 0) {
            String cur = que.pollFirst();
            char[] cs = cur.toCharArray();
            int step = thisMap.get(cur);
            for(int i = 0; i < 8; i++) {
                for(char c : items) {
                    if(cs[i] == c)
                        continue;
                    char[] clone = cs.clone();
                    clone[i] = c; // 换第i为为c
                    String nxt = String.valueOf(clone);
                    if(!bankSet.contains(nxt)) // 是否合法出现在bank里
                        continue;
                    if(thatMap.containsKey(nxt)) // 是否在另一边遍历过
                        return step + 1 + thatMap.get(nxt);
                    if(!thisMap.containsKey(nxt) || thisMap.get(nxt) > step + 1) { // 更新队列
                        thisMap.put(nxt, step + 1);
                        que.addLast(nxt);
                    }
                }
            }
        }
        return -1;
    }
}
  • 时间复杂度: O ( C × n ) O(C\times n) O(C×n),其中 C = 32 = 8 × 4 C=32=8\times4 C=32=8×4,即四个基因分别替换八个位置
  • 空间复杂度: O ( n ) O(n) O(n)

C++

【不道为啥超时间限制了……调了半天……就糊弄了】

static char items[4] = {'A', 'C', 'G', 'T'};
class Solution {
    string start, end;
    vector<string> bank;
public:
    int minMutation(string start, string end, vector<string>& bank) {
        this->start = start;
        this->end = end;
        this->bank = bank;
        return bfs();
    }

    int bfs() {
        queue<string> que1, que2;
        unordered_map<string, int> map1, map2;
        que1.push(start);
        map1[start] = 0;
        que2.push(end);
        map2[end] = 0;
        while(!que1.empty() && !que2.empty()) {  // 平均二者空间
            int step = -1;
            if(que1.size() <= que2.size())
                step = update(que1, map1, map2);
            else
                step = update(que2, map2, map1);
            if(step != -1)
                return step;
        }
        return -1;
    }

    int update(queue<string> que, unordered_map<string, int> thisMap, unordered_map<string, int> thatMap) {
        int size = que.size();
        while(size-- > 0) {
            string cur = que.front();
            que.pop();
            int step = thisMap[cur];
            for(int i = 0; i < 8; i++) {
                for(char c : items) {
                    if(cur[i] == c)
                        continue;
                    string nxt = cur;
                    nxt[i] = c; // 换第i为为c
                    if(find(bank.begin(), bank.end(), nxt) == bank.end()) // 是否合法出现在bank里
                        continue;
                    if(thatMap.count(nxt)) // 是否在另一边遍历过
                        return step + 1 + thatMap[nxt];
                    if(!thisMap.count(nxt) || thisMap[nxt] > step + 1) { // 更新队列
                        thisMap[nxt] = step + 1;
                        que.push(nxt);
                    }
                }
            }
        }
        return -1;
    }
};
  • 时间复杂度: O ( C × n ) O(C\times n) O(C×n),其中 C = 32 = 8 × 4 C=32=8\times4 C=32=8×4,即四个基因分别替换八个位置
  • 空间复杂度: O ( n ) O(n) O(n)

思路三:A*

  • 计算最小转换步数作为启发式函数,进行启发式搜索;
  • 使用优先队列维护状态, 启 发 值 = 最 小 转 换 步 数 启发值=最小转换步数 =的状态优先出队扩展。

【启发式搜索分析时空复杂度意义不大】

A star算法

  • 学习参考链接
  • 一种启发式搜索算法,可以看作是升级版Dijkstra;
  • 找最小的 F = G + H F=G+H F=G+H
    • G G G是起点到当前节点的距离;
    • H H H是当前节点到终点的估计距离(启发式),通常使用曼哈顿距离估算。

Java

class Solution {
    class Node {
        String s;
        int val; // 到最终状态的距离
        Node(String _s) {
            s = _s;
            for(int i = 0; i < 8; i++)
                if(s.charAt(i) != end.charAt(i))
                    val++;
        }
    }
    static char[] items = new char[]{'A', 'C', 'G', 'T'};
    String start, end;
    public int minMutation(String start, String end, String[] bank) {
        Set<String> bankSet = new HashSet<>();
        for (String s : bank)
            bankSet.add(s);

        this.start = start;
        this.end = end;
        PriorityQueue<Node> pq = new PriorityQueue<>((a, b) -> a.val - b.val);
        Map<String, Integer> map = new HashMap<>();
        pq.add(new Node(start));
        map.put(start, 0); // 步数为0

        while(!pq.isEmpty()) {
            Node cur = pq.poll();
            char[] cs = cur.s.toCharArray();
            int step = map.get(cur.s);
            for(int i = 0; i < 8; i++) {
                for(char c : items) {
                    if(cs[i] == c)
                        continue;
                    char[] clone = cs.clone();
                    clone[i] = c; // 换第i位为c
                    String nxt = String.valueOf(clone);
                    if(!bankSet.contains(nxt)) // 是否合法出现在bank里
                        continue;
                    if(nxt.equals(end)) // 是否到结尾
                        return step + 1;
                    if(!map.containsKey(nxt) || map.get(nxt) > step + 1) { // 更新队列
                        map.put(nxt, step + 1);
                        pq.add(new Node(nxt));
                    }
                }
            }
        }
        return -1;
    }
}

C++

【自定义类很难哦,还有priority_queue好久不用都忘了,调了半天】

static char items[4] = {'A', 'C', 'G', 'T'};
class Solution {
    string start, end;
public:
    class Node {
    public:
        string s;
        int val; // 到最终状态的距离
        Node(string _s) {
            s = _s;
            Solution solu;
            for(int i = 0; i < 8; i++)
                if(s[i] != solu.end[i])
                    val++;
        }
    };

    int minMutation(string start, string end, vector<string>& bank) {
        auto cmp = [](Node a, Node b) { return a.val > b.val; };
        priority_queue<Node, vector<Node>, decltype(cmp)> pq(cmp);
        unordered_map<string, int> map;
        pq.push(Node(start));
        map[start] = 0; // 步数为0

        while(!pq.empty()) {
            Node cur = pq.top();
            pq.pop();
            int step = map[cur.s];
            for(int i = 0; i < 8; i++) {
                for(char c : items) {
                    if(cur.s[i] == c)
                        continue;
                    string nxt = cur.s;
                    nxt[i] = c; // 换第i位为c
                    if(find(bank.begin(), bank.end(), nxt) == bank.end()) // 是否合法出现在bank里
                        continue;
                    if(nxt == end) // 是否到结尾
                        return step + 1;
                    if(!map.count(nxt) || map[nxt] > step + 1) { // 更新队列
                        map[nxt] = step + 1;
                        pq.push(Node(nxt));
                    }
                }
            }
        }

        return -1;
    }
};

思路四:建图+DFS

  • 建图,两个字符仅有一位不同则有边;
  • 遍历List建图,以 s t a r t start start开始 e n d end end结束;
  • 跑一遍DFS;
  • 避免环or无解,设置最大搜索深度 n n n和最优解剪枝避免死循环。

Java

class Solution {
    int N = 15, M = 15 * 15 * 2 + 50, idx =0, loc = 1;
    int[] head = new int[N], edge = new int[M], next = new int[M];
    int n, res;
    void add(int a, int b) {
        edge[idx] = b;
        next[idx] = head[a];
        head[a] = idx++;
    }
    void dfs(int u, int fa, int depth) {
        if(depth >= res)
            return ; // 最优解剪枝
        if(u == n) {
            res = depth;
            return ;
        }
        for(int i = head[u]; i != -1; i = next[i]) {
            int j = edge[i];
            if(j == fa)
                continue;
            dfs(j, u, depth + 1);
        }
    }

    public int minMutation(String start, String end, String[] bank) {
        List<String> list = new ArrayList<>();
        list.add(start);
        boolean ok = false;
        for(String s : bank) {
            if(s.equals(start))
                continue;
            if(s.equals(end)) {
                ok = true;
                continue;
            }
            list.add(s);
        }
        if(!ok) // 到不了end
            return -1;
        list.add(end);

        n = list.size();
        res = n;
        Arrays.fill(head, -1);
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                if(i == j)
                    continue;
                int cnt = 0;
                for(int k = 0; k < 8 && cnt <= 1; k++)
                    if(list.get(i).charAt(k) != list.get(j).charAt(k))
                        cnt++;
                if(cnt == 1){ // 只有一位不同,连一条边
                    add(i + 1, j + 1);
                    add(j + 1, i + 1);
                }
            }
        }
        dfs(1, -1, 0); // 初始深度为0
        return res == n ? -1 : res;
    }
}
  • 时间复杂度: O ( C × n 2 ) O(C\times n^2) O(C×n2),预处理出 L i s t List List的复杂度为 O ( n ) O(n) O(n);建图复杂度为 O ( C × n 2 ) O(C\times n^2) O(C×n2),其中 C = 8 C=8 C=8;DFS有最大深度约束复杂度为 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2),最坏情况完全图

C++

【会报错,不知道咋肥死会超出索引边界,也不知道哪个edge去做了撒子】

const static int N = 15, M = 15 * 15 * 2 + 50;
class Solution {
    int idx = 0;
    int head[N], edge[M], next[M];
    int n, res;
public:
    void add(int a, int b) {
        edge[idx] = b;
        next[idx] = head[a];
        head[a] = idx++;
    }
    void dfs(int u, int fa, int depth) {
        if(depth >= res)
            return ; // 最优解剪枝
        if(u == n) {
            res = depth;
            return ;
        }
        for(int i = head[u]; i != -1; i = next[i]) {
            int j = edge[i]; // error: out of bounds
            if(j == fa)
                continue;
            dfs(j, u, depth + 1);
        }
    }

    int minMutation(string start, string end, vector<string>& bank) {
        vector<string> list;
        list.push_back(start);
        bool ok = false;
        for(auto s : bank) {
            if(s == start)
                continue;
            if(s == end) {
                ok = true;
                continue;
            }
            list.push_back(s);
        }
        if(!ok)
            return -1;
        list.push_back(end);

        n = list.size();
        res = n;
        memset(head, -1, N);
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                if(i == j)
                    continue;
                int cnt = 0;
                for(int k = 0; k < 8 && cnt <= 1; k++)
                    if(list[i][k] != list[j][k])
                        cnt++;
                if(cnt == 1) {
                    add(i + 1, j + 1);
                    add(j + 1, i + 1);
                }
            }
        }
        dfs(1, -1, 0);
        return res == n ? -1 : res;
    }
};
  • 时间复杂度: O ( C × n 2 ) O(C\times n^2) O(C×n2),预处理出 L i s t List List的复杂度为 O ( n ) O(n) O(n);建图复杂度为 O ( C × n 2 ) O(C\times n^2) O(C×n2),其中 C = 8 C=8 C=8;DFS有最大深度约束复杂度为 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2),最坏情况完全图

总结

今天的题解法好多,周末心思不太正,浅学了一下A star但感觉没有理解透,不过搞懂了双向BFS(安慰自己,但其实代码都没跑通)。最后DFS还复习了一下链式前向星,哦好多知识,任重道远。

留了两个报错小尾巴,C++好烦哦嘤嘤嘤,之后要来好好复习!!!


欢迎指正与讨论!
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值