蓝桥杯真题训练第八届

一.第一题迷宫

1.题目

迷宫由10x10相互连通的小房间组成。房间的地板上写着一个很大的字母。我们假设玩家是面朝上坡的方向站立,则: L表示走到左边的房间,R表示走到右边的房间,U表示走到上坡方向的房间,D表示走到下坡方向的房间。

开始的时候,直升机把100名玩家放入一个个小房间内。玩家一定要按照地上的字母移动。迷宫地图如下:

UDDLUULRUL
UURLLLRRRU
RRUURLDLRD
RUDDDDUUUU
URUDLLRRUU
DURLRLDLRL
ULLURLLRDU
RDLULLRDDD
UUDDUDUDLL
ULRDLUURRR

请你计算一下,最后有多少玩家会走出迷宫? 而不是在里边兜圈子。请提交该整数,表示走出迷宫的玩家数目,不要填写任何多余的内容。

(1) 涉及到的知识点

[1]原地转圈,要懂得自己转换为是形成环,在这里就是指返回自己已经访问过的点的时候,就是说明它在原地转圈了

[2]将输入的字符串转换为字符并且存入字符数组的方法

for(int i=0;i<10;i++){
  String s=cin.nextLine();
  maze[i]=s.toCharyArray();
 }

[3] 当每次都想给调用的函数传入一个新的数组时,可以直接用new boolean[]来

		for(int i=0;i<10;i++) {
			for(int j=0;j<0;j++) {
				if((checked(maze,i,j,new boolean[10][10])))//知识点2
				{
					count++;
				}
			}
		}
	}
	static boolean checked(char [][]maze,int i,int j,boolean[][] marked) {}

(2) 解题思路

利用递归的形式,100名玩家一人一个房间,按照房间中字母指示的方向移动,若移动的路线成环,则无法走出迷宫。因此解题的关键在于判断是否有环。我们只需要用一个boolean数组标记到达过的房间,若下次到达的房间已访问过,说明移动路线有环,无法走出迷宫;若到达地图边界,说明走出迷宫。

(3) 代码

package _7th;

import java.util.*;

//知识点1:如果成为环,那么肯定就出不去,原地转要自己懂得转变为是有环,就是返回自己已访问过的地点
public class Main1 {
	private static char [][]maze;
	
	public static void main(String []args) {
		int count=0;
		maze=new char [10][10];
		Scanner cin=new Scanner (System.in);
		for(int i=0;i<10;i++) {
			String s=cin.nextLine();//知识点3
			maze[i]=s.toCharArray();
		}
		
		for(int i=0;i<10;i++) {
			for(int j=0;j<0;j++) {
				if((checked(maze,i,j,new boolean[10][10])))//知识点2
				{
					count++;
				}
			}
		}
	}
	
	static boolean checked(char [][]maze,int i,int j,boolean[][] marked) {
		if(i<0||i>10||j<0||j>10) {
			return true;
		}
		if (marked[i][j]) return false;
		marked[i][j]=true;
		char c=maze[i][j];
		if(c=='U') return checked(maze,i-1,j,marked);
		if(c=='D') return checked(maze,i+1,j,marked);
		if(c=='L') return checked(maze,i,j-1,marked);
		if(c=='R') return checked(maze,i,j+1,marked);
	}
	
}


二.九数算数

1.题目

观察如下的算式:

9213 x 85674 = 789314562

左边的乘数和被乘数正好用到了19的所有数字,每个1次。而乘积恰好也是用到了19的所有数字,并且每个1次。请你借助计算机的强大计算能力,找出满足如上要求的9数算式一共有多少个?

注意:

1.总数目包含题目给出的那个示例。
2. 乘数和被乘数交换后作为同一方案来看待

2.解题思路

由于最后为1-9的全排列,所以我们可以先 全排列,然后在每一种全排列的情况中,用乘法将其分为2部分,因此就把问题转换成,将乘法插入哪个位置的时候,分成的两部分相乘的结果为1-9的没有重复的数字构成,若有的话,则数量加1

3.易错点
(1) 乘积也为9位数,但不能有0

