Page 21 of 61

LeetCode刷题【488】祖玛游戏

你正在参与祖玛游戏的一个变种。

在这个祖玛游戏变体中,桌面上有 一排 彩球,每个球的颜色可能是:红色 'R'、黄色 'Y'、蓝色 'B'、绿色 'G' 或白色 'W' 。你的手中也有一些彩球。

你的目标是 清空 桌面上所有的球。每一回合:

  • 从你手上的彩球中选出 任意一颗 ,然后将其插入桌面上那一排球中:两球之间或这一排球的任一端。
  • 接着,如果有出现 三个或者三个以上颜色相同 的球相连的话,就把它们移除掉。
    • 如果这种移除操作同样导致出现三个或者三个以上且颜色相同的球相连,则可以继续移除这些球,直到不再满足移除条件。
  • 如果桌面上所有球都被移除,则认为你赢得本场游戏。
  • 重复这个过程,直到你赢了游戏或者手中没有更多的球。

给你一个字符串 board ,表示桌面上最开始的那排球。另给你一个字符串 hand ,表示手里的彩球。请你按上述操作步骤移除掉桌上所有球,计算并返回所需的 最少 球数。如果不能移除桌上所有的球,返回 -1

 

示例 1:

输入:board = "WRRBBW", hand = "RB"
输出:-1
解释:无法移除桌面上的所有球。可以得到的最好局面是:
- 插入一个 'R' ,使桌面变为 WRRRBBW 。WRRRBBW -> WBBW
- 插入一个 'B' ,使桌面变为 WBBBW 。WBBBW -> WW
桌面上还剩着球,没有其他球可以插入。

示例 2:

输入:board = "WWRRBBWW", hand = "WRBRW"
输出:2
解释:要想清空桌面上的球,可以按下述步骤:
- 插入一个 'R' ,使桌面变为 WWRRRBBWW 。WWRRRBBWW -> WWBBWW
- 插入一个 'B' ,使桌面变为 WWBBBWW 。WWBBBWW -> WWWW -> empty
只需从手中出 2 个球就可以清空桌面。

示例 3:

输入:board = "G", hand = "GGGGG"
输出:2
解释:要想清空桌面上的球,可以按下述步骤:
- 插入一个 'G' ,使桌面变为 GG 。
- 插入一个 'G' ,使桌面变为 GGGGGG -> empty
只需从手中出 2 个球就可以清空桌面。

示例 4:

输入:board = "RBYYBBRRB", hand = "YRBGB"
输出:3
解释:要想清空桌面上的球,可以按下述步骤:
- 插入一个 'Y' ,使桌面变为 RBYYYBBRRB 。RBYYYBBRRB -> RBBBRRB -> RRRB -> B
- 插入一个 'B' ,使桌面变为 BB 。
- 插入一个 'B' ,使桌面变为 BBBBBB -> empty
只需从手中出 3 个球就可以清空桌面。

 

提示:

  • 1 <= board.length <= 16
  • 1 <= hand.length <= 5
  • boardhand 由字符 'R''Y''B''G''W' 组成
  • 桌面上一开始的球中,不会有三个及三个以上颜色相同且连着的球
