【兔年创意投稿】兔兔的奇妙之旅(二)侦探兔兔初现天赋

1,461 阅读4分钟

我正在参加「兔了个兔」创意投稿大赛,详情请看:「兔了个兔」创意投稿大赛

注:本系列文章实为算法题分析,所涉及到的兔兔场景为虚构题材,博君一笑,请勿当真。如有雷同,实属巧合~


不知不觉,兔子突突又长大了一点,也开始去认识一些新的小伙伴。

摸石子游戏

任务描述

这天,它在河边看到一大一小两只兔子在玩什么游戏,它凑过去观察了一会儿,终于了解了游戏规则:

  • 现场有一些小石子。
  • 每一回合,这两只兔子轮流拿掉 1-3 块石子。
  • 拿掉最后一块石头的兔子就是赢家。

但是突突感到很奇怪的是,每一次大兔子都说要首先拿,但每一次最后的家却总是大兔子,这可把小兔子给气哭了。而大兔子说:“诶嘿~这可不赖我!说明是你运气不好,你看,你每次明明可以选择拿1-3个石子呀,你有这么多次改变的机会,都没能顺利拿到最后一块石头,是你的问题哟~”。

小兔子嚷嚷着它也要先抽,大兔子就以扎手为由随手扔掉了几颗石子,或者补新石子或者不补,几番回合下来,大兔子依然全胜。

突突觉得事情没那么简单。果然,它通过观察分析,最终发现了大兔子的秘密,并且告诉了小兔子。

大兔子嘿嘿一笑,说是和自己妹妹开玩笑的。说完,它从背后拿出了一根胡萝卜送给小兔子以示补偿。

你知道突突发现的秘密是什么吗? 欢迎把答案打在评论区上!答案文末也会揭晓~

N皇后问题

突突的机敏让大兔子对它刮目相看,于是它打算给突突出一道难题。富有挑战精神的的突突爽快地答应了下来。

难题描述

大兔子把突突领到家里的象棋桌前,给它讲起了规则:

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给定一个整数 n ,返回所有不同的 n皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位

突突以前哪里见过这种场面?一下就被唬住了。

它尝试了n=4的情况,得出了有两种解法的结论。但当它想尝试5的时候,感觉自己头都要大了。

显然,突突现在还不适合解决这种问题。精通编程的你能帮帮它,借助计算机的算力解决这个问题吗?

游戏谜底

摸石子游戏

这个游戏又叫“Nim游戏”。只要掌握技巧,从一开始就已经注定了结局。 当石子数不是4的倍数的时候,首先抽的人总能赢。 用代码表示就是:return n%4!=0;

至于为什么,可以用以下这段话概括:

面对4的整数倍的人永远无法翻身,你拿N根对手就会拿4-N根,保证每回合共减4根,你永远面对4倍数,直到4. 相反,如果最开始不是4倍数,你可以拿掉刚好剩下4倍数根,让对方永远面对4倍数。——力扣评论区

而这也是大兔子不在首抽后想要换掉石子数量的原因。

N皇后问题

这是经典的回溯问题。以下是基于集合的回溯解法:(代码中有较详细注释)

class Solution {
private:
    vector<vector<string>> result;  
    vector<string> path;
private:
    // 检测以[row,col]位置的 两条对角线以及列上 是否存在皇后 (剪枝: 只需要检测 [0,row) 行的部分)
    bool check(int row, int col, int n) {
        for (int i = 0; i < row; ++i) {
            int col45 = row + col - i;      // 当前[row,col]位置的 45°对角线(正对角线)上的 所有坐标的纵坐标。 (这条对角线上的 row+col相等)
            int col135 = i - (row - col);   // 当前[row,col]位置的 135°对角线(反对角线)上的 所有坐标的纵坐标。(这条对角线上的 row-col相等)
            if (path[i][col] == 'Q' || (col45 >= 0 && col45 < n && path[i][col45] == 'Q') || (col135 >= 0 && col135 < n && path[i][col135] == 'Q')) { 
                return false;
            }
        }
        return true;
    }
    // 第row行的皇后放在何处
    void dfs(int n, int row) {
        if (row == n) {   // 说明最后一行也放完了,从而得到一种结果
            result.push_back(path);
            return;
        }
        // 每行只能放一个,但可能在该行任意列
        for (int col = 0; col < n; ++col) {  
            if (check(row, col, n)) {   // 检测当前位置是否可以放皇后
                path[row][col] = 'Q';   // 放  
                dfs(n, row + 1);        // 再下一行放
                path[row][col] = '.';   // 收 (回溯)
            }
        }
    }
public:
    vector<vector<string>> solveNQueens(int n) {
        path = vector<string>(n, string(n, '.'));  // 初始化 (都没放)
        dfs(n, 0);  // 从第0行开始放
        return result;
    }
};

本期内容到这里就结束啦~感谢你能看到这里!欲知后事如何,请听下回分解!