月度归档: 2022年6月

LeetCode刷题【794】有效的井字游戏

给你一个字符串数组 board 表示井字游戏的棋盘。当且仅当在井字游戏过程中,棋盘有可能达到 board 所显示的状态时,才返回 true

井字游戏的棋盘是一个 3 x 3 数组,由字符 ' ''X''O' 组成。字符 ' ' 代表一个空位。

以下是井字游戏的规则:

  • 玩家轮流将字符放入空位(' ')中。
  • 玩家 1 总是放字符 'X' ,而玩家 2 总是放字符 'O'
  • 'X''O' 只允许放置在空位中,不允许对已放有字符的位置进行填充。
  • 当有 3 个相同(且非空)的字符填充任何行、列或对角线时,游戏结束。
  • 当所有位置非空时,也算为游戏结束。
  • 如果游戏结束,玩家不允许再放置字符。

 

示例 1:

输入:board = ["O  ","   ","   "]
输出:false
解释:玩家 1 总是放字符 "X" 。

示例 2:

输入:board = ["XOX"," X ","   "]
输出:false
解释:玩家应该轮流放字符。

示例 3:

输入:board = ["XOX","O O","XOX"]
输出:true

 

提示:

  • board.length == 3
  • board[i].length == 3
  • board[i][j]'X''O'' '