Related Topics
  • 广度优先搜索
  • 记忆化搜索
  • 字符串
  • 动态规划

  • 👍 258
  • 👎 0
  • 深度优先搜索方法,board 结尾借个空格好处理点

    上班摸鱼,凑合摸了一个小时多,水平有限

    深度优先搜索,

    删除连续相同颜色的,我在结尾添加了一个空格,代码稍微好写点具体参见reductionChars方法,不过这个还是写的不太完美,需要递归处理,应该有不用递归的写法的,回头再尝试

    具体还是看代码吧,有一些注解,应该能看看吧

    class Solution {
        HashSet<String> visited = new HashSet<>();
    
        int min = 100;
    
        public int findMinStep(String board, String hand) {
            //借个空结尾代码好处理点
            board = board+" ";
            boolean[] handUsed = new boolean[hand.length()];
            char[] boardChars = board.toCharArray();
            dfs(boardChars,hand,handUsed);
            //如果是100表示不能全消除
            return min==100?-1:min;
        }
    
        public void dfs(char[] chars, String hand , boolean[] handUsed){
            if (chars.length==1){
                //统计用了几个
                int usedCount = 0;
                for (boolean used : handUsed) {
                    if (used){
                        usedCount++;
                    }
                }
                min = Math.min(min,usedCount);
                return ;
            }
            //剪枝已经处理过的情况
            if (visited.contains(new String(chars))){
                return ;
            }
            //记录下当前情况已经处理过
            visited.add(new String(chars));
            //从handUsed所有在handUsed中没有标记的已用过的位置中取一个字符
            for (int idx = 0; idx < handUsed.length; idx++) {
                if (handUsed[idx]){
                    continue;
                }
                //标记已使用
                handUsed[idx] = true;
                //构造所有可能的组合情况
                List<char[]> list = insertChar(chars,hand.charAt(idx));
                for (char[] charArr : list) {
                    //继续递归
                    dfs(reductionChars(charArr),hand,handUsed);
                }
                //回溯标记未使用
                handUsed[idx] = false;
            }
        }
    
        /**
         * 往`char[] chars` 数组中所有能插入的位置插入一个新的字符`c`
         * 如果原来的被 插入位置和当前插入字符相同,只插入到后面一个位置
         * @param chars
         * @param c
         * @return
         */
        public List<char[]> insertChar(char[] chars, char c){
            List<char[]> list = new ArrayList<>();
            int idx = -1;
            while (++idx < chars.length){
                while (chars[idx]==c){
                    idx++;
                }
                char[] newChars = new char[chars.length+1];
                System.arraycopy(chars,0,newChars,0,idx);
                System.arraycopy(chars,idx,newChars,idx+1,chars.length-idx);
                newChars[idx] = c;
                list.add(newChars);
            }
            return list;
        }
    
        /**
         * 删除字符串数组中连续出现次数大于等于3的区段内容
         * @param chars
         * @return
         */
        public char[] reductionChars(char[] chars){
            //记录下当前的长度
            int length = chars.length;
            int idx = 0;
            char lastChar = chars[0];
            int lastCharCount = 1;
            while (++idx < chars.length){
                if (chars[idx] == lastChar){
                    lastCharCount++;
                }else{
                    //当前字符和前一个不同
                    if (lastCharCount>=3){
                        //从当前位置往前删除掉lastCharCount个
                        char[] newChars = new char[chars.length-lastCharCount];
                        System.arraycopy(chars,0,newChars,0,idx-lastCharCount);
                        System.arraycopy(chars,idx,newChars,idx-lastCharCount,chars.length-idx);
                        chars = newChars;
                        idx -= lastCharCount;
                    }
                    lastChar = chars[idx];
                    lastCharCount = 1;
                }
            }
            //如果没有能处理删除掉的字符则直接返回
            //如果长度发生了变化,尝试再处理一次,防止   WWRRRWW 变成了 WWWW后还要再处理一次的情况
            return length == chars.length?chars:reductionChars(chars);
        }
    }

    LeetCode刷题【剑指 Offer 59 – II】队列的最大值

    请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_valuepush_backpop_front均摊时间复杂度都是O(1)。

    若队列为空,pop_frontmax_value 需要返回 -1

    示例 1:

    输入: 
    ["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
    [[],[1],[2],[],[],[]]
    输出: [null,null,null,2,1,2]
    

    示例 2:

    输入: 
    ["MaxQueue","pop_front","max_value"]
    [[],[],[]]
    输出: [null,-1,-1]
    

     

    限制:

    • 1 <= push_back,pop_front,max_value的总操作数 <= 10000
    • 1 <= value <= 10^5
    Related Topics
  • 设计
  • 队列
  • 单调队列

  • 👍 370
  • 👎 0
  • 双队列空间换时间

    1. 一个queue用来保存正常顺序的队列Queue<Integer> queue
    2. 另一个双端队列Deque<Integer> deque ,从队尾插入的时候时候需要从队列尾部往前删除比自己小的值,直到遇到一个大于等于当前需要插入的值的时候停止
    3. 这样这个双端队列deque从队头读取的时候,能保证,对头元素是当前所有插入的值中的最大值
    4. 当从queue中删除头部的元素的时候,对比下deque中队首的元素是否是当前被从queue中删除的那个,如果是的话,则同时删除双端队列deque的队首元素
    5. 这样双端队列deque删除队首元素的时候,下一个小于等于当前删除的队首元素的,且位置是位于当前queue队首那个元素到结尾这个区间内的最大元素,那么符合题意的设计就完成了

    class MaxQueue {
    
        Queue<Integer> queue;
    
        Deque<Integer> deque;
    
        public MaxQueue() {
            queue = new ArrayDeque<>();
            deque = new ArrayDeque<>();
        }
        
        public int max_value() {
            if (queue.isEmpty()){
                return -1;
            }
            return deque.peekFirst();
        }
        
        public void push_back(int value) {
            queue.add(value);
            while (!deque.isEmpty() && deque.peekLast() < value){
                deque.pollLast();
            }
            deque.offer(value);
        }
        
        public int pop_front() {
            if (queue.isEmpty()){
                return -1;
            }
            if (queue.peek().equals(deque.peekFirst())){
                deque.pollFirst();
            }
            return queue.poll();
        }
    }

    LeetCode刷题【剑指 Offer 12】矩阵中的路径

    给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false

    单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

     

    例如,在下面的 3×4 的矩阵中包含单词 “ABCCED”(单词中的字母已标出)。

    示例 1:

    输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
    输出:true
    

    示例 2:

    输入:board = [["a","b"],["c","d"]], word = "abcd"
    输出:false
    

     

    提示:

    • 1 <= board.length <= 200
    • 1 <= board[i].length <= 200
    • boardword 仅由大小写英文字母组成

     

    注意:本题与主站 79 题相同:https://leetcode-cn.com/problems/word-search/

    Related Topics
  • 数组
  • 回溯
  • 矩阵

  • 👍 577
  • 👎 0
  • 回溯模板题了已经相当

    回溯模板题了已经相当于。

    1. 找一个开头匹配的
    2. 往四个方向开始匹配下一个字符
    3. 中途标记已经访问过的节点
    4. 退出递归的时候记得要把访问过的节点标记为未访问过
    class Solution {
        boolean[][] visited;
    
        char[][] board;
    
        int[][] dist = {{1,0},{-1,0},{0,1},{0,-1}};
    
        public boolean exist(char[][] board, String word) {
            visited = new boolean[board.length][board[0].length];
            this.board = board;
    
            for (int row = 0; row < board.length; row++) {
                for (int col = 0; col < board[row].length; col++) {
                    if (board[row][col] == word.charAt(0) && searchWord(word,0, row, col)){
                        return true;
                    }
                }
            }
            return false;
        }
    
        boolean searchWord(String word, int idx, int row,int col){
            if (idx == word.length()-1 && word.charAt(idx) == board[row][col]){
                return true;
            }
            if (word.charAt(idx) != board[row][col]){
                return false;
            }
            visited[row][col] = true;
    
            for (int[] ints : dist) {
    
                int x = row + ints[0];
                int y = col + ints[1];
                if ( x<0 || y<0 || x >= board.length || y >= board[0].length || visited[x][y] ){
                    continue;
                }
    
                if (searchWord(word,idx+1,x,y)){
                    return true;
                }
            }
            visited[row][col] = false;
            return false;
        }
    }

    LeetCode刷题【剑指 Offer 31】栈的压入、弹出序列

    输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。

     

    示例 1:

    输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
    输出:true
    解释:我们可以按以下顺序执行:
    push(1), push(2), push(3), push(4), pop() -> 4,
    push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
    

    示例 2:

    输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
    输出:false
    解释:1 不能在 2 之前弹出。
    

     

    提示:

    1. 0 <= pushed.length == popped.length <= 1000
    2. 0 <= pushed[i], popped[i] < 1000
    3. pushed 是 popped 的排列。

    注意:本题与主站 946 题相同:https://leetcode-cn.com/problems/validate-stack-sequences/

    Related Topics
  • 数组
  • 模拟

  • 👍 324
  • 👎 0
  • Java 栈模拟,分步分析理解

    直接模拟

    1. 构造一个Stack,以及两个下标位置标记idxPopidxPush
    2. 一开始为空,先压入一个pushed
    3. 判断栈顶元素是否和popped当前下标的的值一样,如果一样,则说明当前应该进行弹出操作,给stack.pop();同时idxPop++;
    4. 持续执行步骤3,直到不需要弹出为止
    5. 再次回到步骤2,继续往栈内压入元素,记得同时idxPush++,标记执行到哪一步了
    6. 步骤2至5是是一个完整准确的栈压入、弹出过程、只要按照这个步骤就可以顺利执行到结束
    7. 最终判断idxPopidxPush这两个指针是否都指向了poppedpushed两个数组的结尾,如果都指向了结尾则表明所有数据都进行过了压入栈操作,同时所有数据都进行了弹出栈操作。
    class Solution {
        public boolean validateStackSequences(int[] pushed, int[] popped) {
            Stack<Integer> stack = new Stack<>();
            int idxPop = 0;
            int idxPush = -1;
            while ( ++idxPush < pushed.length){
                stack.add(pushed[idxPush]);
                while (!stack.isEmpty() && stack.peek() == popped[idxPop]){
                    idxPop++;
                    stack.pop();
                }
            }
            return idxPush == pushed.length && idxPop == popped.length;
        }
    }

    LeetCode刷题【剑指 Offer 44】数字序列中某一位的数字

    数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。

    请写一个函数,求任意第n位对应的数字。

     

    示例 1:

    输入:n = 3
    输出:3
    

    示例 2:

    输入:n = 11
    输出:0

     

    限制:

    • 0 <= n < 2^31

    注意:本题与主站 400 题相同:https://leetcode-cn.com/problems/nth-digit/

    Related Topics
  • 数学
  • 二分查找

  • 👍 238
  • 👎 0
  • 模拟分析

    按图分析

    1. 0-9的时候都可以直接返回自己
    2. 10-99一共有90个数字,每个数字两个字符
    3. 100-999一共有900个数字,每个数字三个字符
    4. 1000-9999一共有9000个数字,每个数字四个字符
    5. 后续雷同

    按照这个规律分析,我们只需从头开始,依次从头开始

    1. 减去10个字符
    2. 减去90 * 2 个字符
    3. 减去900 * 3个字符
    4. 减去9000 * 4个字符
    5. 如果不够减了则从这一档的1 x 10^pow 开始,
    • 首先除一下当前档位的字符长度,就知道当前档位的第某个数字,在加上这个档位的开始值1 x 10^pow ,就能得到当前是在哪个数字
    • 再于一下当前档位的数字的长度,就知道了是刚刚求得的结果中的数字,从左往右从下标0开始的第某个数字

    问题

    1. 运算中间的值
     cur + 9 * powOf10 * ( pow+1 ) 

    会超过int型上限,所以最简单的处理办法中间的运算过程用了long型数据

    1. 取最后结果的某一位数字也可以用数学方法,不过偷懒了下直接转字符串了
    代码
    class Solution {
        public int findNthDigit(int n) {
            if (n < 10){
                return n;
            }
            long cur = 10;
            long powOf10 = 10;
            long pow = 1;
            while (cur + 9 * powOf10 * ( pow+1 )< n){
                cur += 9 * powOf10 * (pow+1);
                powOf10 *= 10;
                pow++;
            }
            long order = powOf10 + (n-cur) / (pow+1);
            long idx = (n - cur) % (pow+1);
            String orderStr = Long.toString(order);
            return orderStr.charAt((int)idx)-'0';
        }
    }

    LeetCode刷题【299】猜数字游戏

    你在和朋友一起玩 猜数字(Bulls and Cows)游戏,该游戏规则如下:

    写出一个秘密数字,并请朋友猜这个数字是多少。朋友每猜测一次,你就会给他一个包含下述信息的提示:

    • 猜测数字中有多少位属于数字和确切位置都猜对了(称为 “Bulls”,公牛),
    • 有多少位属于数字猜对了但是位置不对(称为 “Cows”,奶牛)。也就是说,这次猜测中有多少位非公牛数字可以通过重新排列转换成公牛数字。

    给你一个秘密数字 secret 和朋友猜测的数字 guess ,请你返回对朋友这次猜测的提示。

    提示的格式为 "xAyB"x 是公牛个数, y 是奶牛个数,A 表示公牛,B 表示奶牛。

    请注意秘密数字和朋友猜测的数字都可能含有重复数字。

     

    示例 1:

    输入:secret = "1807", guess = "7810"
    输出:"1A3B"
    解释:数字和位置都对(公牛)用 '|' 连接,数字猜对位置不对(奶牛)的采用斜体加粗标识。
    "1807"
      |
    "7810"

    示例 2:

    输入:secret = "1123", guess = "0111"
    输出:"1A1B"
    解释:数字和位置都对(公牛)用 '|' 连接,数字猜对位置不对(奶牛)的采用斜体加粗标识。
    "1123"        "1123"
      |      or     |
    "0111"        "0111"
    注意,两个不匹配的 1 中,只有一个会算作奶牛(数字猜对位置不对)。通过重新排列非公牛数字,其中仅有一个 1 可以成为公牛数字。

     

    提示:

    • 1 <= secret.length, guess.length <= 1000
    • secret.length == guess.length
    • secretguess 仅由数字组成
    Related Topics
  • 哈希表
  • 字符串
  • 计数

  • 👍 242
  • 👎 0
  • 题目有点绕,看的有点懵。还是简单点处理

    第一次遍历要做的事情,
    1. 统计某个数字出现了几次
    2. 如果当前数字在secretguess中有位置对应的,则减去一个,表示待匹配数量减少一个,同时countA++
    3. 本次遍历结束map中留下的是待匹配的数字未被匹配中的数量
    进行第二次遍历
    1. 根据之前匹配的结果,本次遍历只处理未被匹配中的情况
    2. 如果当前guess中这个位置的字符有待匹配的数量,则countB++,表示有一个匹配位置错了
    3. 同时map[guess.charAt(idx)-'0']--表示处理过了一个未被匹配中的情况

    最终输出结果

    代码
    class Solution {
        public String getHint(String secret, String guess) {
            int countA = 0;
            int countB = 0;
            int[] map = new int[10];
            int idx = -1;
            while (++idx < secret.length()){
                map[secret.charAt(idx)-'0']++;
                if (secret.charAt(idx) == guess.charAt(idx)){
                    countA++;
                    map[secret.charAt(idx)-'0']--;
                }
            }
            idx=-1;
            while (++idx < secret.length()){
                if (secret.charAt(idx) != guess.charAt(idx) && map[guess.charAt(idx)-'0'] > 0){
                    countB++;
                    map[guess.charAt(idx)-'0']--;
                }
            }
            return countA + "A" + countB + "B";
        }
    }

    LeetCode刷题【290】单词规律

    给定一种规律 pattern 和一个字符串 s ,判断 s 是否遵循相同的规律。

    这里的 遵循 指完全匹配,例如, pattern 里的每个字母和字符串 str 中的每个非空单词之间存在着双向连接的对应规律。

     

    示例1:

    输入: pattern = "abba", str = "dog cat cat dog"
    输出: true

    示例 2:

    输入:pattern = "abba", str = "dog cat cat fish"
    输出: false

    示例 3:

    输入: pattern = "aaaa", str = "dog cat cat dog"
    输出: false

     

    提示:

    • 1 <= pattern.length <= 300
    • pattern 只包含小写英文字母
    • 1 <= s.length <= 3000
    • s 只包含小写英文字母和 ' '
    • s 不包含 任何前导或尾随对空格
    • s 中每个单词都被 单个空格 分隔
    Related Topics
  • 哈希表
  • 字符串

  • 👍 459
  • 👎 0
  • 哈希表匹配

    s按空格分隔
    pattern对应匹配

    1. 先对比长度
    2. 对比当前字符串是否和之前存过的映射关系一致
    3. 新增映射关系的时候需要判断去重

    也许可以把哈希表key和value反一下?,是不是可以不用额外匹配个existSet

    class Solution {
        public boolean wordPattern(String pattern, String s) {
            HashMap<Integer,String> hashMap = new HashMap<>();
            HashSet<String> existSet = new HashSet<>();
            String[] arr = s.split(" ");
            if (pattern.length() != arr.length){
                return false;
            }
            for (int idx = 0; idx < arr.length; idx++) {
                if (hashMap.containsKey(pattern.charAt(idx)-'a')){
                    if (!hashMap.get(pattern.charAt(idx)-'a').equals(arr[idx])){
                        return false;
                    }
                }else{
                    if (!existSet.contains(arr[idx])){
                        hashMap.put(pattern.charAt(idx)-'a',arr[idx]);
                        existSet.add(arr[idx]);
                    }else{
                        return false;
                    }
    
                }
            }
            return true;
        }
    }