【备战蓝桥】 算法·每日一题(详解+多解)-- day3

97 阅读6分钟

@[TOC](【备战蓝桥】 算法·每日一题(详解+多解)-- day3)

✨博主介绍

🌊 作者主页:苏州程序大白

🌊 作者简介:🏆CSDN人工智能域优质创作者🥇,苏州市凯捷智能科技有限公司创始之一,目前合作公司富士康、歌尔等几家新能源公司

💬如果文章对你有帮助,欢迎关注、点赞、收藏

💅 有任何问题欢迎私信,看到会及时回复 💅关注苏州程序大白,分享粉丝福利

第一题

题目描述:

给定一个可能含有重复元素的整数数组,要求随机输出给定的数字的索引。 您可以假设给定的数字一定存在于数组中。 注意 数组大小可能非常大。 使用太多额外空间的解决方案将不会通过测试。

class Solution {
    int[]arr;
    public Solution(int[] nums) {
    arr=nums.clone();
    }
    
    public int pick(int target) {
    int count=0;
        for(int i=0;i<arr.length;i++){
            if(arr[i]==target) count++;
        }
        int choose=new Random().nextInt(count)+1;
        count=0;
        for(int i=0;i<arr.length;i++){
            if(arr[i]==target){
                count++;
                if(count==choose) return  i;
            }
        }
        return  -1;
    }
}

第二题

题目描述:

寻找两个正序数组的中位数,给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。 算法的时间复杂度应该为 O(log (m+n)) 。

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
   int m = nums1.length;
        int n = nums2.length;
        int left = (m + n + 1) / 2;
        int right = (m + n + 2) / 2;
        return (findKth(nums1, 0, nums2, 0, left) + findKth(nums1, 0, nums2, 0, right)) / 2.0;
    }
    //i: nums1的起始位置 j: nums2的起始位置
    public int findKth(int[] nums1, int i, int[] nums2, int j, int k){
        if( i >= nums1.length) return nums2[j + k - 1];//nums1为空数组
        if( j >= nums2.length) return nums1[i + k - 1];//nums2为空数组
        if(k == 1){
            return Math.min(nums1[i], nums2[j]);
        }
        int midVal1 = (i + k / 2 - 1 < nums1.length) ? nums1[i + k / 2 - 1] : Integer.MAX_VALUE;
        int midVal2 = (j + k / 2 - 1 < nums2.length) ? nums2[j + k / 2 - 1] : Integer.MAX_VALUE;
        if(midVal1 < midVal2){
            return findKth(nums1, i + k / 2, nums2, j , k - k / 2);
        }else{
            return findKth(nums1, i, nums2, j + k / 2 , k - k / 2);
        }        
    }
}

第三题

题目描述:

正则表达式匹配 给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。 . 匹配任意单个字符 * 匹配零个或多个前面的那一个元素 所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

class Solution {
    public boolean isMatch(String s, String p){
       return db(s,p,0,0);
    }
   
    /***
     * s 原字符串
     * p 正则表达式
     * sindex s的指针
     * pindex p的指针
     *  当 s指针和p指针都指到了末尾了 就结束了
     * @return
     */
    public boolean db(String s, String p,int sindex,int pindex){
        //超出长度
        if(sindex < s.length() && pindex < p.length()){
            //相等的话 返回true
            if(s.charAt(sindex) == p.charAt(pindex) || p.charAt(pindex) == '.'){
                //往下继续判断 没有到最后一个
                if(pindex < p.length()-1){
                    //判断后一位是否为*
                    //取到下一位的数字
                    char next = p.charAt(pindex+1);
                    if(next == '*'){
                        //匹配到了 三种可能 1 一个也不匹配 2 匹配一个 3 匹配多个
                        return db(s,p,sindex,pindex+2) || db(s,p,sindex+1,pindex+2)|| db(s,p,sindex+1,pindex);
                    }else{
                        //往下走
                        return db(s,p,sindex +1,pindex+1);
                    }
                }else{
                    //最后一个了
                    if(sindex == s.length()-1){
                        return true;
                    }else{
                        return false;
                    }
                }
            }else{
                //不相等
               if(pindex < p.length()-1){
                    //判断后一位是否为*
                    //取到下一位的数字
                    char next = p.charAt(pindex+1);
                    if(next == '*'){
                        //1 一个也不匹配
                        return db(s,p,sindex,pindex+2);
                    }else {
                        //不相等
                        return false;
                    }
                }else {
                    //不相等
                    return false;
                }
            }
        }else{
            //超出长度了
            if(sindex == s.length()){
                if(pindex == p.length()){
                    return true;
                }else {
                    //p 没有匹配完 判断后一位是不是 *
                    if(pindex < p.length()-1){
                        //判断后一位是否为*
                        //取到下一位的数字
                        char next = p.charAt(pindex + 1);
                        if (next == '*') {
                            //1 一个也不匹配
                            return db(s, p, sindex, pindex + 2);
                        } else {
                            //不相等
                            return false;
                        }
                    } else {
                        //不相等
                        return false;
                    }
                }
            }else{
                return false;
            }
        }
    }
}