(2) 由于ab=c与ba=c的结果相同,因此最后算出的总数要除以2

4.知识点

(1) 如何利用栈实现全排列。栈的u 是:不用变量去标记下标
其实这题用栈和数组都可以的,用栈的好处就是不用for循环去取每一个数,直接while非空就行,但是数组的话就要for循环去取

利用栈实现抓取法的全排列模板如下:

private static void dfs(Deque<Integer> stack) {
        if (stack.size() == 9) //相当于原来数组的index
        {
            return;
        }
        for (int i = 1; i <= 9; i++) //与原来数组实现的方法保持一致,抓取数字
        {
            if (marked[i])
                continue;
            stack.offer(i);//相当于数组法的number[index]=i;
            marked[i] = true;
            dfs(stack);
            // 回溯
            stack.pollLast();//这点事数组与栈的最大区别,因为栈只能一直存存存,不会自动的更改,而数组的话,你在同一位置存数的话,会取代,因此栈的话要每次都pollLast。
            marked[i] = false;
        }
    }

PS:offer与add区别
两者都是往队列尾部插入元素,不同的时候,当超出队列界限的时候,add()方法是抛出异常让你处理,而offer()方法比较友好,直接返回false。

(2)如何将栈,队列,数组的内容变成字符串

如果栈,队列和数组的内容都是整型的话:

①第一步:**先建立一个StringBuilder 类的变量:

StringBuilder sb=new StringBuilder();

**②第二步:**把栈队列和数组的内容全部加入到StringBuilder中

加入为队列q,则:

while(!q.isEmpty()){
  sb.append(q.poll());
}

**第三步:**将StringBuilder的内容变成字符串

String s=sb.toString();

(3)对于字符串,怎样将其变成两部分,然后变成整形,进行相乘

先用sustring截取想要的字符串 ,注意是左闭右开,然后用相应的XXX.parseXXX()即可

        long a = Long.parseLong(s.substring(0, i));
        long b = Long.parseLong(s.substring(i, 9));
        if (check(a * b)) {
            ans++;
        }

(4) 对于每一个全排列相乘之后如何判断结果是否符合要求,即是否也为1-9的数字构成

先把这个数字转化为字符串类型,然后就可以遍历它的每一位数,这个数为字符型,然后再将这个字符减去48,变成整型数num,再建立一个boolean类的整型数组temp,下标即为0-9,若temp[num]为true的话,说明已经出现过这个数字了,所以返回false,否则就把temp[num]设为true,遍历剩下的数字,如果遍历完,都没有返回的话就说明,这个全排列没有重复且没有0

5.代码

package _7th;

import java.util.*;

public class Main2 {
	private static Deque<Integer> stack=new LinkedList<>();
	private static boolean []marked=new boolean [10];
	private static int ans=0;
	
	static void init() {
		for(int i=0;i<10;i++) {
			marked[i]=false;
		}
	}
	
	static void Gennerate() {
		if(stack.size()==9) {
			StringBuilder sb=new StringBuilder();//知识点2
			Deque<Integer> q=new LinkedList<>(stack);//易错点2
			while(!q.isEmpty()) {
				sb.append(q.poll());
			}
			String s=sb.toString();//知识点3
			for(int i=1;i<=8;i++) {
				long a=Long.parseLong(s.substring(0, i));//知识点和易错点3,substring为下标.且左闭右开
				
				long b=Long.parseLong(s.substring(i,9));
				if(checked(a*b)) {
					ans++;
				}
			}
		}
		for(int i=1;i<=9;i++) {
			if(marked[i]) continue;
			if(marked[i]==false) {
				stack.offer(i);//知识点1
				marked[i]=true;
				Gennerate();
				//回溯
				stack.pollLast();//易错点1
				marked[i]=false;
			}
		}
	}
	
