首页 > 其他分享 >【Leetcode 每日一题】2056. 棋盘上有效移动组合的数目

【Leetcode 每日一题】2056. 棋盘上有效移动组合的数目

时间:2024-12-04 11:32:55浏览次数:11  
标签:移动 2056 int positions Move pieces 棋子 棋盘 Leetcode

问题背景

有一个 8 × 8 8 \times 8 8×8 的棋盘,它包含 n n n 个棋子(棋子包括车,后和象三种)。给你一个长度为 n n n 的字符串数组 p i e c e s pieces pieces,其中 p i e c e s [ i ] pieces[i] pieces[i] 表示第 i i i 个棋子的类型(车,后或象)。除此以外,还给你一个长度为 n n n 的二维整数数组 p o s i t i o n s positions positions,其中 p o s i t i o n s [ i ] = [ r i , c i ] positions[i] = [r_i, c_i] positions[i]=[ri​,ci​] 表示第 i i i 个棋子现在在棋盘上的位置为 ( r i , c i ) (r_i, c_i) (ri​,ci​),棋盘下标从 1 1 1 开始。
棋盘上每个棋子都可以移动 至多一次 。每个棋子的移动中,首先选择移动的 方向 ,然后选择 移动的步数 ,同时你要确保移动过程中棋子不能移到棋盘以外的地方。棋子需按照以下规则移动:

  • 车可以 水平或者竖直 从 ( r , c ) (r, c) (r,c) 沿着方向 ( r + 1 , c ) (r + 1, c) (r+1,c), ( r − 1 , c ) (r - 1, c) (r−1,c), ( r , c + 1 ) (r, c + 1) (r,c+1), ( r , c − 1 ) (r, c-1) (r,c−1)移动。
  • 后可以 水平竖直或者斜对角 从 ( r , c ) (r, c) (r,c) 沿着方向 ( r + 1 , c ) (r + 1, c) (r+1,c), ( r − 1 , c ) (r - 1, c) (r−1,c), ( r , c + 1 ) (r, c + 1) (r,c+1), ( r , c − 1 ) (r, c - 1) (r,c−1), ( r + 1 , c + 1 ) (r + 1, c + 1) (r+1,c+1), ( r + 1 , c − 1 ) (r + 1, c - 1) (r+1,c−1), ( r − 1 , c + 1 ) (r - 1, c + 1) (r−1,c+1), ( r − 1 , c − 1 ) (r - 1, c - 1) (r−1,c−1) 移动。
  • 象可以 斜对角 从 ( r , c ) (r, c) (r,c) 沿着方向 ( r + 1 , c + 1 ) (r + 1, c + 1) (r+1,c+1), ( r + 1 , c − 1 ) (r + 1, c - 1) (r+1,c−1), ( r − 1 , c + 1 ) (r - 1, c + 1) (r−1,c+1), ( r − 1 , c − 1 ) (r - 1, c - 1) (r−1,c−1) 移动。
    移动组合 包含所有棋子的 移动 。每一秒,每个棋子都沿着它们选择的方向往前移动 一步 ,直到它们到达目标位置。所有棋子从时刻 0 0 0开始移动。如果在某个时刻,两个或者更多棋子占据了同一个格子,那么这个移动组合 无效
    请你返回 有效 移动组合的数目。

注意:
初始时,不会有两个棋子同一个位置
有可能在一个移动组合中,有棋子不移动。
如果两个棋子 直接相邻 且两个棋子下一秒要互相占据对方的位置,可以将它们在同一秒内 交换位置

数据约束

  • n = p i e c e s . l e n g t h n = pieces.length n=pieces.length
  • n = p o s i t i o n s . l e n g t h n = positions.length n=positions.length
  • 1 ≤ n ≤ 4 1 \le n \le 4 1≤n≤4
  • p i e c e s pieces pieces 只包含字符串 “rook” ,“queen” 和 “bishop” 。
  • 棋盘上最多只有一个后。
  • 1 ≤ r i , c i ≤ 8 1 \le r_i, c_i \le 8 1≤ri​,ci​≤8
  • 每一个 p o s i t i o n s [ i ] positions[i] positions[i] 互不相同

解题过程

很遗憾没有做出来,抄完 灵神的题解 回过头来看这题在算法考察这方面并不是特别难。所有的棋子都是同时移动,数据量不是很大,并且限制比较多,这当然也是能用回溯的前提。
回溯的思路是比较简单的,每一层确定一个棋子的位置,根据它移动的情况去挨个判断会不会跟之前已经存在的棋子重叠,不累计重叠的情况。