第四题

题目描述:

编写一个程序,通过填充空格来解决数独问题。 数独的解法需 遵循如下规则: 数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图) 数独部分空格内已填入了数字,空白格用 '.' 表示。 在这里插入图片描述

class Solution {
    public void solveSudoku(char[][] board) {
 /**
         * 记录某行,某位数字是否已经被摆放
         */
        boolean[][] row = new boolean[9][9];
        /**
         * 记录某列,某位数字是否已经被摆放
         */
        boolean[][] col = new boolean[9][9];
        /**
         * 记录某 3x3 宫格内,某位数字是否已经被摆放
         */
        boolean[][] block = new boolean[9][9];

        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (board[i][j] != '.') {
                    int num = board[i][j] - '1';
                    row[i][num] = true;
                    col[j][num] = true;
                    // blockIndex = i / 3 * 3 + j / 3,取整
                    block[i / 3 * 3 + j / 3][num] = true;
                }
            }
        }
        dfs(board, row, col, block, 0, 0);
    }

    private boolean dfs(char[][] board, boolean[][] row, boolean[][] col, boolean[][] block, int i, int j) {
        // 找寻空位置
        while (board[i][j] != '.') {
            if (++j >= 9) {
                i++;
                j = 0;
            }
            if (i >= 9) {
                return true;
            }
        }
        for (int num = 0; num < 9; num++) {
            int blockIndex = i / 3 * 3 + j / 3;
            if (!row[i][num] && !col[j][num] && !block[blockIndex][num]) {
                // 递归
                board[i][j] = (char) ('1' + num);
                row[i][num] = true;
                col[j][num] = true;
                block[blockIndex][num] = true;
                if (dfs(board, row, col, block, i, j)) {
                    return true;
                } else {
                    // 回溯
                    row[i][num] = false;
                    col[j][num] = false;
                    block[blockIndex][num] = false;
                    board[i][j] = '.';
                }
            }
        }
        return false;
    }

    private void printBoard(char[][] board) {
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                System.out.print(board[i][j] + " ");
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        char[][] board = new char[][]{
                {'5', '3', '.', '.', '7', '.', '.', '.', '.'},
                {'6', '.', '.', '1', '9', '5', '.', '.', '.'},
                {'.', '9', '8', '.', '.', '.', '.', '6', '.'},
                {'8', '.', '.', '.', '6', '.', '.', '.', '3'},
                {'4', '.', '.', '8', '.', '3', '.', '.', '1'},
                {'7', '.', '.', '.', '2', '.', '.', '.', '6'},
                {'.', '6', '.', '.', '.', '.', '2', '8', '.'},
                {'.', '.', '.', '4', '1', '9', '.', '.', '5'},
                {'.', '.', '.', '.', '8', '.', '.', '7', '9'}
        };
        Solution solution = new Solution();
        solution.printBoard(board);
        solution.solveSudoku(board);
        solution.printBoard(board);
    }
}

第五题

题目描述:

单词接龙:字典 wordList 中从单词 beginWordendWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> ... -> sk: 每一对相邻的单词只差一个字母。 对于 1 <= i <= k 时,每个si都在wordList中。注意, beginWord 不需要在wordList 中。 sk == endWord 给你两个单词beginWordendWord和一个字典 wordList ,返回 从 beginWord endWord的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0 。

class Solution {
    private List<String> baseList = new ArrayList<>(); 

    private Map<String, Boolean> visited = new LinkedHashMap<>(); 