	static boolean checked(long a)//知识点4:把整型数转化为字符串,这样遍历的时候就不用取余,直接下标就可,而且字符串有很多可以直接用的函数
	{
		String s=String.valueOf(a);
		boolean []temp=new boolean [10];//知识点5:整型数组一旦创建是不是默认为0?布尔型数组一旦创建是不是默认就是false
		if(s.length()!=9) return false;
		for(int i=0;i<9;i++) {
			int num=s.charAt(i)-'0';//易错点4:数字的字符转为整型要减去'0'
			if(num==0) return false;
			if(temp[num]==true) return false;
			temp[num]=true;
		}
		return true;
	}
	
	public static void main(String []args) {
		init();
		Gennerate();
		System.out.print(ans/2);
	}

}

三、方块分割

题目
6x6的方格,沿着格子的边线剪开成两部分。要求这两部分的形状完全相同。

如下图所示的方格是一种可行的分割方法。
在这里插入图片描述
试计算一共有多少种不同的分割方法。注意:旋转对称的属于同一种分割法。请提交该整数,不要填写任何多余的内容或说明文字。

1.做题思路

当初做题的时候,是想对每一个方块进行dfs,然后记录路径,对路径中的每一个点的坐标进行一个check判断是否是对称,但是这个思路没有考虑到T字形的问题,因此要换种思路

2.心得

**(1)**以后如果遇到类似的题目,要对每个点进行dfs时,都要取思考一下存不存在T字形的情况,如果存在的话,就要转化思路,主要可以参考两者转化思路的类型,一种是像这题的方法,另一种是像第七届JAVA A的第七题的剪邮票的方法

(2) 每次做题都要考虑是否需要去重

3.正确解题方法

这题要用同步双搜索的方法:

本题不适合使用dfs遍历格子,同样是因为无法解决T字形的问题,即dfs无法遍历T字形的路线,因此我们换一个角度:观察切割线。

在这里插入图片描述
如上图所示,红点为切割线中心,切割线由蓝绿两线组成,这条切割线一定会把图片分成两部分,现在的问题是切割线如何行进才能把图片分割为两个完全相同的部分。

不难看出,当切割线也是中心对称的时候,它分割出的两个图案便是完全相同的(中心对称)。所以我们从中心点开始,对切割线的路线进行dfs,切割线每移动一步,我们就在它中心对称的位置也移动一步,也就是说我们向两个方向同时前进(蓝绿两个方向),当一个方向到达图片边界,另一个方向也在同一时刻到达边界,切割便完成了,这也是dfs的递归边界,至此我们得到了一个可行的分割方案。

最后还需要注意进行去重。因为题目提到“旋转对称的属于同一种分割法”,而我们的dfs算法可以向4个方向行进得到4个相同的形状,所以最后答案要除以4去重。

4.易错点

最后还需要注意进行去重。因为题目提到“旋转对称的属于同一种分割法”,而我们的dfs算法可以向4个方向行进得到4个相同的形状,所以最后答案要除以4去重。

5.代码

import java.util.*;

class Main {
    private static int ans;
    private static int[] dx = { 0, 0, 1, -1 };
    private static int[] dy = { 1, -1, 0, 0 };
    private static boolean[][] marked = new boolean[7][7];

    public static void main(String[] args) {
        dfs(3, 3);
        System.out.println(ans / 4); // 除以4去重
    }

    private static void dfs(int x, int y) {
        // 到达边界,意味完成剪取
        if (x == 0 || x == 6 || y == 0 || y == 6) {
            ans++;
            return;
        }
        if (marked[x][y])
            return;
        marked[x][y] = true;
        marked[6 - x][6 - y] = true;
        for (int i = 0; i < 4; i++)
            dfs(x + dx[i], y + dy[i]);
        // 回溯
        marked[x][y] = false;
        marked[6 - x][6 - y] = false;
    }
}

六 动态规划求最大公共子串

掌握动态规划求最大公共子串的方法:

题目:

最大公共子串长度问题就是:求两个串的所有子串中能够匹配上的最大长度是多少。

比如:“abcdkkk” 和 “baabcdadabc”,可以找到的最长的公共子串是"abcd",所以最大公共子串长度为4。

下面的程序是采用矩阵法进行求解的,这对串的规模不大的情况还是比较有效的解法。请分析该解法的思路,并补全划线部分缺失的代码。

