leetcode每日一结

11 篇文章 0 订阅
4 篇文章 0 订阅

滑动谜题

主要使用广度优先算法遍历树


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


题目

在一个 2 x 3 的板上(board)有 5 块砖瓦,用数字 1~5 来表示, 以及一块空缺用 0 来表示.

一次移动定义为选择 0 与一个相邻的数字(上下左右)进行交换.

最终当板 board 的结果是 [[1,2,3],[4,5,0]] 谜板被解开。

给出一个谜板的初始状态,返回最少可以通过多少次移动解开谜板,如果不能解开谜板,则返回 -1 。

示例:

输入:board = [[1,2,3],[4,0,5]]
输出:1
解释:交换 0 和 5 ,1 步完成
输入:board = [[1,2,3],[5,4,0]]
输出:-1
解释:没有办法完成谜板
输入:board = [[4,1,2],[5,0,3]]
输出:5
解释:
最少完成谜板的最少移动次数是 5 ,
一种移动路径:
尚未移动: [[4,1,2],[5,0,3]]
移动 1 次: [[4,1,2],[0,5,3]]
移动 2 次: [[0,1,2],[4,5,3]]
移动 3 次: [[1,0,2],[4,5,3]]
移动 4 次: [[1,2,0],[4,5,3]]
移动 5 次: [[1,2,3],[4,5,0]]
输入:board = [[3,2,4],[1,5,0]]
输出:14
提示:

board 是一个如上所述的 2 x 3 的数组.
board[i][j] 是一个 [0, 1, 2, 3, 4, 5] 的排列.

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sliding-puzzle
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


答案

代码如下(示例):

class Solution {
private:
    vector<vector<int>> neighbors = {{1, 3}, {0, 2, 4}, {1, 5}, {0, 4}, {1, 3, 5}, {2, 4}};

public:
    int slidingPuzzle(vector<vector<int>>& board) {
        // 枚举 status 通过一次交换操作得到的状态
        auto get = [&](string& status) -> vector<string> {
            //auto的原理就是根据后面的值,来自己推测前面的类型是什么。
            //auto的作用就是为了简化变量初始化,如果这个变量有一个很长很长的初始化类型,就可以用auto代替。

            vector<string> ret;
            int x = status.find('0');
            for (int y: neighbors[x]) {
                swap(status[x], status[y]);//将所有可能的交换结果放入ret
                ret.push_back(status);
                swap(status[x], status[y]);//还原,方便下次交换
            }
            return ret;
        };

        string initial;
        for (int i = 0; i < 2; ++i) {
            for (int j = 0; j < 3; ++j) {
                initial += char(board[i][j] + '0');//将二维数组转变为string,+ '0'表示转换为字符
            }
        }
        if (initial == "123450") {
            return 0;
        }

        queue<pair<string, int>> q;
        //定义队列容器,队列的每个元素是pair,pair是由两个元素string组成的结构体
        // pair是将2个数据组合成一组数据,当需要这样的需求时就可以使用pair,如stl中的map就是将key和value放在一起来保存。
        //另一个应用是,当一个函数需要返回2个数据的时候,可以选择pair。
        // pair的实现是一个结构体,主要的两个成员变量是first second 因为是使用struct不是class,所以可以直接使用pair的成员变量。

        q.emplace(initial, 0);//emplace():用传给 emplace() 的参数调用 T 的构造函数,在 queue 的尾部生成对象。
        unordered_set<string> seen = {initial};

        while (!q.empty()) {
            auto [status, step] = q.front();//开始,将队列首项(初始状态)初始化给status与step
            q.pop();//将分析过的pair弹出
            for (auto&& next_status: get(status)) {//将所有的结果循环
                if (!seen.count(next_status)) {
                    //unordered_set::count()函数是C++ STL中的内置函数,用于对unordered_set容器中特定元素的出现进行计数。
                    //由于unordered_set容器不允许存储重复的元素,因此该功能通常用于检查容器中是否存在元素。
                    //如果元素存在于容器中,则该函数返回1,否则返回0。
                    if (next_status == "123450") {
                        return step + 1;//循环出口
                    }
                    q.emplace(next_status, step + 1);//将(中间)结果放入待分析列表
                    seen.insert(move(next_status));//记录所有出现过的情况,一是防止重复判断剪枝,另一个是作为判断是否不能完成的重要条件
                }
            }
        }

        return -1;
    }
};

总结

1.pair的使用(相当于字典)
字典首先把所有的键hash成数值,然后排序,然后通过二分法来查找数值从而取出键呢?二分法的效率可是很高的啊!
实际上,字典采用的时一种更高级的算法,需要的步数更少。
这里我们也就明白了hash的另一种作用和与字典的联系。
queue<pair<string, int>> q;
//定义队列容器,队列的每个元素是pair,pair是由两个元素string组成的结构体
// pair是将2个数据组合成一组数据,当需要这样的需求时就可以使用pair,如stl中的map就是将key和value放在一起来保存。
//另一个应用是,当一个函数需要返回2个数据的时候,可以选择pair。
// pair的实现是一个结构体,主要的两个成员变量是first second 因为是使用struct不是class,所以可以直接使用pair的成员变量。
q.emplace(initial, 0);//emplace():用传给 emplace() 的参数调用 T 的构造函数,在 queue 的尾部生成对象。

2. auto get = [&](string& status) -> vector {
//auto的原理就是根据后面的值,来自己推测前面的类型是什么。
//auto的作用就是为了简化变量初始化,如果这个变量有一个很长很长的初始化类型,就可以用auto代替。

3.unordered_set::count()函数
//unordered_set::count()函数是C++ STL中的内置函数,用于对unordered_set容器中特定元素的出现进行计数。
//由于unordered_set容器不允许存储重复的元素,因此该功能通常用于检查容器中是否存在元素。
//如果元素存在于容器中,则该函数返回1,否则返回0。

4.对于经常出现的广度优先搜索,判别不可能出现的情况
seen.insert(move(next_status));//记录所有出现过的情况,一是防止重复判断剪枝,另一个是作为判断是否不能完成的重要条件

类似上面,记录每次都中间状态待判断状态,保存所有的中间状态,根据已经出现过的中间状态进行剪枝,判断是否放入待判断状态中,若所有的中间状态都已经出现过,此时待判断状态列表为空,表示已经结束(保证了树是有限的)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值