    private boolean flag = false; 

    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        if (beginWord.equals(endWord)) {
            return 0; 
        }

        baseList.addAll(wordList); 

        Queue<String> queue = new LinkedBlockingQueue<>(); 
        queue.add(beginWord); 
        visited.put(beginWord, true);
        int depth = 1; 
        while (queue.size() > 0) {
            int size = queue.size(); 
            while (size-- > 0) {
                String key = queue.poll(); 
                generateNeighbours(key).stream().filter(w -> !visited.containsKey(w) || !visited.get(w))
                    .forEach(w -> {
                        if (w.equals(endWord)) {
                            flag = true; 
                        }

                        visited.put(w, true); 
                        queue.add(w); 
                }); 

                if (flag) {
                    return depth + 1;
                }
            }

            if (queue.size() > 0) {
                depth++; 
            }
        }
        if (!flag) {
            return 0; 
        } else {
            return depth; 
        }
    }

    private List<String> generateNeighbours(String word) {
        List<String> neighbours = new ArrayList<>(); 

        neighbours.addAll(baseList.stream().filter(w -> {
            int index = 0; 
            int len = word.length(); 
            int l = w.length(); 
            if (len != l) {
                return false; 
            }

            for (int i = 0; i < len; ++i) {
                if (word.charAt(i) == w.charAt(i)) {
                    index++; 
                }
            }

            if (index + 1 == len) {
                return true; 
            } else {
                return false; 
            }
        }).collect(Collectors.toList()));

        return neighbours;  
    }
}

第六题

题目描述:

移除盒子: 给出一些不同颜色的盒子 boxes ,盒子的颜色由不同的正数表示。 你将经过若干轮操作去去掉盒子,直到所有的盒子都去掉为止。每一轮你可以移除具有相同颜色的连续 k 个盒子(k >= 1),这样一轮之后你将得到 k * k 个积分。 返回 你能获得的最大积分和 。

class Solution {
    int max = 0;
    public int removeBoxes(int[] boxes) {
        dfs(boxes,0);
        return max;
    }

    private void dfs(int[] boxes,int total){
        int n = boxes.length;
        if(n==0){
            max = Math.max(total,max);
            return;
        }
        int cur = 0;
        int count = 0;
        int start = 0;
        for(int i=0;i<n;i++){
            if(boxes[i]!=cur){
                if(count>0){
                    dfs(getNewList(boxes,start,count),total+count*count);
                }
                start = i;
                cur = boxes[i];
                count=1;
            }else{
                count++;
            }
            if(i==n-1){
                if(start+count==n){
                    dfs(getNewList(boxes,start,count),total+count*count);
                }
            }
        }
        
    }

    private int[] getNewList(int[]boxes,int start,int count){
        int n = boxes.length;
        int []newlist = new int[n-count];
        for(int i=0;i<n-count;i++){
            if(i<start){
                newlist[i] = boxes[i];
            }else{
                newlist[i] = boxes[i+count];
            }
        }
        return newlist;
    }
}

第七题

题目描述:

祖玛游戏:你正在参与祖玛游戏的一个变种。 在这个祖玛游戏变体中,桌面上有 一排 彩球,每个球的颜色可能是:红色 'R'黄色 'Y'蓝色 'B'绿色 'G' 白色 'W' 。你的>手中也有一些彩球。 你的目标是 清空 桌面上所有的球。每一回合: 从你手上的彩球中选出 任意一颗 ,然后将其插入桌面上那一排球中:两球之间或这一排球的任一端。 接着,如果有出现 三个或者三个以上 且 颜色相同 的球相连的话,就把它们移除掉。 如果这种移除操作同样导致出现三个或者三个以上且颜色相同的球相连,则可以继续移除这些球,直到不再满足移除条件。 如果桌面上所有球都被移除,则认为你赢得本场游戏。 重复这个过程,直到你赢了游戏或者手中没有更多的球。 给你一个字符串 board ,表示桌面上最开始的那排球。另给你一个字符串hand,表示手里的彩球。请你按上述操作步骤移除掉桌上所有球,计算并返回所需的 最少 球数。如果不能移除桌上所有的球,返回-1

class Solution {
    private static final int MAX = 0x3F3F3F3F;