public class Main
{
    static int f(String s1, String s2)
    {
        char[] c1 = s1.toCharArray();
        char[] c2 = s2.toCharArray();

        int[][] a = new int[c1.length+1][c2.length+1];

        int max = 0;
        for(int i=1; i<a.length; i++){
            for(int j=1; j<a[i].length; j++){
                if(c1[i-1]==c2[j-1]) {
                    a[i][j] = __________________;  //填空 
                    if(a[i][j] > max) max = a[i][j];
                }
            }
        }

        return max;
    }

    public static void main(String[] args){
        int n = f("abcdkkk", "baabcdadabc");
        System.out.println(n);
    }
}

注意 最大公共子序列和最大公共子串是不一样的,
动态规划求最大子序列:
https://www.cnblogs.com/chenleideblog/p/10455723.html
动态规划求最大子串:
https://www.cnblogs.com/chenleideblog/p/10457320.html

七、正则问题

题目
考虑一种简单的正则表达式:只由 x、( )、| 组成。求出这个正则表达式能接受的最长字符串的长度。

例如, ((xx|xxx)x|(x|xx))xx 能接受的最长字符串是: xxxxxx,长度是6。

输入:
一个由x、()、 |组成的正则表达式。输入长度不超过100,保证合法。
输出:
这个正则表达式能接受的最长字符串的长度。

1.知识点:
(1) 正则表达式的理解

(2)如何写遍历字符串的递归,先去解决一次的问题,然后再去想子问题,就是第一种的情况,遍历时的下标直接设成全局变量即可,这样就不用老是传参了

(3) 对数组从小到大排序

Array.sort()

从大到小排序:

package com.itheimajavase;

import java.util.Arrays;
import java.util.Comparator;

public class Day01 {

    public static void main(String[] args) {

        Integer[] arr = {4, 6, 3, 9, 1, 5, 8};
        Mycomparator c = new Mycomparator();    // 实例化一个Comparator对象
        Arrays.sort(arr, c);
        for(Integer ele : arr) {
            System.out.print(ele +" ");
        }
    }
    // 运行后是从大到小排好序的
}
class Mycomparator implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        if(o1 > o2) // 默认是o1 < o2时返回-1, 一下同理
            return -1;
        if(o1 < o2)
            return 1;
        return 0;
    }
}

2.易错点

while循环跳出后,如果要执行if,注意要和while循环的条件结合起来思考,不然容易出现数组越界问题

3.解题思路

本题要处理的字符串只包含4种字符:( ) | X,数量不多,不妨采用以下思路求解。

把正则表达式分为两部分:

一部分是原表达式中由括号组成的一个更小的子表达式。
另一部分则为原表达式中非括号部分。
例如样例中的((xx|xxx)x|(x|xx))xx这个表达式分为:

((xx|xxx)x|(x|xx))即原式括号内的子表达式。当然,这个子表达式中还有更小的子表达式。
XX即原式中的非括号部分。
我们循环遍历每个字符,计数所能接受的最长字符串的长度,同时,每遇到一个子表达式,我们递归地去求解这个表达式接受的最大长度。也就是说,每层递归中,我们遍历当前子表达式,每个字符有4种可能:

(,说明即将遇到一个子表达式,递归求解该子表达式的长度,累加到当前结果cur。
),递归边界,说明一个子表达式结束,返回结果Math.max(cur, pre),其中,pre用于记录当前表达式中|符号前面的最大长度。
|,说明该符号前后两侧均有一个长度,我们要取两者中较大者,因此需要用变量pre记录之前的结果。
X,增加当前X字符的数量。
最后,指针p遍历完表达式返回结果Math.max(cur, pre)。

4.代码如下:

import java.util.*;

