LeetCode 348, 285

385 阅读3分钟

LeetCode 348, 285

LeetCode 348 Design Tic-Tac-Toe

链接:leetcode.com/problems/de…

方法:数组

时间复杂度:每次move是O(1)

空间复杂度:O(n)

想法:是一个比较基本的操作,因为题目里面说赢的条件是某一行全是某个人的,或者某一列全是某个人的,或者主对角线或副对角线全是某一个人的,因此最暴力的做法是每次move操作遍历一遍整个棋盘,每次是O(n^2)。但是这样肯定是太慢的。我们对于每次move操作,实际上不需要知道player1和player2具体之前都放在了棋盘的哪里,我们只需要知道,现在要放的这个位置,它所在的行、列、主副对角线(如果在对角线上)是不是全都属于某一个玩家。一种想法是利用数组,如果是player1进行操作,那就对相对应的行列数组,和对角线的变量+1,如果是player2进行的操作,就-1。比方说对于第一行,我们用row[0]代表,player1每放一个棋子在第一行,它就+1,那么如果说row[0]在某个时刻变成了n,那就说明全是第一行全是player1的棋子。反之如果变成了-n,就说明全是player2下的棋子。

代码:

class TicTacToe {
    
    private int[] rows, cols;
    private int diag, revDiag, N;

    /** Initialize your data structure here. */
    public TicTacToe(int n) {
        this.rows = new int[n];
        this.cols = new int[n];
        this.diag = 0;
        this.revDiag = 0;
        this.N = n;
    }
    
    /** Player {player} makes a move at ({row}, {col}).
        @param row The row of the board.
        @param col The column of the board.
        @param player The player, can be either 1 or 2.
        @return The current winning condition, can be either:
                0: No one wins.
                1: Player 1 wins.
                2: Player 2 wins. */
    public int move(int row, int col, int player) {
        int add = player == 1 ? 1 : -1;
        
        rows[row] += add;
        if (rows[row] == N) {
            return 1;
        }
        if (rows[row] == -N) {
            return 2;
        }
        
        cols[col] += add;
        if (cols[col] == N) {
            return 1;
        }
        if (cols[col] == -N) {
            return 2;
        }
        
        if (row == col) {
            diag += add;
            if (diag == N) {
                return 1;
            }
            if (diag == -N) {
                return 2;
            }
        }
        
        if (row + col == N - 1) {
            revDiag += add;
            if (revDiag == N) {
                return 1;
            }
            if (revDiag == -N) {
                return 2;
            }
        }
        
        return 0;
    }
}

LeetCode 285 Inorder Successor in BST

链接:leetcode.com/problems/in…

方法1:dfs中序遍历

时间复杂度:O(n)

空间复杂度:O(n)

想法:最基本的做法,就是把它当成一般的二叉树来做,对于一个二叉树的节点,找它的后继节点。那么就开全局变量prev和cur,当遍历到p节点时,设上prev,然后在它的下一个节点设上cur。最后return cur。这样做的话严格会把所有节点扫一遍,而且因为递归出口是判断节点是不是null,因此我也没想出来能不能用boolean型dfs或者怎么用boolean型dfs。

代码:

class Solution {
    private TreeNode prev = null, cur = null;
    
    public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
        if (root == null || p == null) {
            return null;
        }
        dfs(root, p);
        return cur;
    }
    
    private void dfs(TreeNode root, TreeNode p) {
        if (root == null) {
            return;
        }
        
        dfs(root.left, p);
        
        if (prev != null && cur == null) {
            cur = root;
        }
        else if (root == p) {
            prev = root;
        }
        
        dfs(root.right, p);
    }
}

方法2:迭代型中序遍历

时间复杂度:O(n)

空间复杂度:O(n)

想法:还是把它当成普通的二叉树来做,不过这是利用栈来做中序遍历的做法。与上面的方法比起来,这个做法只要找到了node之后就会break出来,不一定会严格扫描所有的节点。但不管怎么样时间复杂度还是O(n)。

代码:

class Solution {
    public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
        Stack<TreeNode> stack = new Stack<>();
        while (root != null) {
            stack.push(root);
            root = root.left;
        }
        
        boolean found = false;
        while (!stack.isEmpty()) {
            TreeNode node = stack.pop();
            if (found) {
                return node;
            }
            if (node == p) {
                found = true;
            }
            
            
            if (node.right != null) {
                node = node.right;
                while (node != null) {
                    stack.push(node);
                    node = node.left;
                }
            }
        }
        
        return null;
    }
}

方法3:递归、BST性质

时间复杂度:O(h)

空间复杂度:O(h)

想法:使用二叉树的性质来做优化,而不是一味地将它当成普通的二叉树来处理。如果说root.val <= p.val,说明p要么就是root,要么在root的右子树,那么p的后继节点肯定是在root的右子树,因此返回inorderSuccessor(root.right, p);。反之,如果root的值比p的值大,那么p肯定是在root的左子树,p的后继要么是root,要么在root的左子树里面,因此我们先查看一下inorderSuccessor(root.left, p);返回什么,如果说在root的左子树里面能够找到p的后继,也就是说这里的返回值不是null,那就返回它,否则说明左子树里面也没找到。不在右子树、然后左子树也没找到,那就只能是root本身。

代码:

class Solution {
    public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
        if (root == null || p == null) {
            return null;
        }
        
        if (root.val <= p.val) {
            return inorderSuccessor(root.right, p);
        }
        TreeNode l = inorderSuccessor(root.left, p);
        
        return l == null ? root : l;
    }
}

方法4:迭代、BST性质

时间复杂度:O(h)

空间复杂度:O(h)

想法:方法3的迭代写法,在第一个while里面,如果root.val > p.val,就把successor设为root,然后root去左子树;否则的话只让root去右子树。这样做了之后,root在某一刻或许会指向p节点,这个时候,假设说root.right == null,没有右子树,那么当前的successor就是要返回的结果。不然的话,因为我们的root和successor是从上到下这么过来的,这个时刻的successor不是最终结果,因为root的后继,应该是root的右子树里面最靠左的那一个。因此再开一个while一直找到那个节点为止。

代码:

class Solution {
    public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
        if (root == null || p == null) {
            return null;
        }
        
        TreeNode successor = null;
        while (root != null && root != p) {
            if (root.val > p.val) {
                successor = root;
                root = root.left;
            }
            else {
                root = root.right;
            }
        }
        
        if (root.right == null) {
            return successor;
        }
        
        root = root.right;
        while (root != null) {
            successor = root;
            root = root.left;
        }
        
        return successor;
    }
}