    public int findMinStep(String board, String hand) {
        //1<<hand.length:所有球的使用情况,初始都是未使用,用0表示
        int ans = memoization(board, hand, new HashMap<>(), 1 << hand.length());
        return ans == MAX ? -1 : ans;
    }

    private int memoization(String board, String hand, Map<String, Integer> cache, int cur) {
        if (board.length() == 0) return 0;
        if (cache.containsKey(board)) return cache.get(board);
        int ans = MAX;
        //遍历手中的所有球
        for (int i = 0; i < hand.length(); i++) {
            //如果当前球已经用过,则不再使用
            if (((cur >> i) & 1) == 1) continue;
            //当前球没有被用过,使用当前球发射,状态压缩标记为已经使用
            int next = (1 << i) | cur;
            //枚举所有插入位置
            for (int j = 0; j <= board.length(); j++) {
                //剪枝,对于RRWW来说,手中的球如果是R,插入第一个位置和第二个位置的情况是一样的
                if (j > 0 && j < board.length() - 1 && board.charAt(j) == board.charAt(j - 1)) continue;
                //剪枝,如果选出的球的颜色和插入的球的颜色不相同,没必要进行下去,即便产生连续消除也需要有至少两个同色,比如board=RRWWR,hand=W,W只能插入在W附近才有意义,如果是RRWR,无论W插在哪都没有意义
                if (j > 0 && j < board.length() - 1 && board.charAt(j) != hand.charAt(i)) continue;
                //curBoard记录插入当前球后的情况
                StringBuilder curBoard = new StringBuilder();
                curBoard.append(board, 0, j).append(hand.charAt(i));
                if (j != board.length()) curBoard.append(board.substring(j));
                //双指针进行消除相同颜色的球,StringBuilder是引用传递,不需要使用返回结果进行更新
                eliminateSameColor(curBoard, j);
                ans = Math.min(ans, memoization(curBoard.toString(), hand, cache, next) + 1);
            }
        }
        cache.put(board, ans);
        return ans;
    }

    private void eliminateSameColor(StringBuilder curBoard, int i) {
        //从i位置进行扩散消除
        while (i >= 0 && i < curBoard.length()) {
            int left = i, right = i;
            char c = curBoard.charAt(i);
            while (left >= 0 && curBoard.charAt(left) == c) {
                left--;
            }
            while (right < curBoard.length() && curBoard.charAt(right) == c) {
                right++;
            }
            //如果有3个或者以上相同色的球,就进行消除
            if (right - left > 3) {
                curBoard.delete(left + 1, right);
                i = left >= 0 ? left : right;
            } else {
                break;
            }
        }
    }
}

第八题

题目描述:

贴纸拼词:我们有 n 种不同的贴纸。每个贴纸上都有一个小写的英文单词。 您想要拼写出给定的字符串 target ,方法是从收集的贴纸中切割单个字母并重新排列它们。如果你愿意,你可以多次使用每个贴纸,每个贴纸的数量是无限的。 返回你需要拼出 target 的最小贴纸数量。如果任务不可能,则返回 -1 。 注意:在所有的测试用例中,所有的单词都是从 1000 个最常见的美国英语单词中随机选择的,并且 target 被选择为两个随机单词的连接

class Solution {

    private Map<String, Integer> cache;

    public int minStickers(String[] stickers, String target) {
        cache = new HashMap<>();
        int n = stickers.length;
        int[][] stk = new int[n][26];
        for (int i = 0; i < n; i++) {
            for (char c : stickers[i].toCharArray()) {
                stk[i][c - 'a']++;
            }
        }
        return dfs(stk, target);
    }

    private int dfs(int[][] stk, String target) {
        if (target.length() == 0) return 0;
        if (cache.containsKey(target)) return cache.get(target);
        int[] cnt = new int[26];
        for (char c : target.toCharArray()) cnt[c - 'a']++;
        int ans = -1;
        for (int[] ints : stk) {
            if (ints[target.charAt(0) - 'a'] <= 0) continue;
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 26; i++) {
                int max = Math.max(0, cnt[i] - ints[i]);
                for (int j = 0; j < max; j++) sb.append((char) (i + 'a'));
            }
            int sub = dfs(stk, sb.toString());
            if (sub == -1) continue;
            if (ans == -1 || 1 + sub < ans) ans = 1 + sub;
        }
        cache.put(target, ans);
        return ans;
    }
}