但是除了核心点以外的处理,还是有些复杂的。
尤其是定义一个类来刻画移动的情形目前我是完全没想到,见世面了算是。

具体实现

class Solution {

    // 常量定义,SIZE 表示棋子下标的上界,PIECE_DIRECITONS 表示不同棋子可能的移动方向
    private static final int SIZE = 8;
    private static final Map<Character, int[][]> PIECE_DIRECTIONS = Map.of(
        'r', new int[][]{{-1, 0}, {1, 0}, {0, -1}, {0, 1}},
        'b', new int[][]{{1, 1}, {-1, 1}, {-1, -1}, {1, -1}},
        'q', new int[][]{{-1, 0}, {1, 0}, {0, -1}, {0, 1}, {1, 1}, {-1, 1}, {-1, -1}, {1, -1}}
    );

    // 定义一个类来表示 棋子从 (x0, y0) 点出发,沿 (dx, dy) 方向移动 step 步 这样的操作
    private record Move(int x0, int y0, int dx, int dy, int step){}

    public int countCombinations(String[] pieces, int[][] positions) {
        int n = pieces.length;
        // 预处理,记录每个棋子所有可能的移动
        Move[][] moves = new Move[n][];
        for(int i = 0; i < n; i++) {
            moves[i] = generateMoves(positions[i][0], positions[i][1], PIECE_DIRECTIONS.get(pieces[i].charAt(0)));
        }
        Move[] path = new Move[n];
        return dfs(0, n, moves, path);
    }

    private Move[] generateMoves(int x0, int y0, int[][] directions) {
        List<Move> moves = new ArrayList<>();
        moves.add(new Move(x0, y0, 0, 0, 0)); // 添加初始位置
        for(int[] direction : directions) {
            // 按 step 迭代,得到棋子按某个方向移动可能到达的各个位置
            int x = x0 + direction[0], y = y0 + direction[1];
            for(int step = 1; 0 < x && x <= SIZE && 0 < y && y <= SIZE; step++) {
                moves.add(new Move(x0, y0, direction[0], direction[1], step));
                x += direction[0];
                y += direction[1];
            }
        }
        // List 转数组,这里构造的 Move 类型的数组限定了列表中的泛型
        return moves.toArray(Move[]::new);
    }

    // 判断有没有可能重叠
    private boolean check(Move m1, Move m2) {
        // 从两个起始位置出发
        int x1 = m1.x0, y1 = m1.y0;
        int x2 = m2.x0, y2 = m2.y0;
        for(int i = 0; i < Math.max(m1.step, m2.step); i++) {
            // 每次走一步
            if(i < m1.step) {
                x1 += m1.dx;
                y1 += m1.dy;
            }
            if (i < m2.step) {
                x2 += m2.dx;
                y2 += m2.dy;
            }
            // 重叠返回 false
            if (x1 == x2 && y1 == y2) {
                return false;
            }
        }
        return true;
    }

    private int dfs(int i , int n, Move[][] moves, Move[] path) {
        // 当前棋子下标越界,表示已经判断完可能重叠的情形,返回一种符合条件的结果
        if(i == n) {
            return 1;
        }
        int res = 0;
        // 定义循环控制标记,用来剪枝
        outer:
        for(Move move : moves[i]) {
            for(int j = 0; j < i; j++) {
                if(!check(move, path[j])) {
                    continue outer; // 出现重叠的情况,后面都不用尝试了,直接开始下一轮
                }
            }
            path[i] = move; // 记录路径
            res += dfs(i + 1, n, moves, path); // 进一步递归下一个棋子的情形
        }
        return res;
    }
}

梳理总结

record 关键字

在 Java 中,record 关键字是从 Java 14 引入的,它提供了一种简洁的方式来创建不可变的数据类。record 是一种特殊的类,它的主要用途是封装数据,特别适用于那些只包含属性(字段)并且需要自动实现 equals、hashCode 和 toString 方法的场景。

循环控制标记

在 Java 中,标记并跳出多重循环可以通过使用 标签 labelbreakcontinue 结合来实现,标签可以用来指定退出哪一层循环:

  • break [label]; 表示从标签位置开始执行,不再继续跳出的循环。
  • break [label]; 表示从标签位置开始执行,从跳出的循环的下一轮次继续。