public class Main {
    private static char[] s; // 输入字符串
    private static int p;// 指示当前正在处理的字符下标

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        s = sc.nextLine().toCharArray();
        System.out.println(helper());
    }

    // 递归地求解当前括号内的正则表达式所能接受的最长字符串的长度
    private static int helper() {
        int cur = 0, pre = 0; // 两个变量用于解决 | 两边的最大长度
        while (p < s.length) {
            if (s[p] == '(') {
                p++;
                cur += helper(); // 递归地求解这对括号内的结果
            } else if (s[p] == ')') {
                return Math.max(cur, pre); // 当前括号内求解结束,返回结果
            } else if (s[p] == '|') {
                pre = Math.max(pre, cur); // 记录 | 之前的最大值
                cur = 0; // 重新求 | 之后的值
            } else {
                cur++; // X个数加一
            }
            p++; // 处理下一个字符
        }
        return Math.max(cur, pre); // 返回最大值
    }
}

九 分巧克力

题目

儿童节那天有K位小朋友到小明家做客。小明一共有N块巧克力,其中第i块是Hi x Wi的方格组成的长方形。小明需要从这 N 块巧克力中切出K块巧克力分给小朋友们,为了公平起见,切出的巧克力需要满足:

形状是正方形,边长是整数
大小相同
例如一块6x5的巧克力可以切出3块2x2的巧克力或者2块3x3的巧克力。当然小朋友们都希望得到的巧克力尽可能大,你能帮小Hi计算出最大的边长是多少么?

输入:
第一行包含两个整数N和K。(1 <= N, K <= 100000)
以下N行每行包含两个整数Hi和Wi。(1 <= Hi, Wi <= 100000)
输入保证每位小朋友至少能获得一块1x1的巧克力。

输出:
输出切出的正方形巧克力最大可能的边长。

样例输入:
2 10
6 5
5 6

样例输出:
2

1.知识点:

(1) 二分法找右端点,以及二分法防止溢出的Mid的写法,超易错

要注意二分过程可能引起死循环的问题。两个指针在循环中的变化为:lo = mid;和hi = mid - 1;,因此若mid = lo + (hi - lo) / 2;则当lo和hi相差1时,将可能出现死循环,因此要变成 mid=lo+(hi-lo+1)/2

详情看以前的博客,有记录二分法查找的

2.解题思路
将n块巧克力切成k块大小相同且边长为整数的正方形,求这样的正方形边长最大为多少。

显然,我们可以从小到大暴力枚举正方形的边长,然后将各个巧克力块按照该正方形来切分,检查切出来的数量是否大于等于k。方法是可行的,但是时间复杂度过高。

既然我们从小到大枚举,那自然可以用二分法加快枚举的过程,实现起来是不难的,但有几个细节:

①切分时的制约因素是各巧克力块长宽中的较小者,所以我们仅存储较小者即可。

②可以将存储长宽较小者的数组按降序排列,这样一旦巧克力块无法满足当前正方形边长的切分需求,可提前退出循环;此外,当切分的数量已达到k时也可提前退出循环。

③要注意二分过程可能引起死循环的问题。两个指针在循环中的变化为:lo = mid;和hi = mid - 1;,因此若mid = lo + (hi - lo) / 2;则当lo和hi相差1时,将可能出现死循环,因此要做一些不影响逻辑的细节处理,如下面代码所示。
代码

3.代码如下:

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int k = sc.nextInt();
        // 切分的制约因素是长宽中的较小者,len数组储存各巧克力块的长宽较小者
        Integer len[] = new Integer[n];
        for (int i = 0; i < n; i++)
            len[i] = Math.min(sc.nextInt(), sc.nextInt());
        Arrays.sort(len, (a, b) -> b - a); // 各巧克力边长按降序排列
        int lo = 1, hi = 100000;
        while (lo < hi) {
            int mid = lo + (hi - lo + 1) / 2; // 防止死循环
            int cnt = 0; // 以mid作为每个人得到的巧克力的边长,cnt表示可以切给多少人
            for (int i : len) {
                if (i < mid)
                    break; // 巧克力太小,不能再切
                if (cnt >= k)
                    break;// 已切够数量
                cnt += i / mid * i / mid;
            }
            if (cnt >= k)
                lo = mid;
            else
                hi = mid - 1;
        }
        System.out.println(lo); // 最大边长
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值