Related Topics
  • 数组
  • 字符串

  • 👍 112
  • 👎 0
  • 略坑,又臭又长,可以看看

    1. XO数量情况符合 0 <= XO <= 1
    2. 输赢情况判断
      • X赢 :X三连数量为1个或者两个,O三连数量为0个, 此时因为X先手,必然有 数量统计结果XO == 1
      • O赢 : X三连数量为0个,O三连数量为1个,因为O后手,必然有 数量统计结果XO == 0
      • 两人都还没赢: X三连数量为0个,O三连数量为0个

    写二维坐标计算统计太麻烦了,我把board[0] + board[1] + board[2]拼起来了,
    用这样的一维坐标计算统计了

        0 1 2
        3 4 5
        6 7 8
    
        对应为
    
        0 1 2 3 4 5 6 7 8

    代码

    class Solution {
    
        char X = 'X';
        char O = 'O';
    
        public boolean validTicTacToe(String[] board) {
            int xCnt = 0;
            int oCnt = 0;
            String str = board[0] + board[1] + board[2];
            for (int i = 0; i < str.length(); i++) {
                if (str.charAt(i) == X){
                    xCnt++;
                }
                if (str.charAt(i) == O){
                    oCnt++;
                }
            }
            if (xCnt - oCnt == 1 || xCnt - oCnt == 0){
                int[] res = countLine(str);
                if (res[0] >=1 && res[1]==0){
                    //X赢
                    // 有个特殊情况,X是可以连成两个3连的
                    // X X X
                    // O O X
                    // O O X
                    return  xCnt - oCnt == 1;
                }else if(res[0]==0 && res[1]==1){
                    //O赢
                    return xCnt - oCnt == 0;
                }else if (res[0] == 0 && res[1] == 0){
                    //都还没赢 比如
                    // X O X
                    // O   O
                    // X O X
                    return true;
                }
            }
            return false;
        }
    
    
        public int[] countLine(String str){
            // 下标转换下,写起来方便
            // 0 1 2
            // 3 4 5
            // 6 7 8
            int[][] arr = new int[8][2];
            arr[0][0] = charCount(str , X, 0,1,2);
            arr[1][0] = charCount(str , X, 3,4,5);
            arr[2][0] = charCount(str , X, 6,7,8);
            arr[0][1] = charCount(str , O, 0,1,2);
            arr[1][1] = charCount(str , O, 3,4,5);
            arr[2][1] = charCount(str , O, 6,7,8);
            arr[3][0] = charCount(str , X, 0,3,6);
            arr[4][0] = charCount(str , X, 1,4,7);
            arr[5][0] = charCount(str , X, 2,5,8);
            arr[3][1] = charCount(str , O, 0,3,6);
            arr[4][1] = charCount(str , O, 1,4,7);
            arr[5][1] = charCount(str , O, 2,5,8);
            arr[6][0] = charCount(str , X, 0,4,8);
            arr[6][1] = charCount(str , O, 0,4,8);
            arr[7][0] = charCount(str , X, 2,4,6);
            arr[7][1] = charCount(str , O, 2,4,6);
            int[] res = new int[2];
            for (int[] ints : arr) {
                res[0] += ints[0]==3?1:0;
                res[1] += ints[1]==3?1:0;
            }
            return res;
        }
    
        public int charCount(String str, char c, int i1, int i2, int i3){
            int count = 0;
            count += str.charAt(i1)==c? 1 : 0;
            count += str.charAt(i2)==c? 1 : 0;
            count += str.charAt(i3)==c? 1 : 0;
            return count;
        }
    
    }

    LeetCode刷题【689】三个无重叠子数组的最大和

    给你一个整数数组 nums 和一个整数 k ,找出三个长度为 k 、互不重叠、且全部数字和(3 * k 项)最大的子数组,并返回这三个子数组。

    以下标的数组形式返回结果,数组中的每一项分别指示每个子数组的起始位置(下标从 0 开始)。如果有多个结果,返回字典序最小的一个。

     

    示例 1:

    输入:nums = [1,2,1,2,6,7,5,1], k = 2
    输出:[0,3,5]
    解释:子数组 [1, 2], [2, 6], [7, 5] 对应的起始下标为 [0, 3, 5]。
    也可以取 [2, 1], 但是结果 [1, 3, 5] 在字典序上更大。
    

    示例 2:

    输入:nums = [1,2,1,2,1,2,1,2,1], k = 2
    输出:[0,2,4]
    

     

    提示:

    • 1 <= nums.length <= 2 * 104
    • 1 <= nums[i] < 216
    • 1 <= k <= floor(nums.length / 3)
    Related Topics
    • 数组
    • 动态规划

  • 👍 320
  • 👎 0
  • 动态规划【图解】

    解题思路

    1. 根据题面长度k区间,自然会想到的是滑动窗口来构建一个新的数组sumK
    2. 接下来的思路稍微棘手点,但是如果你做过42. 接雨水,并且是用动态规划的方法来做的话,那么就会非常好理解了学了好久的动态规划再来做这题
    3. 接雨水的思路是:找到左侧的最大值,同时找到右侧的最大值
    4. 而这里,不能单纯的从左边右边移动一位,而是对应的需要移动距离k之外的最大值
    5. 看图示例

    1. 首先求得sumK数组 3 3 3 8 13 12 6
    2. 接下来分别求左侧和右侧的最大值,下标0,1没有左侧区间,下标5,6也没有右侧区间
    3. 左侧最大值从下标2开始,对应值为sumK[0],之后往右移动,下标5的左侧最大值为sumK[3],下标6同理
    4. 右侧最大值为从右往左,原理同左侧最大值
    5. 最终只遍历中间部分左右侧最大值都有的区间,找到和最大的那个值,并记下对应的下标索引

    代码

    class Solution {
    
        /*
        1,  2,  1,  2,  6,  7,  5,  1    k = 2
        3   3   3   8  13  12   6
                3   3   3   8  13
       13  13  13  12   6
         */
    
    
        public int[] maxSumOfThreeSubarrays(int[] nums, int k) {
            int[] sumK = new int[nums.length-k+1];
            for (int i = 0; i < k; i++) {
                sumK[0] += nums[i];
            }
            for (int i = 1; i < sumK.length; i++) {
                sumK[i] = sumK[i-1] + nums[i+k-1] - nums[i-1];
            }
    //        System.out.println(Arrays.toString(sumK));
            int[][] dp = new int[sumK.length][2];
            boolean flag = false;
            for (int i = k; i < sumK.length - k; i++) {
                int j = sumK.length - i - 1;
                if (flag){
                    dp[i][0] = sumK[i-k] > sumK[dp[i-1][0]] ? i-k : dp[i-1][0];
                    dp[j][1] = sumK[j+k] >= sumK[dp[j+1][1]] ? j+k : dp[j+1][1];
                }else{
                    flag = true;
                    dp[i][0] = i-k;
                    dp[j][1] = j+k;
                }
    //            System.out.println(sumK[dp[i][0]] + "    "+sumK[dp[j][1]]);
            }
    //        for (int[] ints : dp) {
    //            System.out.println(Arrays.toString(ints));
    //        }
            int[] res = new int[3];
            int max = 0;
            for (int i = k; i + k < sumK.length; i++) {
                int sum = sumK[i] + sumK[dp[i][0]] + sumK[dp[i][1]];
    //            System.out.println("i  "+i + "  sum"+ sum);
                if ( sum > max){
                    max = sum;
                    res[0] = dp[i][0];
                    res[1] = i;
                    res[2] = dp[i][1];
                }
            }
            return res;
        }
    }

    LeetCode刷题【剑指 Offer 49】丑数

    我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

     

    示例:

    输入: n = 10
    输出: 12
    解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。

    说明:  

    1. 1 是丑数。
    2. n 不超过1690。

    注意:本题与主站 264 题相同:https://leetcode-cn.com/problems/ugly-number-ii/

    Related Topics
    • 哈希表
    • 数学
    • 动态规划
    • 堆(优先队列)

  • 👍 356
  • 👎 0
  • dp,算是dp吧

    解题思路

    此处撰写解题思路

    代码

    class Solution {
        public int nthUglyNumber(int n) {
            int[] dp = new int[n];
            dp[0] = 1;
            int[] numArr = new int[]{2,3,5};
            int[] pArr = new int[3];
            for (int i = 1; i < n; i++) {
                dp[i] = Math.min(Math.min(dp[pArr[0]]*numArr[0],dp[pArr[1]]*numArr[1]),dp[pArr[2]]*numArr[2]);
                for (int p = 0; p < pArr.length; p++) {
                    if (dp[i] == dp[pArr[p]]*numArr[p]){
                        pArr[p]++;
                    }
                }
            }
            return dp[n-1];
        }
    }

    LeetCode刷题【剑指 Offer 17】打印从1到最大的n位数

    输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。

    示例 1:

    输入: n = 1
    输出: [1,2,3,4,5,6,7,8,9]
    

     

    说明:

    • 用返回一个整数列表来代替打印
    • n 为正整数
    Related Topics
    • 数组
    • 数学

  • 👍 230
  • 👎 0
  • 打表
    简单打表

    class Solution {
        final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
                99999999, 999999999, Integer.MAX_VALUE };
        public int[] printNumbers(int n) {
            int max = sizeTable[n-1];
            int[] arr = new int[max];
            int i = -1;
            while (++i < max){
                arr[i] = i + 1;
            }
            return arr;
        }
    }

    这个sizeTable在java基础类库java.lang.Integer中就有,挺好用的

        final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
                                          99999999, 999999999, Integer.MAX_VALUE };
    
        // Requires positive x
        static int stringSize(int x) {
            for (int i=0; ; i++)
                if (x <= sizeTable[i])
                    return i+1;
        }

    LeetCode刷题【1034】边界着色

    给你一个大小为 m x n 的整数矩阵 grid ,表示一个网格。另给你三个整数 rowcolcolor 。网格中的每个值表示该位置处的网格块的颜色。

    两个网格块属于同一 连通分量 需满足下述全部条件:

    • 两个网格块颜色相同
    • 在上、下、左、右任意一个方向上相邻

    连通分量的边界 是指连通分量中满足下述条件之一的所有网格块:

    • 在上、下、左、右任意一个方向上与不属于同一连通分量的网格块相邻
    • 在网格的边界上(第一行/列或最后一行/列)

    请你使用指定颜色 color 为所有包含网格块 grid[row][col]连通分量的边界 进行着色,并返回最终的网格 grid

     

    示例 1:

    输入:grid = [[1,1],[1,2]], row = 0, col = 0, color = 3
    输出:[[3,3],[3,2]]

    示例 2:

    输入:grid = [[1,2,2],[2,3,2]], row = 0, col = 1, color = 3
    输出:[[1,3,3],[2,3,3]]

    示例 3:

    输入:grid = [[1,1,1],[1,1,1],[1,1,1]], row = 1, col = 1, color = 2
    输出:[[2,2,2],[2,1,2],[2,2,2]]

     

    提示:

    • m == grid.length
    • n == grid[i].length
    • 1 <= m, n <= 50
    • 1 <= grid[i][j], color <= 1000
    • 0 <= row < m
    • 0 <= col < n

     

    Related Topics
    • 深度优先搜索
    • 广度优先搜索
    • 数组
    • 矩阵

  • 👍 147
  • 👎 0
  • 海星,理解了题意的话就比较简单了(补充更新)
    给个图吧,题目写得太拗口了

    给出如图的矩阵grid[][],给出了对应坐标row = 3col = 4,和一个color随便是什么,并不重要

    要求把row, col位置连通分量的边界的值修改为color

    题目中并给出了连通分量的定义,即四个方向中任意一个方向上相邻,且值相等的区块,那么自然的边界的定义我们就能理解出来,如果上下左右4个方向上有任意一个和连通分量的值不同的话,那么就可以说明这个格子是边框,且如果这个连通分量格子本身是矩阵的边界的话,那么他也是边框

    那么按照题意,这个图上的应该要修改的位置就是如下

    理解了这层的话就好办了,直接上DFS

    代码

    class Solution {
    
        int[][] grid;
        int m;
        int n;
        int connectedComponentValue;
        int borderColor;
        boolean[][] visited;
        int[][] dir = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};
    
        public int[][] colorBorder(int[][] grid, int row, int col, int color) {
            this.grid = grid;
            m = grid.length;
            n = grid[0].length;
            visited = new boolean[m][n];
            connectedComponentValue = grid[row][col];
            borderColor = color;
    
            dfs(row,col);
    
            return this.grid;
        }
    
        public void dfs(int row, int col){
            boolean borderFlag = false;
            visited[row][col] = true;
            for (int[] ints : dir) {
                int x = row + ints[0];
                int y = col + ints[1];
    
                if (isOutOfBoundary(x,y)){
                    borderFlag = true;
                    continue;
                }
                if (visited[x][y]){
                    continue;
                }
                if (grid[x][y] != connectedComponentValue){
                    borderFlag = true;
                    continue;
                }
                dfs(x,y);
            }
            if (borderFlag){
                grid[row][col] = borderColor;
            }
        }
    
    //    boolean isEdge(int row, int col){
    //        return row == 0 || col == 0 || row == m-1 || col==n-1;
    //    }
    
        boolean isOutOfBoundary(int row, int col){
            return row < 0 || col < 0 || row >= m || col >= n;
        }
    }

    补充更新个小细节

    1. isOutOfBoundary判断需要在visited[x][y]之前,为了防止发生越界错误
    2. grid[x][y] != connectedComponentValue判断需要在visited[x][y]之后,深搜会先改变染色掉之前访问到的格子,如果不先判断visited[x][y]的话会导致类似缩圈的效果,把整个连通区域全都染色

    LeetCode刷题【剑指 Offer 38】字符串的排列

    输入一个字符串,打印出该字符串中字符的所有排列。

     

    你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。

     

    示例:

    输入:s = "abc"
    输出:["abc","acb","bac","bca","cab","cba"]
    

     

    限制:

    1 <= s 的长度 <= 8

    Related Topics
    • 字符串
    • 回溯

  • 👍 573
  • 👎 0
  • 回溯基本操作了

    解题思路

    回溯基本操作了

    代码

    class Solution {
    
    
        private List<String> list;
    
        private Integer length;
    
        public String[] permutation(String s) {
            list = new ArrayList<>();
            length = s.length();
            boolean[] visited = new boolean[s.length()];
            dfs(s.toCharArray(),visited,0,new char[s.length()]);
            String[] res = new String[list.size()];
            int i = 0;
            for (String s1 : list) {
                res[i] = s1;
                i++;
            }
            return res;
        }
    
    
        private void dfs(char[] charArr,boolean[] visited,int index,char[] newCharArr){
            if (index==length){
                list.add(new String(newCharArr));
                return;
            }
            Set<Character> sameChar = new HashSet<>();
            for (int i = 0; i < charArr.length; i++) {
                if (visited[i]){
                    continue;
                }
                if (sameChar.contains(charArr[i])){
                    continue;
                }
                sameChar.add(charArr[i]);
                visited[i] = true;
                newCharArr[index] = charArr[i];
                dfs(charArr,visited,index+1,newCharArr);
                visited[i] = false;
            }
        }
    }

    LeetCode刷题【剑指 Offer 33】二叉搜索树的后序遍历序列

    输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

     

    参考以下这颗二叉搜索树:

         5
        / \
       2   6
      / \
     1   3

    示例 1:

    输入: [1,6,3,2,5]
    输出: false

    示例 2:

    输入: [1,3,2,6,5]
    输出: true

     

    提示:

    1. 数组长度 <= 1000
    Related Topics
    • 二叉搜索树
    • 递归
    • 二叉树
    • 单调栈

  • 👍 541
  • 👎 0
  • 分段验证

    解题思路

    分段验证

    代码

    class Solution {
        public boolean verifyPostorder(int[] postorder) {
            return validate(postorder,0,postorder.length-1);
        }
    
    
        private boolean validate(int[] postorder, int left ,int right){
            //如果左指针大于等于右指针了,说明当前节点以及没有子节点了,自然是符合条件的】
            if (left>=right || left < 0 || right < 0) return true;
            //找到这段数组对应的根节点,根据后序遍历的特性,即为这段数组的最后一位
            int rootNum = postorder[right];
            //初始赋值
            int leftEnd = -1;
            int rightStart = -1;
            //开始遍历
            for (int i = left; i < right; i++) {
                if (postorder[i] < rootNum){
                    //如果这个值小于根节点的值,说明这个节点应该是在左子树中
                    leftEnd = i;
                }
                if (postorder[i] > rootNum && rightStart == -1){
                    //如果这个值大于根节点的值,说明这个节点应该是右子树上的
                    //且rightStart == -1 表示是第一次碰到的
                    rightStart = i;
                }
            }
            //此时如果符合条件的话,应该是 leftEnd 在 rightStart 的左边一位
            //或者  没有左子树:leftEnd == -1 且rightStart == left
            //或者  没有右子树:rightStart == -1 且leftEnd == right-1
            boolean validateResult = (leftEnd>-1 &&  rightStart> -1 && leftEnd+1== rightStart)
                    || ( leftEnd == -1 && rightStart == left )
                    || ( rightStart == -1 && leftEnd == right-1);
            //自身验证完了,还要对分割好了的子序列的有效性判断
            if (validateResult){
                return validate( postorder, left, leftEnd ) && validate( postorder, rightStart, right-1 );
            }
            return false;
        }
    }