不常用,一般情况下不建议用,积累一下。

标签:移动,2056,int,positions,Move,pieces,棋子,棋盘,Leetcode
From: https://blog.csdn.net/2401_88859777/article/details/144233687

相关文章

  • 【力扣】3274. 检查棋盘方格颜色是否相同
    一、题目给你两个字符串coordinate1和coordinate2,代表8x8国际象棋棋盘上的两个方格的坐标。以下是棋盘格的参考图:如果这两个方格颜色相同,返回true,否则返回false。坐标总是表示有效的棋盘方格。坐标的格式总是先字母(表示列),再数字(表示行)。示例:输入:coordinate1......
  • 代码随想录算法训练营第三十六天|LeetCode1049.最后一块石头的重量II、LeetCode494.目
    前言打卡代码随想录算法训练营第49期第三十六天...φ(0 ̄*)啦啦啦_φ(* ̄0 ̄)′首先十分推荐学算法的同学可以先了解一下代码随想录,可以在B站卡哥B站账号、代码随想录官方网站代码随想录了解,卡哥清晰易懂的算法教学让我直接果断关注,也十分有缘和第49期的训练营大家庭一起进步。Le......
  • leetcode 面试17.26 稀疏相似度
    一个文档可以用某个int集合来表示,两个文档的相似度定义为对应集合的交集大小除以并集大小,例如{1,5,3}与{1,7,2,3}的相似度为0.4。给定n个相似度很稀疏的文档,返回所有相似度大于0的组合。1<=n<=500,1<=set[i]<=500分析:采用类似倒排索引的做法,对集合中的每个int,记录在哪些文档中......
  • LeetCode刷题 -- 分治快排
    目录颜色分类题目解析算法原理代码排序数组题目解析算法原理代码数组中第K个最大元素题目解析算法原理代码LCR159.库存管理III题目解析算法原理代码颜色分类题目链接题目解析数组分为三块算法原理1.如果nums[i]==0,++left,i++下标对应元素交换,先+......
  • 2024/12/3 【哈希表】 LeetCode 242.有效的字母异位词 【x】
    题目链接:https://leetcode.cn/problems/valid-anagram/description/解法1:classSolution:defisAnagram(self,s:str,t:str)->bool:record=[0]*26foriins:record[ord(i)-ord('a')]+=1fori......
  • 3274. 检查棋盘方格颜色是否相同
    给你两个字符串coordinate1和coordinate2,代表8x8国际象棋棋盘上的两个方格的坐标。以下是棋盘的参考图。如果这两个方格颜色相同,返回true,否则返回false。坐标总是表示有效的棋盘方格。坐标的格式总是先字母(表示列),再数字(表示行)。示例1:输入:coordinate1="a1",coo......
  • 搞定leetcode面试经典150题之哈希算法
    系列博客目录搞定leetcode面试经典150题之哈希算法搞定leetcode面试经典150题之双指针搞定leetcode面试经典150题之滑动窗口文章目录系列博客目录理论知识1.哈希函数(HashFunction)2.哈希表(HashTable)通过HashMap实现3.哈希算法的应用4.哈希算法的时间复杂度编......
  • 2024/12/2【链表】LeetCode 142 环形链表 II 【X】
    题目链接:https://leetcode.cn/problems/linked-list-cycle-ii/description/题解链接:https://www.programmercarl.com/0142.%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8II.html#%E5%85%B6%E4%BB%96%E8%AF%AD%E8%A8%80%E7%89%88%E6%9C%AC没做出来......
  • LeetCode题练习与总结:字典序的第 K 小数字--440
    一、题目描述给定整数 n 和 k,返回  [1,n] 中字典序第 k 小的数字。示例1:输入:n=13,k=2输出:10解释:字典序的排列是[1,10,11,12,13,2,3,4,5,6,7,8,9],所以第二小的数字是10。示例2:输入:n=1,k=1输出:1提示:1<=k<=n<......
  • LeetCode题练习与总结:排列硬币--441
    一、题目描述你总共有 n 枚硬币,并计划将它们按阶梯状排列。对于一个由 k 行组成的阶梯,其第 i 行必须正好有 i 枚硬币。阶梯的最后一行 可能 是不完整的。给你一个数字 n ,计算并返回可形成 完整阶梯行 的总行数。示例1:输入:n=5输出:2解释:因为第三行不完......