华为OD机试(21-40)老题库解析&Java源码系列连载ing



郑重声明: 1.博客中涉及题目为网上搜索而来,若侵权,请联系作者删除。 源码内容为个人原创,仅允许个人学习使用。
2.博客中涉及的源码非官方答案,不保证正确性。
3.切勿将博客内容用于官方考试,若应用于官方考试,后果自行承担,与原作者无关。
4.原作者保留知识版权,且有权向侵权者追究刑事责任

老题库

21.字符串序列判定

题目描述

输入两个字符串S和L,都只包含英文小写字母。S长度<=100,L长度<=500,000。
判定S是否是L的有效子串。
判定规则:
S中的每个字符在L中都能找到(可以不连续),且S在L中字符的前后顺序与S中顺序要保持一致。
(例如,S=”ace”是L=”abcde”的一个子序列且有效字符是a、c、e,而”aec”不是有效子序列,且有效字符只有a、e)

输入描述

输入两个字符串S和L,都只包含英文小写字母。S长度<=100,L长度<=500,000。
先输入S,再输入L,每个字符串占一行。

输出描述

S串最后一个有效字符在L中的位置。(首位从0开始计算,无有效字符返回-1)

输入输出说明
ace
abcde
4a出现的索引为0 c出现的索引为2 e出现的索引为4 ace索引顺序相同,因此ace为abcde的子串,其最后一位为e其索引为4
fgh
abcde
-1fgh三个字符不是abcde的有效子串

源码和解析
解析:

这个题其实理解起来蛮简单,但是有个点需要注意的是若某个字符在目标字符中出现了几次,这个时候该字符到底取哪个索引?总不能只取第一个索引吧。
例如:
输入第一个字符为ace 输入的第二个字符为abece
这个时候匹配的索引情况可能为a-0 c-3 e-[2|4]
很明显,这个ace仍然是abece的有效子串,因为0,3,4这组索引顺序与原字符ace顺序一致。
这个题可以使用正则快速完成,如果对正则不熟悉,也可以使用字符串的indexOf方法来完成

示例代码(正则写法):

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class T21 {
	public static void main(String[] args) {
		String input1 = "ace";
		String input2 = "abeceee";
		String regex = ""; // a[a-z]*c[a-z]*e[a-z]*
		for (int i = 0; i < input1.length(); i++) {
			regex += input1.charAt(i) + "[a-z]*";
		}
		System.out.println(regex);
		Pattern pattern = Pattern.compile(regex);
		Matcher matcher = pattern.matcher(input2);
		// public int end() 返回最后匹配字符之后的偏移量。
		if (matcher.find()) {
			System.out.println(matcher.end() - 1);
		}else{
			System.out.println(-1);
		}
	}
}

示例代码(逐个查找索引法):

import java.util.ArrayList;
import java.util.List;

public class T21 {
	public static void main(String[] args) {
		String input1 = "ace";
		String input2 = "abaeceece";
		// 解决思路:将input1的所有字符在2中出现的索引全部记录下来,将索引存起来 最后按顺序去判断
		List<List<Integer>> indexList = new ArrayList<List<Integer>>();
		for (int i = 0; i < input1.length(); i++) {
			char c = input1.charAt(i);
			List<Integer> indexItem = new ArrayList<>();
			StringBuilder stringBuilder = new StringBuilder(input2);
			while (stringBuilder.indexOf(c + "") != -1) {
				indexItem.add(stringBuilder.indexOf(c + ""));
				stringBuilder.setCharAt(stringBuilder.indexOf(c + ""), '_');
			}
			if (indexItem.size() != 0)
				indexList.add(indexItem);
		}
		// System.out.println(indexList); [[0], [3], [2, 4, 5, 6]]
		// 找出可能出现的排序 即逐个列表查找,若能找到合适的序列,则成功
		List<List<Integer>> resList = new ArrayList<>();
		for (int i = 0; i < indexList.size(); i++) {
			resList = mult2(resList, indexList.get(i));
			// System.out.println(resList);
		}
		System.out.println(resList);
		int finalMaxIndex = -1;// 最后的且最大的那个有序的
		for (List<Integer> indexList1 : resList) {
			boolean flag = true;// 是否出现是有序的
			Integer last = indexList1.get(0);// 上一个索引
			for (int i = 1; i < indexList1.size(); i++) {
				if (indexList1.get(i) <= last) {
					flag = false;
					break;
				}
			}
			if (flag) {
				// 出现一次有序的了
				if (indexList1.get(indexList1.size() - 1) > finalMaxIndex) {
					finalMaxIndex = indexList1.get(indexList1.size() - 1);
				}
			}
		}
		System.out.println(finalMaxIndex);
	}

	// 二维数组 乘以一维数组
	public static List<List<Integer>> mult2(List<List<Integer>> list1,
			List<Integer> list2) {
		List<List<Integer>> objList = new ArrayList<>();
		if (list1.size() == 0) {
			for (Integer item2 : list2) {
				List<Integer> item11 = new ArrayList<>();
				item11.add(item2);
				objList.add(item11);
			}
			return objList;
		}
		for (List<Integer> item : list1) {
			for (Integer item2 : list2) {
				List<Integer> item11 = new ArrayList<>();
				item11.addAll(item);
				item11.add(item2);
				objList.add(item11);
			}
		}
		return objList;
	}
}

[[0, 2], [4, 7], [3, 5, 6, 8]] 对应a c e出现的索引
[[0, 4, 3], [0, 4, 5], [0, 4, 6], [0, 4, 8], [0, 7, 3], [0, 7, 5], [0, 7, 6], [0, 7, 8], [2, 4, 3], [2, 4, 5], [2, 4, 6], [2, 4, 8], [2, 7, 3], [2, 7, 5], [2, 7, 6], [2, 7, 8]] 对应ace索引组合可能出现的情况

22.最长的指定瑕疵度的元音子串

输入描述

开头和结尾都是元音字母(aeiouAEIOU)的字符串为元音字符串,其中混杂的非元音字母数量为其瑕疵度。比如:
“a” 、 “aa”是元音字符串,其瑕疵度都为0
“aiur”不是元音字符串(结尾不是元音字符)
“abira”是元音字符串,其瑕疵度为2
给定一个字符串,请找出指定瑕疵度的最长元音字符子串,并输出其长度,如果找不到满足条件的元音字符子串,输出0。
子串:字符串中任意个连续的字符组成的子序列称为该字符串的子串。

输出描述

首行输入是一个整数,表示预期的瑕疵度flaw,取值范围[0, 65535]。
接下来一行是一个仅由字符a-z和A-Z组成的字符串,字符串长度(0, 65535]。

输入输出说明
0
asdbuiodevauufgh
3uio为瑕疵度为0的最长子串,故长度为3 当然auu也是
2
aeueo
30

解析

该题考查正则表达式的使用。 其中一个隐含的点就是正则匹配时不能找出所有满足的子串。因此可以匹配到一个就把开始字符替换掉。这样能尽量多的找出满足条件的子串。

源码

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class T22 {
	public static void main(String[] args) {
		int flaw =1; // 瑕疵度
		String inputStr = "asdabbuiooodevauufghu"; // 匹配字符
		//开头是元音 结尾是元音 中间出现的非元音字符是瑕疵度
		String regex="";
		if(flaw==0){
			regex="[aeiouAEIOU]{1,}";
		}else{
			regex="[aeiouAEIOU]{1}";
			for(int i=0;i<flaw;i++){
				regex+="[^aeiouAEIOU]{1}[aeiouAEIOU]*";
			}
			regex+="[aeiouAEIOU]{1}";
		}
		
		System.out.println(regex);
		System.out.println(inputStr.matches("[aeiou]{1}"));
//		while(inputStr.contains("[aeiou]{1}"))
		Pattern pattern=Pattern.compile(regex);
		Matcher matcher=pattern.matcher(inputStr);
		int max=0;
		while(matcher.find()){
			System.out.println(inputStr.substring(matcher.start(), matcher.end()));
			if(matcher.end()-matcher.start()>max){
				max=matcher.end()-matcher.start();
			}
			StringBuilder sb=new StringBuilder(inputStr);
			sb.setCharAt(matcher.start(), '_');//每次匹配到之后就把开始字符替换掉 可以一直往后匹配。
			inputStr=sb.toString();
			matcher=pattern.matcher(inputStr);
		}
		System.out.println("长度:"+max);
	}	
}

23.处理器问题

题目描述

某公司研发了一款高性能AI处理器。每台物理设备具备8颗AI处理器,编号分别为0、1、2、3、4、5、6、7。
编号0-3的处理器处于同一个链路中,编号4-7的处理器处于另外一个链路中,不通链路中的处理器不能通信。
如下图所示。现给定服务器可用的处理器编号数组array,以及任务申请的处理器数量num,找出符合下列亲和性调度原则的芯片组合。
如果不存在符合要求的组合,则返回空列表。
亲和性调度原则:
-如果申请处理器个数为1,则选择同一链路,剩余可用的处理器数量为1个的最佳,其次是剩余3个的为次佳,然后是剩余2个,最后是剩余4个。
-如果申请处理器个数为2,则选择同一链路剩余可用的处理器数量2个的为最佳,其次是剩余4个,最后是剩余3个。
-如果申请处理器个数为4,则必须选择同一链路剩余可用的处理器数量为4个。
-如果申请处理器个数为8,则申请节点所有8个处理器。
提示:
任务申请的处理器数量只能是1、2、4、8。\n编号0-3的处理器处于一个链路,编号4-7的处理器处于另外一个链路。
处理器编号唯一,且不存在相同编号处理器。

输入描述

输入包含可用的处理器编号数组array,以及任务申请的处理器数量num两个部分。
第一行为array,第二行为num。例如:
[0, 1, 4, 5, 6, 7] 1
表示当前编号为0、1、4、5、6、7的处理器可用。任务申请1个处理器。
0 <= array.length <= 8
0 <= array[i] <= 7
num in [1, 2, 4, 8]

输出描述

输出为组合列表,当array=[0,1,4,5,6,7],num=1 时,输出为[[0], [1]]

输入[0, 1, 4, 5, 6, 7]
1
输出[[0], [1]]
说明

根据第一条亲和性调度原则,在剩余两个处理器的链路(0, 1, 2, 3)中选择处理器。

由于只有0和1可用,则返回任意一颗处理器即可。

输入[0, 1, 4, 5, 6, 7]
4
输出[[4, 5, 6, 7]]
说明根据第三条亲和性调度原则,必须选择同一链路剩余可用的处理器数量为4个的环

解析

可以把亲和性调度原则封装为一个Map集合。如下
在这里插入图片描述

Java源码示例

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class T23 {

	public static void main(String[] args) {
		String input = "0,1,4,5,6,7";
		int applyNum = 1;
		String[] cArr = input.split(",");
		List<Integer> link1 = new ArrayList<Integer>();
		List<Integer> link2 = new ArrayList<Integer>();
		for (String item : cArr) {
			int num = Integer.parseInt(item);
			if (num <= 3) {
				link1.add(num);
			} else {
				link2.add(num);
			}
		}
		System.out.println(link1);
		System.out.println(link2);
		/**
		 * -如果申请处理器个数为1,则选择同一链路,剩余可用的处理器数量为1个的最佳,其次是剩余3个的为次佳,然后是剩余2个,最后是剩余4个。
		 * -如果申请处理器个数为2,则选择同一链路剩余可用的处理器数量2个的为最佳,其次是剩余4个,最后是剩余3个。
		 * -如果申请处理器个数为4,则必须选择同一链路剩余可用的处理器数量为4个。 -如果申请处理器个数为8,则申请节点所有8个处理器。
		 */
		Map<Integer, Integer[]> map = new HashMap<Integer, Integer[]>();
		Integer[] pri1 = { 1, 3, 2, 4 };
		Integer[] pri2 = { 2, 4, 3 };
		Integer[] pri4 = { 4 };
		Integer[] pri8 = { 8 };
		map.put(1, pri1);
		map.put(2, pri2);
		map.put(4, pri4);
		map.put(8, pri8);

		switch (applyNum) {
		case 1:
			Integer[] pri = map.get(1);
			for (int i = 0; i < pri.length; i++) {
				if (link1.size() == link2.size()) {
					// 两个一样多,那么就任意取一个
					List<List<Integer>> result = new ArrayList<>();
					for (Integer item : link1) {
						List<Integer> itemList = new ArrayList<>();
						itemList.add(item);
						result.add(itemList);
					}
					for (Integer item : link2) {
						List<Integer> itemList = new ArrayList<>();
						itemList.add(item);
						result.add(itemList);
					}
					System.out.println(result);
					break;
				}
				if (link1.size() == pri[i]) {
					// 输出link1内的内容
					List<List<Integer>> result = new ArrayList<>();
					for (Integer item : link1) {
						List<Integer> itemList = new ArrayList<>();
						itemList.add(item);
						result.add(itemList);
					}
					System.out.println(result);
					break;
				}
				if (link2.size() == pri[i]) {
					// 输出link1内的内容
					List<List<Integer>> result = new ArrayList<>();
					for (Integer item : link2) {
						List<Integer> itemList = new ArrayList<>();
						itemList.add(item);
						result.add(itemList);
					}
					System.out.println(result);
					break;
				}
			}
			break;
		case 2:
			Integer[] priTwo = map.get(2);
			for (int i = 0; i < priTwo.length; i++) {
				if (link1.size() == link2.size()) {
					// 随机取出2个
					ArrayList<List<Integer>> result = new ArrayList<List<Integer>>();
					result.addAll(getRandom(link1, applyNum));
					result.addAll(getRandom(link2, applyNum));
					System.out.println(result);
					break;
				}
				if(link1.size()==priTwo[i]){
					ArrayList<List<Integer>> result = new ArrayList<List<Integer>>();
					result.addAll(getRandom(link1, applyNum));
					System.out.println(result);
					break;
				}
				if(link2.size()==priTwo[i]){
					ArrayList<List<Integer>> result = new ArrayList<List<Integer>>();
					result.addAll(getRandom(link2, applyNum));
					System.out.println(result);
					break;
				}
			}
			break;
		case 4:
			if(link1.size()==link2.size()&&link1.size()==4){
				ArrayList<List<Integer>> result = new ArrayList<List<Integer>>();
				List<Integer> itemList = new ArrayList<>();
				itemList.addAll(link1);
				result.add(itemList);
				itemList = new ArrayList<>();
				itemList.addAll(link2);
				result.add(itemList);
				System.out.println(result);
				break;
			}
			if (link1.size() == 4) {
				ArrayList<List<Integer>> result = new ArrayList<List<Integer>>();
				List<Integer> itemList = new ArrayList<>();
				itemList.addAll(link1);
				result.add(itemList);
				System.out.println(result);
				break;
			}
			if (link2.size() == 4) {
				ArrayList<List<Integer>> result = new ArrayList<List<Integer>>();
				List<Integer> itemList = new ArrayList<>();
				itemList.addAll(link2);
				result.add(itemList);
				System.out.println(result);
			}
			break;
		case 8:
			if (link1.size() == 4 && link2.size() == 4) {
				ArrayList<List<Integer>> result = new ArrayList<List<Integer>>();
				List<Integer> itemList = new ArrayList<>();
				itemList.addAll(link1);
				itemList.addAll(link2);
				result.add(itemList);
				System.out.println(result);
			}
			break;
		default:
			break;
		}
	}

	// 随机产生随机下标
	static List<List<Integer>> getRandom(List<Integer> link, int num) {
		List<List<Integer>> resultList = new ArrayList<>();
		if (link.size() == num) {
			resultList.add(link);
		} else if (link.size() > num) {
			// 长度是4 取2个 长度4取3个
			// 长度3取2个
			if (link.size() == 4 && num == 2) {
				List<Integer> items = new ArrayList<>();
				items.add(link.get(0));
				items.add(link.get(1));
				resultList.add(items);

				items = new ArrayList<>();
				items.add(link.get(0));
				items.add(link.get(2));
				resultList.add(items);

				items = new ArrayList<>();
				items.add(link.get(0));
				items.add(link.get(3));
				resultList.add(items);

				items = new ArrayList<>();
				items.add(link.get(1));
				items.add(link.get(2));
				resultList.add(items);

				items = new ArrayList<>();
				items.add(link.get(1));
				items.add(link.get(3));
				resultList.add(items);

				items = new ArrayList<>();
				items.add(link.get(2));
				items.add(link.get(3));
				resultList.add(items);
			} else if (link.size() == 3 && num == 2) {
				List<Integer> items = new ArrayList<>();
				items.add(link.get(0));
				items.add(link.get(1));
				resultList.add(items);

				items = new ArrayList<>();
				items.add(link.get(0));
				items.add(link.get(2));
				resultList.add(items);
				
				items = new ArrayList<>();
				items.add(link.get(1));
				items.add(link.get(2));
				resultList.add(items);
			}

		}
		return resultList;
	}
}

24.单向链表中间节点

题目描述

求单向链表中间的节点值,如果奇数个节点取中间,偶数个取偏右边的那个值。

输入描述

  1. 第一行 链表头节点地址 后续输入的节点数n
  2. 后续输入每行表示一个节点,格式 节点地址 节点值 下一个节点地址(-1表示空指针)
  3. 输入保证链表不会出现环,并且可能存在一些节点不属于链表。

输出描述

单向链表中间的节点值

源码和解析
解析:

  1. 这种题目相对来说较好理解,可以简单理解为将节点按首尾相接的形式排序,首先第一行指定了节点开始地址。后续每行输入节点开始地址,值和结束地址。
  2. 输入时,可以使用循环来接收每个节点的信息。并装入一个ArrayList之中。当接收的节点开始地址等于第一行的开始地址时,证明该节点就是链表的第一个节点。
  3. 编写一个方法,用于取出未排序ArrayList中的目标节点。也即是有序链表的下一个节点。
  4. 这里需要注意的是,取出节点后就将目标节点从原始列表中移出,这样可以减少下一次查找的时间。如果在列表中无法找到目标节点,那么其他节点就不需要管了。我们只需要以开始地址为开头的那个节点形成链表的中间值。其他不在链表之中的就过滤掉即可。

示例代码:

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class T24 {
	static class Point{
		String start;//开始地址
		int value;//值
		String next; //下一个节点地址
		@Override
		public String toString() {
			return "Point [start=" + start + ", value=" + value + ", next="
					+ next + "]";
		}
	}
	public static void main(String[] args) {
		System.out.println("请输入:");
		Scanner scanner=new Scanner(System.in);
		String input=scanner.nextLine();
		Point startPoint=new Point();
		int number=Integer.parseInt(input.split(" ")[1]);
		startPoint.start=input.split(" ")[0];
		List<Point> rawPoints=new ArrayList<T24.Point>();
		//排序后的List
		List<Point> orderList=new ArrayList<>();
		for(int i=0;i<number;i++){
			input=scanner.nextLine();
			Point point=new Point();
			point.start=input.split(" ")[0];
			point.value=Integer.parseInt(input.split(" ")[1]);
			point.next=input.split(" ")[2];
			if(point.start.equals(startPoint.start)){
				startPoint=point;
				orderList.add(point);
			}else{
				rawPoints.add(point);
			}
		}
		Point point=getNextPoint(rawPoints,startPoint.next);
		while(point!=null){
			orderList.add(point);
			rawPoints.remove(point);
			point=getNextPoint(rawPoints,point.next);
		}
		System.out.println(orderList);
		Point middlePoint=null;
		int middle=orderList.size()/2;//下标
		middlePoint=orderList.get(middle);
		System.out.println(middlePoint.value);
		// 长度为5  下标0 1 2 3 4  中间为2===5/2=2  长度为6 下标 0 1 2 3 4 5 中间为3  6/2=3 
	}
	//在List中查找以某个为开始索引的节点
	static Point getNextPoint(List<Point> points,String nextAddress){
		Point point=null;
		if(nextAddress.equals("-1")){
			return point;
		}
		for(int i=0;i<points.size();i++){
			if(points.get(i).start.equals(nextAddress)){
				point=points.get(i);
				break;
			}
		}
		return point;
	}
}

代码运行结果:
在这里插入图片描述
在这里插入图片描述

25.字符串重新排列、字符串重新排序

题目描述

给定一个字符串s,s包括以空格分隔的若干个单词,请对s进行如下处理后输出:
1、单词内部调整:对每个单词字母重新按字典序排序
2、单词间顺序调整:
1)统计每个单词出现的次数,并按次数降序排列
2)次数相同,按单词长度升序排列
3)次数和单词长度均相同,按字典升序排列请输出处理后的字符串,每个单词以一个空格分隔。

输入描述

一行字符串,每个字符取值范围:[a-zA-z0-9]以及空格,字符串长度范围:[1,1000]

输出描述

输出处理后的字符串,每个单词以一个空格分隔。

用例

输入This is an apple
输出an is This aelpp
说明
输入My sister is in the house not in the yard
输出in in eht eht My is not adry ehosu eirsst
说明

源码和解析
解析:

本题目只要对Java中的List排序熟悉的话,就可以轻松进行解决。

示例代码:

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class T25 {
	public static void main(String[] args) {
		String input="My sisiter is in the house not in the yard";
		String[] words=input.split(" ");
		List<String> wordList=new ArrayList<String>();
		for(String word:words){
			//单词内部 对每个单词重新按字典排序
			char[] chArr=word.toCharArray();
			List<Character> characters=new ArrayList<>();
			for(char ch:chArr){
				characters.add(ch);
			}
			characters.sort(new Comparator<Character>() {
				@Override
				public int compare(Character o1, Character o2) {
					if(o1>o2){
						return 1;
					}else if(o1<o2){
						return -1;
					}
					return o1;
				}
			});
			StringBuilder sb=new StringBuilder();
			for(Character c:characters){
				sb.append(c);
			}
			wordList.add(sb.toString());
		}
		//遍历wordList 确定出现的次数
		final Map<String, Integer> wordCountMap=new HashMap<String, Integer>();
		for(String word:wordList){
			if(wordCountMap.containsKey(word)){
				wordCountMap.put(word, wordCountMap.get(word)+1);
			}else{
				wordCountMap.put(word, 1);
			}
		}
		wordList.sort(new Comparator<String>() {
			@Override
			public int compare(String w1, String w2) {
				//1)统计每个单词出现的次数,并按次数 降序排列Q
				//2) 次数相同,按 单词长度升序排列
				//3)次数和单词长度均相同,按字典升序排列
				if(wordCountMap.get(w1)>wordCountMap.get(w2)){
					return -1;
				}else if(wordCountMap.get(w1)<wordCountMap.get(w2)){
					return 1;
				}
				//次数相同的 按 单词长度升序排列
				if(w1.length()>w2.length()){
					return 1;
				}else if(w1.length()<w2.length()){
					return -1;
				}
				//次数和单词长度均相同,按字典升序排列
				int wordLen=w1.length();
				for(int i=0;i<wordLen;i++){
					if(w1.charAt(i)==w2.charAt(i)){
						continue;
					}
					if(w1.charAt(i)>w2.charAt(i)){
						return 1;
					}else{
						return -1;
					}
				}
				return 0;
			}
		});
		for(String s:wordList){
			System.out.print(s+" ");
		}
	}
}

26.完美走位

题目描述

在第一人称射击游戏中,玩家通过键盘的A、S、D、W四个按键控制游戏人物分别向左、向后、向右、向前进行移动,从而完成走位。
假设玩家每按动一次键盘,游戏任务会向某个方向移动一步,如果玩家在操作一定次数的键盘并且各个方向的步数相同时,此时游戏任务必定会回到原点,则称此次走位为完美走位。
现给定玩家的走位(例如:ASDA),请通过更换其中一段连续走位的方式使得原走位能够变成一个完美走位。其中待更换的连续走位可以是相同长度的任何走位。
请返回待更换的连续走位的最小可能长度。
如果原走位本身是一个完美走位,则返回0。

输入描述

输入为由键盘字母表示的走位s,例如:ASDA

输出描述

输出为待更换的连续走位的最小可能长度。

用例

输入WASDAASD
输出1
说明将第二个A替换为W,即可得到完美走位
输入AAAA
输出3
说明将其中三个连续的A替换为WSD,即可得到完美走位

源码和解析
解析:

先匹配出多的字符及个数 那个字符次数-平均值为多余的。即需要在原字符串中找出一个子串,该子串满足多余出来的字符和出现的字数。 如A多了2次 S多了一次 那么查找的子串可以为ASA AAS SSAA等。最后返回的结果是那个最短子串的长度。
使用双指针来解决这个问题。不断地判断两个指针之间的字符串是否满足条件。若满足条件,左指针右移到右指针那个位置,继续寻找下一个子串。最后再将字符串反向来查找一次。就基本能满足大多数测试用例了。

示例代码:

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class T26 {
	public static void main(String[] args) {
		String input = "WASDAASDSDAS";
		char[] chArr = input.toCharArray();
		char C[] = { 'W', 'A', 'S', 'D' };
		Map<Character, Integer> chCountMap = new HashMap<Character, Integer>();
		for (char c : C) {
			chCountMap.put(c, 0);
		}
		// 统计每个字符出现的次数
		for (char c : chArr) {
			chCountMap.put(c, chCountMap.get(c) + 1);
		}
		int avgLen = input.length() / 4;
		// 计算每个字符的差值
		for (char c : C) {
			chCountMap.put(c, chCountMap.get(c) - avgLen);
		}
		// 计算每个字符正和负的情况 其实就是统计正出现的个数即可
		for (char c : C) {
			int count = chCountMap.get(c);
			if (count <= 0)
				chCountMap.remove(c); // 移除负数次数的
		}
		int min = count(input, chCountMap);
		int reverseMin = count(new StringBuilder(input).reverse().toString(),
				chCountMap);
		if (min < reverseMin) {
			System.out.println(min);
		} else {
			System.out.println(reverseMin);
		}
	}

	// 统计双指针找满足条件的最小子串长度
	public static int count(String input, Map<Character, Integer> chCountMap) {
		int start = 0; // 开始索引
		int end = 1;// 结束索引
		int min = Integer.MAX_VALUE;
		String word;
		while (end < input.length()) {
			word = input.substring(start, end);
			if (!chCountMap.containsKey(word.charAt(0))) {
				// 首字母不是 干掉
				start++;
				end++;
				continue;
			}
			if (check(word, chCountMap)) {
				start = end;
				if (word.length() < min) {
					min = word.length();
				}
				end++;
			} else {
				end++;
			}
		}
		return min;
	}

	// 统计 字符串中是否有满足map中字符出现个数的
	public static boolean check(String word, Map<Character, Integer> wordMap) {
		char[] chArr = word.toCharArray();
		Map<Character, Integer> cMap = new HashMap<>();
		for (char c : chArr) {
			if (cMap.containsKey(c)) {
				cMap.put(c, cMap.get(c) + 1);
			} else {
				cMap.put(c, 1);
			}
		}
		Set<Character> keySet = wordMap.keySet();
		boolean flag = true;
		for (Character key : keySet) {
			if (!cMap.containsKey(key))
				return false;
			if (wordMap.get(key) > cMap.get(key))
				return false;
		}
		if (flag)
			return true;
		return false;
	}
}

代码测试结果

输入WASDAASDSDAS

在这里插入图片描述

输入AAAA

在这里插入图片描述

27.最多颜色的车辆

题目描述

在一个狭小的路口,每秒只能通过一辆车,假设车辆的颜色只有 3 种,找出 N 秒内经过的最多颜色的车辆数量。
三种颜色编号为0 ,1 ,2

输入描述

第一行输入的是通过的车辆颜色信息
[0,1,1,2] 代表4 秒钟通过的车辆颜色分别是 0 , 1 , 1 , 2
第二行输入的是统计时间窗,整型,单位为秒

输出描述

输出指定时间窗内经过的最多颜色的车辆数量。

源码和解析
解析:

简单滑动窗口应用。我们可以利用相邻两个滑窗的差异比较,来避免重复的计算。
可以定义两个指针,两个指针同时右移。右移是原来的左侧出一个,右侧进一个。可以定义map来记录数量,若出了则数量-1 若入了则数量+1;
通过每次获取窗口(两个指针间)中的最大值来进行记录。

示例代码:

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

public class T27 {
	public static void main(String[] args) {
		String input = "0 1 2 1 2 2 1 1 2";
		int number = 3;// 秒数
		String[] carColor = input.split(" ");
		int start = 0;
		int end = number;
		Map<String, Integer> map = new HashMap<String, Integer>();
		LinkedList<String> colorList = new LinkedList<>();
		// 0-number 前几个先入库
		for (int i = 0; i < number; i++) {
			if (map.containsKey(carColor[i])) {
				map.put(carColor[i], map.get(carColor[i]) + 1);
			} else {
				map.put(carColor[i], 1);
				colorList.push(carColor[i]);
			}
		}
		int max = 0;
		while (end < carColor.length) {
			// 开始指针与结束指针整体右移动一位 每移动1次 上一次开始指针的那个就减1 右移进来的就加1
			map.put(carColor[start], map.get(carColor[start]) - 1);
			start++;
			if (map.containsKey(carColor[end])) {
				map.put(carColor[end], map.get(carColor[end]) + 1);
			} else {
				map.put(carColor[end], 1);
				colorList.push(carColor[end]);
			}
			end++;
			// 每次循环都找在map中找一下,求最大的那个值
			for (String color : colorList) {
				if (max < map.get(color)) {
					max = map.get(color);
				}
			}
		}
		System.out.println(max);
	}
}

28.不含101的数

题目描述

小明在学习二进制时,发现了一类不含 101的数,也就是:
将数字用二进制表示,不能出现 101 。
现在给定一个整数区间 [l,r] ,请问这个区间包含了多少个二进制不含 101 的整数?

输入描述

输入的唯一一行包含两个正整数 l, r( 1 ≤ l ≤ r ≤ 10^9)。

输出描述

输出的唯一一行包含一个整数,表示在 [l,r] 区间内一共有几个不含 101 的数。

输入1 10
输出8
说明区间 [1,10] 内, 5 的二进制表示为 101 ,10的二进制表示为 1010 ,因此区间 [ 1 , 10 ] 内有 10−2=8 个不含 101的数。
输入10 20
输出7
说明区间 [10,20] 内,满足条件的数字有 [12,14,15,16,17,18,19] 因此答案为 7。

源码和解析
解析:

思路1:
for循环暴力求解。十进制转二进制再转字符串。借助字符串的indexOf来判断是否包含。
这种方式就是区间过大时花费的时间会比较久一些。

示例代码(暴力破解):

public class T28 {
	public static void main(String[] args) {
		String input="1 10";
		int left=Integer.parseInt(input.split(" ")[0]);
		int right=Integer.parseInt(input.split(" ")[1]);
		int count=right-left+1;// 二进制不包含101的个数
		for(;left<=right;left++){
			if(Integer.toBinaryString(left).indexOf("101")!=-1){
				count--;
			}
		}
		System.out.println(count);
	}
}

当输入的值为10和20时,测试输出与结果如下图:
在这里插入图片描述
解析:

思路2:使用简单数位DP算法(数不再是数,而是由多个单字符组成的字符)进行求解
若对数位DP算法不懂的,可以参考我的另一篇博客
【算法】使用数位算法生成0至某个数之间的整数(for循环之外的另一种实现方式,蛮长见识的)

public class T28 {
	public static int raw[] = null;
	public static int num[] = null;
	public static int count = 0;

	public static void main(String[] args) {
		String input = "20 50";
		int left = Integer.parseInt(input.split(" ")[0]);
		int right = Integer.parseInt(input.split(" ")[1]);
		int totalCount = right - left + 1;// 二进制不包含101的个数
		handle(left - 1);
		int leftCount = count;
		count = 0;
		handle(right);
		int rightCount = count;
		System.out.println(totalCount - (rightCount - leftCount));
	}

	public static void handle(int number) {
		int len = (number + "").length();
		raw = new int[len];
		num = new int[len];
		for (int i = 0; i < len; i++) {
			raw[i] = number % 10;
			number /= 10;
		}
		dfs(len - 1, true);
	}

	static StringBuilder sb = new StringBuilder();

	public static void dfs(int p, boolean limit) {
		if (p < 0) {
			for (int i = num.length - 1; i >= 0; i--) {
				sb.append(num[i]);
			}
			if (Integer.toBinaryString(Integer.parseInt(sb.toString()))
					.indexOf("101") != -1) {
				count++;
			}
			sb.setLength(0);
			return;
		}
		int up = limit ? raw[p] : 9;
		for (int i = 0; i <= up; i++) {
			num[p] = i;
			dfs(p - 1, limit&&i==up);
		}
	}
}

算法时间比较:

输入算法输出耗时
1 10数位DP8481000纳秒
1 10暴力破解8454500纳秒
10 20数位DP7507200纳秒
10 20暴力破解7445800纳秒
2000 5000000数位DP376367622331000纳秒
2000 5000000暴力破解376367230676000纳秒

从时间的角度来说,这里并未充分发挥出数位DP算法的优势。但是数位DP算法对数字的长度限制会小很多。

29. 租车骑绿岛

题目描述

部门组织绿岛骑行团建活动。租用公共双人自行车,每辆自行车最多坐两人,最大载重M。
给出部门每个人的体重,请问最多需要租用多少双人自行车。

输入描述

第一行两个数字m、n,分别代表自行车限重,部门总人数。
第二行,n个数字,代表每个人的体重,体重都小于等于自行车限重m。
0<m<=200\n0<n<=1000000

输出描述

最小需要的双人自行车数量。

输入3 4
3 2 2 1
输出3
说明

源码和解析
解析:

其实重点只需要关注用户输入第一行的车载重量M和第二行输入的每个人的重量。

  1. 车载最多2人,可以考虑双指针来解决问题
  2. 为了提高运行效率,肯定需要对每个人的体重进行排序。要保证每个车尽可能地坐2个人,因此可以考虑最重的搭配一个最轻的。
  3. 若单人重量已经等于限重了。那么这个人就骑一个车。若剩余最重的那个人搭载一个最轻的人都还超重,那么这个人也只能一个人骑一个车。剩下的就是最重的搭配最轻的。循环解决问题了。

示例代码:

import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class T29 {
	public static void main(String[] args) {
		String input1 = "4 4";// 第一个是每个车限重 第二个是部门人数
		String input2 = "3 2 2 1";// 每个人的体重 且体重都小于限重
		int m = Integer.parseInt(input1.split(" ")[0]);
		String[] empWeights = input2.split(" ");
		List<Integer> weightList = new LinkedList<Integer>();
		for (String w : empWeights) {
			weightList.add(Integer.parseInt(w));
		}
		//排序 降序
		weightList.sort(new Comparator<Integer>() {
			@Override
			public int compare(Integer o1, Integer o2) {
				if (o1 > o2) {
					return -1;
				} else if (o1 < o2) {
					return 1;
				}
				return 0;
			}
		});
		Map<Integer, String> map=new HashMap<Integer, String>();
		int count=0;
		int right=weightList.size()-1;//右侧指针 左侧从0开始
		for(int i=0;i<=right;i++){
			//如果已经到达体重了 那么就坐一个车
			if(weightList.get(i)==m){
				count++;
				map.put(count, weightList.get(i)+"");
				continue;
			}
			if(weightList.get(i)+weightList.get(right)<=m&&i<right){
				//一个人坐不满足车载极限  应该按最大的那个来加,这样可以保证后续可以尽可能的两个人坐一个车
				count++;
				map.put(count, weightList.get(i)+"-"+weightList.get(right));
				right--;
				continue;
			}else{
				//一个人坐不满足车载极限,两个人哪怕是最小的也超载
				count++;
				map.put(count, weightList.get(i)+"");
				continue;
			}
		}
		System.out.println(map);
	}
}

注意示例中的代码仅仅用于测试,里面有多余的代码。学习的时候仅供参考。这里使用了map记录了车的编号和载重人的体重,其示意结果如下:
假设部门人数为4个 且体重不变。都3 2 2 1
那么其分配的三个车的载重如下:
载重为3:
在这里插入图片描述
载重为4:

在这里插入图片描述

30.等和子数组最小和

题目描述

给定一个数组nums,将元素分为若干个组,使得每组和相等,求出满足条件的所有分组中,组内元素和的最小值。

输入描述

第一行输入 m
接着输入m个数,表示此数组nums
数据范围:1<=m<=50, 1<=nums[i]<=50

输出描述

最小拆分数组和

源码和解析
解析:

这个题其实要理解其具体描述的含义:
首先这组数据 可以分组的数量为哪些?其结果肯定是这组数字和的因数,不然没法直接均分。
然后就是得掌握数字的分组技巧。因为已经有了和,那么就可以求出均值,即确保分到一个组里面的数字和不能超过均值。最后判断分组后的每个值是否都和均值相等。

示例代码:

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class T30 {
	static int sum=0;
	public static void main(String[] args) {
		String input="4 3 2 3 5 2 1";
		String[] strArr=input.split(" ");
		List<Integer> numList=new LinkedList<Integer>();
		for(int i=0;i<strArr.length;i++){
			numList.add(Integer.parseInt(strArr[i]));
			sum+=Integer.parseInt(strArr[i]);
		}
		numList.sort(new Comparator<Integer>() {
			@Override
			public int compare(Integer o1, Integer o2) {
				if(o1>o2)return -1;
				if(o1<o2)return 1;
				return 0;
			}
		});
		//求可能的分成的桶数 最小为1桶 最多为n桶 而桶数必须是和的因数
		List<Integer> groupList=new ArrayList<Integer>();
		for(int i=2;i<=numList.size();i++){
			if(sum%i==0) groupList.add(i);
		}
		int res=sum;//默认为1组 
		for(Integer groupNum:groupList){
			if(canGroup(groupNum,numList)){
				res=sum/groupNum; //groupNum分了几组  由于已经排过序 所以越往后分组越细 直接计算结果即可。
			}
		}
		System.out.println(res);
	}
	public static boolean canGroup(int groupNum,List<Integer> numList){
		int avg=sum/groupNum;
		Map<Integer, Integer> map=new HashMap<Integer, Integer>();// 1,桶名 2桶值
		//初始化分组 组名和默认值
		for(int i=0;i<groupNum;i++){
			map.put(i, 0);
		}
		for(Integer num:numList){
			for(int i=0;i<groupNum;i++){
				if(map.get(i)+num<=avg){
					map.put(i, map.get(i)+num);
					break;
				}
			}
//			System.out.println(map); 需要查看分组过程 可以打开注释
		}
		for(int i=0;i<groupNum;i++){
			if(map.get(i)!=avg){
				return false;
			}
		}
		return true;
	}
}

其运算结果示意图如下:
在这里插入图片描述

31.过滤组合字符串

题目描述
每个数字关联多个字母,关联关系如下:

  • 0 关联 “a”,”b”,”c”
  • 1 关联 “d”,”e”,”f”
  • 2 关联 “g”,”h”,”i”
  • 3 关联 “j”,”k”,”l”
  • 4 关联 “m”,”n”,”o”
  • 5 关联 “p”,”q”,”r”
  • 6 关联 “s”,”t”
  • 7 关联 “u”,”v”
  • 8 关联 “w”,”x”
  • 9 关联 “y”,”z”
1. 输入一串数字后,通过数字和字母的对应关系可以得到多个字母字符串(要求按照数字的顺序组合字母字符串);
2. 屏蔽字符串:屏蔽字符串中的所有字母不能同时在输出的字符串出现,如屏蔽字符串是abc,则要求字符串中不能同时出现a,b,c,但是允许同时出现a,b或a,c或b,c等;
3. 给定一个数字字符串和一个屏蔽字符串,输出所有可能的字符组合;
4. 例如输入数字字符串78和屏蔽字符串ux,输出结果为uw,

输入描述

  1. 第一行输入为一串数字字符串,数字字符串中的数字不允许重复,数字字符串的长度大于0,小于等于5;
  2. 第二行输入是屏蔽字符串,屏蔽字符串的长度一定小于数字字符串的长度,屏蔽字符串中字符不会重复;

输出描述

输出可能的字符串组合
注:字符串之间使用逗号隔开,最后一个字符串后携带逗号

用例

输入78
ux
输出uw,vw,vx,
说明
输入

78
x

输出uw,vw,
说明

源码和解析
解析:

  1. 先生成对应的map 键是数字 值是对应的字符列表
java  {0=[a, b, c], 1=[d, e, f], 2=[g, h, i], 3=[j, k, l], 4=[m, n, o], 5=[p, q, r], 6=[s, t], 7=[u, v], 8=[w, x], 9=[y, z]}

在这里插入图片描述
2. 根据输入的内容组合字符(注意这里不能简单的以为输入只会是2个数字,可能是1-5个数字,所以组合的时候得考虑使用数位DP算法)
数位DP算法不懂的,可以参考【算法】使用数位算法生成0至某个数之间的整数
例如输入的数字是0 1 2 时 组合如下
在这里插入图片描述

  1. 过滤掉屏幕字符串
  2. 输出剩余字符

示例代码:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class T31 {
	static Map<Integer, List<Character>> map=new HashMap<Integer, List<Character>>(); //数字得到字符映射
	static String group[]=null; //记录dfs算法生成的数字组合
	static List<String> groupList=new ArrayList<>(); //最后生成的组合列表
	static StringBuilder sb=new StringBuilder();//用于循环产生一个字符串 主要针对于group数组
	public static void main(String[] args) {
		String cs[]={"abc","def","ghi","jkl","mno","pqr","st","uv","wx","yz"};
		String input1="789";
		String input2="ux";
		List<Integer> numList=new ArrayList<>();
		for(int i=0;i<input1.length();i++){
			numList.add(Integer.parseInt(input1.charAt(i)+""));
		}
		int count=0;
		for(int i=0;i<cs.length;i++){
			if(numList.contains(i)){
				List<Character> list=new ArrayList<Character>();
				for(int j=0;j<cs[i].length();j++){
					list.add(cs[i].charAt(j));
				}
				map.put(count++, list); //map中的索引要注意替换 方便dfs算法正常生效
			}
		}
//		System.out.println(map);
		group=new String[map.size()];
		dfs(0);
//		System.out.println(groupList);
		//过滤掉包含字符的
		for(String s:groupList){
			if(s.indexOf(input2)==-1){
				System.out.print(s+",");
			}
		}
	}
	public static void dfs(int p){
		if(p==map.size()){
			for(int i=0;i<group.length;i++){
				sb.append(group[i]);
			}
			groupList.add(sb.toString());
			sb.setLength(0);
			return;
		}
		for(int i=0;i<map.get(p).size();i++){
			group[p]=map.get(p).get(i)+"";
			dfs(p+1);
		}
	}
}

代码运行截图:
在这里插入图片描述

32.真正的密码

题目描述

  1. 一行中输入一个字符串数组,如果其中一个字符串的所有以索引0开头的子串在数组中都有,那么这个字符串就是潜在密码
  2. 在所有潜在密码中最长的是真正的密码,如果有多个长度相同的真正的密码,那么取字典序最大的为唯一的真正的密码,求唯一的真正的密码。

输入描述

一个字符串 字符之间用空格隔开

输出描述

真正的那个密码字符串

用例

输入h he hel hell hello o ok n ni nin ninj ninja
输出ninja
说明

按要求,hello、ok、ninja都是潜在密码。

检查长度,hello、ninja是真正的密码。

检查字典序,ninja是唯一真正密码。

输入a b c d f
输出f
说明

按要求,a b c d f 都是潜在密码。

检查长度,a b c d f 是真正的密码。

检查字典序,f是唯一真正密码。

源码和解析
解析:

  1. 字符串按长度降序排序,若长度相同,则按字母顺序降序排序。
  2. 遍历字符串集合,从前往后匹配,找到第一个满足条件的即可

示例代码:

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class T32 {
	public static void main(String[] args) {
		String input="h he hel hell hello o ok n ni nin ninj ninja";
		List<String> wordList=new ArrayList<String>();
		String[] wordArr=input.split(" ");
		for(String w:wordArr){
			wordList.add(w);
		}
		wordList.sort(new Comparator<String>() {
			@Override
			public int compare(String o1, String o2) {
				//按长度降序排序
				if(o1.length()>o2.length())return -1;
				if(o1.length()<o2.length())return 1;
				//按字符排序
				for(int i=0;i<o1.length();i++){
					if(o1.charAt(i)>o2.charAt(i)) return -1;
					if(o1.charAt(i)<o2.charAt(i)) return 1;
				}
				return 0;
			}
		});
		System.out.println(wordList);
		for(String word:wordList){
			//双指针校验,是否全包含
			int left=0;
			int right=1;
			while(wordList.contains(word.substring(left, right))){
				if(right==word.length()){
					System.out.println(word);
					return;
				}
				right++;
			}
		}
	}
}

代码运行示例:
在这里插入图片描述
在这里插入图片描述

33.最小调整顺序次数、特异性双端队列

题目描述

有一个特异性的双端队列,该队列可以从头部或尾部添加数据,但是只能从头部移出数据。
小A依次执行2n个指令往队列中添加数据和移出数据。其中n个指令是添加数据(可能从头部添加、也可能从尾部添加),依次添加1到n;n个指令是移出数据。
现在要求移除数据的顺序为1到n。
为了满足最后输出的要求,小A可以在任何时候调整队列中数据的顺序。
请问 小A 最少需要调整几次才能够满足移除数据的顺序正好是1到n;

输入描述

第一行一个数据n,表示数据的范围。
接下来的2n行,其中有n行为添加数据,指令为:

  • "head add x" 表示从头部添加数据 x,
  • "tail add x" 表示从尾部添加数据x,
另外 n 行为移出数据指令,指令为:"remove" 的形式,表示移出1个数据;

1 ≤ n ≤ 3 * 10^5。
所有的数据均合法。

输出描述

一个整数,表示 小A 要调整的最小次数。

源码和解析
解析:

其实这个题只要理解了就其实还蛮简单的。小编当时做这个提题目时候前面一脸懵B,压根不知道在说个啥。就只知道要调整次数,但是不确定这个调整次数是啥。
head add 1
[1]
tail add 2
[1, 2]
remove
[2]
head add 3
[3, 2]
tail add 4
[3, 2, 4]
head add 5
[5, 3, 2, 4]
remove
排序了
[3, 4, 5]
remove
[4, 5]
remove
[5]
remove
[]
这个是用例中的例子 输出过程。 认真去体会每个指令执行后的结果
若输入变成
5
head add 2
tail add 1
remove
head add 3
tail add 4
head add 5
remove
remove
remove
remove
排序了1次
那么其输出过程为:
head add 2
[2]
tail add 1
[2, 1]
remove
排序了
[2]
head add 3
[3, 2]
tail add 4
[3, 2, 4]
head add 5
[5, 3, 2, 4]
remove
排序了
[3, 4, 5]
remove
[4, 5]
remove
[5]
remove
[]
排序了2次
====》可以推出 ,当集合中首位值不是集合中最小值是,需要进行调整。而一次调整包含了多次交换位置过程。可以简单的理解为1次排序过程。

示例代码:

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Scanner;

public class T33 {
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		String input = scanner.nextLine();
		int num = Integer.parseInt(input); // 数据范围 并非是数的个数
		List<Integer> numList = new ArrayList<Integer>();
		int count = 0;// 调整次数
		int removeNum = 1;// 下一次需要移走哪一个
		for (int i = 0; i < num * 2; i++) {
			input = scanner.nextLine();
			String strArr[] = input.split(" ");
			System.out.println(input);
			if (strArr[0].equals("remove")) {
				// remove 从头部移出数据
				if (numList.get(0) == removeNum) {
					numList.remove(0);
					removeNum++;
				} else {
					count++;
					// 调整次序 从小到大排序一下
					numList.sort(new Comparator<Integer>() {
						@Override
						public int compare(Integer o1, Integer o2) {
							if (o1 > o2)
								return 1;
							if (o1 < o2)
								return -1;
							return 0;
						}
					});
					System.out.println("排序了");
					numList.remove(0);
					removeNum++;
				}
			}
			// 头部添加
			if (strArr[0].equals("head")) {
				numList.add(0, Integer.parseInt(strArr[2]));
				// continue;
			} else if (strArr[0].equals("tail")) {
				// 尾部添加
				numList.add(Integer.parseInt(strArr[2]));
			}
			System.out.println(numList);
		}
		System.out.println(count);
	}
}

34. 羊、狼、农夫过河

题目描述

羊、狼、农夫都在岸边,当羊的数量小于狼的数量时,狼会攻击羊,农夫则会损失羊。农夫有一艘容量固定的船,能够承载固定数量的动物。
要求求出不损失羊情况下将全部羊和狼运到对岸需要的最小次数。只计算农夫去对岸的次数,回程时农夫不会运送羊和狼。
备注:农夫在或农夫离开后羊的数量大于狼的数量时狼不会攻击羊。

输入描述

第一行输入为M,N,X, 分别代表羊的数量,狼的数量,小船的容量。

输出描述

输出不损失羊情况下将全部羊和狼运到对岸需要的最小次数(若无法满足条件则输出0)。

输入5 3 3
输出3
说明

第一次运2只狼

第二次运3只羊

第三次运2只羊和1只狼

输入5 4 1
输出0
说明如果找不到不损失羊的运送方案,输出0

源码和解析
解析:

  1. 确保两岸的羊无论何时都不能出现羊的数量小于等于狼的数量,即羊的数量-狼的数量>=1
  2. 可以按分组的形式每次运输一个动物,从而得到一个可能存在的单个顺序记录 例如20只羊 8只狼 船容量为5
    [g, g, g, g, g, g, g, g, g, g, g, w, g, w, g, w, g, w, g, w, g, w, g, w, g, w, g, g]
  3. 将2中得到的顺序按船容量进行合并
    3.1 确保每次运输尽可能多的带动物
    3.2 若带5个过去,可能出现本岸或对岸狼吃羊的情况,那么就得少带
    3.3 使用双指针进行合并

示例代码:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class T34 {
	public static void main(String[] args) {
		String input = "20 8 5";
		int ghostNumber = Integer.parseInt(input.split(" ")[0]);
		int wolfNumber = Integer.parseInt(input.split(" ")[1]);
		int shipCapacity = Integer.parseInt(input.split(" ")[2]);
		List<String> shifLog = new ArrayList<String>();// 转移记录 每次转移一个
		while (ghostNumber + wolfNumber > 0) {
			// 羊或者狼还没运输完 运输时优先确保对岸的羊不比狼少 而本岸的则确保羊比狼多一个即可 由于是单次运输 所以对岸的羊可能会和狼一样多
			if (ghostNumber - wolfNumber > 1) {
				// 运输一个羊
				ghostNumber--;
				shifLog.add("g");
				continue;
			}
			if (wolfNumber > 0) {
				// 否则运输狼
				wolfNumber--;
				shifLog.add("w");
			} else {
				// 只剩一个羊
				ghostNumber--;
				shifLog.add("g");
			}
		}
		System.out.println(shifLog);
		// 来检测单个运输过程 是否可以合并为一次的 求出最小运输次数
		Map<String, Integer> map = new HashMap<String, Integer>();
		map.put("g", 0);
		map.put("w", 0);
		int count = 0; // 运输了几次
		int left = 0;
		int right = shipCapacity;// 第一次运算
		wolfNumber = Integer.parseInt(input.split(" ")[1]);
		shipCapacity = Integer.parseInt(input.split(" ")[2]);
		if (left == right - 1 && ghostNumber - wolfNumber < wolfNumber) {
			// 船容量是1 且羊的数量不是狼的2倍 那么这样是不可能移动成功的
			System.out.println(0);
			System.exit(0);
		}
		while (left < shifLog.size()) {
			int wN = 0;
			int gN = 0;
			int onceCount = 0;
			for (int i = 0; i < right - left; i++) {
				if (left + i >= shifLog.size()) {
					break;
				}
				onceCount++;
				if (shifLog.get(left + i).equals("w")) {
					wN++;
				} else {
					gN++;
				}
			}
			if (map.get("g") + gN > map.get("w") + wN) {
				count++;
				map.put("g", map.get("g") + gN);
				map.put("w", map.get("w") + wN);
				left += onceCount;
				right += shipCapacity;
				// 这是一次有效的运输 指针右移到下一次
				System.out.println("第" + count + "次有效运输,运输的羊和狼为:" + gN + ":"
						+ wN);
			} else {
				right--;
			}
		}
		System.out.println(count);
	}
}

上述代码的执行过程如下图:

在这里插入图片描述

35. 打印机队列

题目描述

有5台打印机打印文件,每台打印机有自己的待打印队列。
因为打印的文件内容有轻重缓急之分,所以队列中的文件有1~10不同的代先级,其中
数字越大优先级越高
打印机会从自己的待打印队列中选择优先级最高的文件来打印。
如果存在两个优先级一样的文件,则选择最早进入队列的那个文件。
现在请你来模拟这5台打印机的打印过程。

输入描述

每个输入包含1个测试用例,

每个测试用例第一行给出发生事件的数量N(0 < N < 1000)。

接下来有 N 行,分别表示发生的事件。共有如下两种事件:

  1. “IN P NUM”,表示有一个拥有优先级 NUM 的文件放到了打印机 P 的待打印队列中。(0< P <= 5, 0 < NUM <= 10);
  2. “OUT P”,表示打印机 P 进行了一次文件打印,同时该文件从待打印队列中取出。(0 < P <= 5)。

输出描述

  • 对于每个测试用例,每次”OUT P”事件,请在一行中输出文件的编号
  • 如果此时没有文件可以打印,请输出”NULL“。
  • 文件的编号定义为”IN P NUM”事件发生第 x 次,此处待打印文件的编号为x。编号从1开始。

用例

输入7
IN 1 1
IN 1 2
IN 1 3
IN 2 1
OUT 1
OUT 2
OUT 2
输出3
4
NULL
说明
输入5
IN 1 1
IN 1 3
IN 1 1
IN 1 3
OUT 1
输出2
说明

源码和解析
解析:

1.可以使用有序列表对该题进行求解,但是这种每次添加任务队列都需要解决排序的问题,可以考虑在打印任务时再看是否有序,若无序,则可以排队,若有序,则直接输出首个任务,且直接移出这个任务。
2.另外一种方式就是使用大根堆去解决这个问题

示例代码方式一:

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

public class T35 {
	static class Task {
		int num;
		int priority;
		int taskId;

		public Task(int num, int priority, boolean isFinished,
				int taskId) {
			super();

			this.num = num;
			this.priority = priority;
			this.taskId = taskId;
		}

		@Override
		public String toString() {
			return "Task [num=" + num + ", priority=" + priority
					+ ", taskId=" + taskId + "]\n";
		}
	}

	// 按输入的设备编号进行分堆 存放在不同的map中 键是打印机编号 值为该打印机的任务信息
	static Map<Integer, List<Task>> taskMap = new HashMap<Integer, List<Task>>();
	static List<Integer> pList = new ArrayList<>();// 记录设备的id集
													// 后面就不用keyset去遍历map了
	static boolean isOrdered = false;

	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int num = scanner.nextInt();
		int id = 0;
		for (int i = 0; i < num; i++) {
			id++;
			String op = scanner.next();
			int p = scanner.nextInt();
			int priority = 0;
			if (op.equals("IN")) {
				priority = scanner.nextInt();
			}
			if (!taskMap.containsKey(p)) {
				List<Task> tasks = new ArrayList<T35.Task>();
				taskMap.put(p, tasks);
				pList.add(p);
			}
			Task task = new Task(p, priority, false, id);
			if (op.equals("IN")) {
				taskMap.get(p).add(task);
				isOrdered = false;
			} else {
				if (!isOrdered)
					sort();
				List<Task> tasksItem = taskMap.get(p);
				Task t = tasksItem.size() == 0 ? null : tasksItem.get(0);
				if (t != null) {
					tasksItem.remove(0);
				}
				System.out.println((t == null ? "NULL" : t.taskId));
			}
		}
	}

	// 排序
	public static void sort() {
		isOrdered = true;
		for (int p : pList) {
			taskMap.get(p).sort(new Comparator<Task>() {
				@Override
				public int compare(Task o1, Task o2) {
					// 优先级降序 高的排前面 方便取值
					if (o1.priority > o2.priority) {
						return -1;
					} else if (o1.priority < o2.priority) {
						return 1;
					}
					// 优先级一样 任务顺序排序
					if (o1.taskId < o2.taskId) {
						return -1;
					} else if (o1.taskId > o2.taskId) {
						return 1;
					}
					return 0;
				}
			});
		}
	}
}

执行示意图如下:
在这里插入图片描述
在这里插入图片描述
大根堆解法
解析

例如输入
10
IN 1 2
IN 1 1
IN 1 4
IN 1 2
IN 1 4
IN 1 3
IN 1 5
OUT 1
OUT 2
OUT 2
形成的节点信息如下
在这里插入图片描述
那么在移出时就
OUT 1 针对 1 5 这个节点
OUT 1 查找到 1 5这个节点 无比他大 比他小的 那么只能是1 5 节点的父节点 1 4
OUT 1 查找到 1 4 节点是已完成的 那么可能就是1 3 节点 若1 3节点已完成 则可能是1 2 节点 若 1 2 节点已完成 则可能是其左节点1 1 若 1 1节点已完成 则看其左节点 若 1 1 节点未完成 则看其左节点
注意:这里并非是完全排序好的大根堆,而是依据节点的添加顺序形成的堆

源码示例 大根堆

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class T35 {
	static class Task {
		int devId; // 设备编号
		int priority;
		int taskId;
		boolean isFinished;
		Task leftTask; // 左节点
		Task rightTask; // 右节点
		Task parenTask;// 父节点

		@Override
		public String toString() {
			return "Task [devId=" + devId + ", priority=" + priority
					+ ", taskId=" + taskId + "]\n";
		}
	}

	// 按输入的设备编号进行分堆 存放在不同的map中 键是打印机编号 值为该打印机的任务信息
	static Map<Integer, Task> taskMap = new HashMap<Integer, Task>();
	static Task objTask = null;

	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int num = scanner.nextInt();
		int id = 0;
		for (int i = 0; i < num; i++) {
			id++;
			String op = scanner.next();
			int devId = scanner.nextInt();
			int priority = 0;
			if (op.equals("IN")) {
				priority = scanner.nextInt();
			}
			Task task = new Task();
			task.devId = devId;
			task.priority = priority;
			task.taskId = id;
			if (op.equals("IN")) {
				if (!taskMap.containsKey(devId)) {
					taskMap.put(devId, task);
					System.out.println("挂载跟节点:" + task.taskId);
				} else {
					// 挂载节点
					handle(devId, task);
				}
			} else {
				// 出节点
				objTask = null;
				find(taskMap.get(devId));
				if (objTask == null)
					System.out.println("NULL");
			}
		}
	}

	// 找到目标设备要出的那个
	public static void find(Task task) {
		if (task.isFinished == false) {
			// 自己未完成 那么可能到右侧
			if (task.rightTask != null && task.rightTask.isFinished == false) {
				find(task.rightTask);// 找右侧子节点 这种是不可能找左侧子节点的
			} else {
				// 到自己
				task.isFinished = true;
				objTask = task;
				System.out.println(task.taskId);// 输出任务id
			}
		} else {
			// 当前已完成 那么只有可能是其左侧节点
			if (task.leftTask != null)
				find(task.leftTask);
		}
	}

	public static void handle(int devId, Task task) {
		Task rootTask = taskMap.get(devId);
		mount(rootTask, task);
	}

	// 遍历节点 进行挂载
	public static void mount(Task task, Task objTask) {
		if (task.priority < objTask.priority) {
			// 挂载右侧
			if (task.rightTask != null) {
				mount(task.rightTask, objTask);
			} else {
				task.rightTask = objTask;
				System.out.println("节点" + objTask.taskId + "挂载在节点"
						+ task.taskId + "右侧");
			}
		} else {
			// 挂载左侧
			if (task.leftTask != null) {
				mount(task.leftTask, objTask);
			} else {
				task.leftTask = objTask;
				System.out.println("节点" + objTask.taskId + "挂载节点" + task.taskId
						+ "左侧");
			}
		}
	}
}

大根堆代码运行示意图:
在这里插入图片描述

36.模拟商场优惠打折

题目描述

模拟商场优惠打折,有三种优惠券可以用,满减券、打折券和无门槛券。
满减券:满100减10,满200减20,满300减30,满400减40,以此类推不限制使用;
打折券:固定折扣92折,且打折之后向下取整,每次购物只能用1次;
无门槛券:一张券减5元,没有使用限制。
每个人结账使用优惠券时有以下限制:

  1. 每人每次只能用两种优惠券,并且同一种优惠券必须一次用完,不能跟别的穿插使用(比如用一张满减,再用一张打折,再用一张满减,这种顺序不行)。
  2. 求不同使用顺序下每个人用完券之后得到的最低价格和对应使用优惠券的总数;如果两种顺序得到的价格一样低,就取使用优惠券数量较少的那个。

输入描述

第一行三个数字m,n,k,分别表示每个人可以使用的满减券、打折券和无门槛券的数量;
第二行一个数字x, 表示有几个人购物;
后面x行数字,依次表示是这几个人打折之前的商品总价。

输出描述

输出每个人使用券之后的最低价格和对应使用优惠券的数量

用例

输入3 2 5
3
100
200
400
输出65 6
135 8
275 8
说明

输入:

第一行三个数字m,n,k,分别表示每个人可以使用的满减券、打折券和无门槛券的数量。

输出:

第一个人使用 1 张满减券和5张无门槛券价格最低。(100-10=90, 90-5*5=65)

第二个人使用 3 张满减券和5张无门槛券价格最低。(200-20-10-10=160, 160 – 5*5 = 135)

第二个人使用 3 张满减券和5张无门槛券价格最低。(400-40-30-30=300, 300 – 5*5=275)

源码和解析
解析:

这个题其是暴力求解即可,因为只有三种券且每次只能使用2种。因此按使用顺序不同可以得到6种计算方式。分别对用户输入的价格进行每种方式求解后取最优值即可。
思考?如果券种类多一点,每次使用的数量多一点。那么组合方式就很难,暴力破解就无法了。

示例代码:

import java.util.Scanner;

public class T36 {
	static int mjq;
	static int dzq;
	static int wmkq;

	static class Group {
		int price;// 打折后价格
		int num;// 优惠券使用熟练
	}

	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		String input = scanner.nextLine();
		mjq = Integer.parseInt(input.split(" ")[0]);// 满减券数量 100减10 200减20
													// 300减30
		dzq = Integer.parseInt(input.split(" ")[1]); // 打折券数量 92折 向下取整
		wmkq = Integer.parseInt(input.split(" ")[2]); // 无门槛券 减5元 价格>=0
		int nums = Integer.parseInt(scanner.nextLine());
		for (int i = 0; i < nums; i++) {
			int price = Integer.parseInt(scanner.nextLine());
			Group g = getMinPrice(price);
			System.out.println(g.price + " " + g.num);
		}

	}

	public static Group getMinPrice(int price) {
		String ways[] = { "MD", "MY", "DM", "DY", "YM", "YD" };
		Group group = new Group();
		group.price = price;
		group.num = dzq + mjq + wmkq;
		for (String way : ways) {
			Group g = count(way, price);
			if (g.price < group.price) {
				group.price = g.price;
				group.num = g.num;
			} else if (g.price == group.price) {
				if (g.num < group.num) {
					group.num = g.num;
				}
			}
		}
		return group;
	}

	public static Group count(String way, int price) {
		// Map<Integer, Integer> map = new HashMap<Integer, Integer>(); // 价格
		// 和数量
		Group group = new Group();
		int mjCount = 0;// 满减券使用数量
		int wmqCount = 0;// 优惠券使用数量
		switch (way) {
		case "MD":
			// 先满减 再打折
			while (mjCount < mjq && price >= 100) {
				int num = (int) (price / 100) * 10;
				// System.out.println(price+"_"+num);
				price -= num;
				mjCount++;
			}
			// System.out.println("满减后的价格" + price);
			price = (int) (price * 0.92);
			// System.out.println("打折后的价格" + price);
			group.price = price;
			group.num = mjCount + 1;
			break;
		case "MY":
			// 先满减 无门槛券
			while (mjCount < mjq && price >= 100) {
				int num = (int) (price / 100) * 10;
				// System.out.println(price+"_"+num);
				price -= num;
				mjCount++;
			}
			// System.out.println("满减后的价格" + price);
			while (price > 0 && wmqCount < wmkq) {
				price -= 5;
				price = price < 0 ? 0 : price;
				wmqCount++;
			}
			group.price = price;
			group.num = mjCount + wmqCount;
			// System.out.println("无门槛后的价格" + price);
			break;
		case "DM":
			// 先打折再满减
			price = (int) (price * 0.92);
			// 先满减 再打折
			// System.out.println("打折后的价格" + price);
			while (mjCount < mjq && price >= 100) {
				int num = (int) (price / 100) * 10;
				// System.out.println(price+"_"+num);
				price -= num;
				mjCount++;
			}
			// System.out.println("满减后的价格" + price);
			group.price = price;
			group.num = mjCount + 1;
			break;
		case "DY":
			// 先打折 再使用无门槛券
			price = (int) (price * 0.92);
			// System.out.println("打折后的价格" + price);
			while (price > 0 && wmqCount < wmkq) {
				price -= 5;
				price = price < 0 ? 0 : price;
				wmqCount++;
			}
			// System.out.println("无门槛券后的价格" + price);
			group.price = price;
			group.num = wmqCount + 1;
			break;
		case "YM":
			// 先使用无门槛券 再满减
			while (price > 0 && wmqCount < wmkq) {
				price -= 5;
				wmqCount++;
				price = price < 0 ? 0 : price;
			}
			// System.out.println("无门槛券后的价格" + price);
			while (mjCount < mjq && price >= 100) {
				int num = (int) (price / 100) * 10;
				price -= num;
				mjCount++;
			}
			// System.out.println("满减后的价格" + price);
			group.price = price;
			group.num = mjCount + wmqCount;
			break;
		case "YD":
			// 先使用 无门槛券 再使用打折
			while (price > 0 && wmqCount < wmkq) {
				price -= 5;
				wmqCount++;
				price = price < 0 ? 0 : price;
			}
			// System.out.println("无门槛券后的价格" + price);
			price = (int) (price * 0.92);
			// System.out.println("打折后的价格" + price);
			group.price = price;
			group.num = wmqCount + 1;
		default:
			break;
		}
		return group;
	}
}

上述代码及运行示意图:
在这里插入图片描述

37.二元组个数

题目描述

给定两个数组a,b,若a[i] == b[j] 则称 [i, j] 为一个**二元组**,求在给定的两个数组中,二元组的个数。

输入描述

第一行输入 m
第二行输入m个数,表示第一个数组

第三行输入 n
第四行输入n个数,表示第二个数组

输出描述

二元组个数。

用例

输入4
1 2 3 4
1
1
输出1
说明二元组个数为 1个
输入6
1 1 2 2 4 5
3
2 2 4
输出5
说明二元组个数为 5 个。

源码和解析
解析:

其实这个题目重在理解,如果理解了题意了你就好做了。
小编在做这个题时,就进入了误区
当输入为
6
1 1 2 2 4 5
3
2 2 4
输出5时 我天真的以为组合是这样的
其中a[]={1,1,2,2,4,5} b={2,2,4}
我以为的组合 a数组出一个2 b数组出一个2 得到 [2,0] [3,1] a数组出一个4 b数组出1个4 得到[4,2] 然后a出两个连续2 b出两个连续2 得到[ [2,3],[0,1] ] a出224 b出224 得到[[2,3,4],[0,1,2]]数了一下5个 和答案一致


然而真实的组合是这样的
[2,0] [2,1] [3,0] [3,1] [4,2] 没错就是这么简单
注意,题目只要求每个数组出一个数字即可。不要把题想得那么复杂,这个题我还特意咨询了大佬 伏城之外 忽然觉得自己好憨


既然分析懂了题目,就稍微好做些了。
1.因为是要a数组中的某一个a[i]等于b数组中的某一个b[j] 所以a中的一个数a[i]可以匹配B中的值相同的那个数多个就如a[2]就能匹配b[0]和b[1]
2.所以只要统计出a在b中出现的那些数及其个数 以及这些数在a中出现的个数即可运用简单方法求解出来,就不需要用双层for循环或双指针求解。例如a中2出现2次 所以第一个2在b数组中匹配了2个,自然而然第二个2也是匹配2个 最后就是分别求和相加
3.在a中 有2和4 出现在了b中,因此在b中2和4出现的次数为
{2:2,4:1}
在a中,2和4自己出现的次数为
{2:2,4:1}
所以综合求解为2*2+1*1次

示例代码:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;

public class T37 {
	public static void main(String[] args) {
		Scanner scanner=new Scanner(System.in);
		int num=Integer.parseInt(scanner.nextLine());
		String input1[]=scanner.nextLine().split(" ");
		List<Integer> num1List=new ArrayList<Integer>();
		for(int i=0;i<num;i++){
			num1List.add(Integer.parseInt(input1[i]));
		}
		
		int num2=Integer.parseInt(scanner.nextLine());
		String input2[]=scanner.nextLine().split(" ");
		List<Integer> num2List=new ArrayList<Integer>();
		for(int i=0;i<num2;i++){
			num2List.add(Integer.parseInt(input2[i]));
		}
		Map<Integer, Integer> map1=new HashMap<Integer, Integer>();
		Map<Integer, Integer> map2=new HashMap<Integer, Integer>();
		for(int n:num1List){
			//对方的数 在自己的中出现过几次map1
			if(num2List.contains(n)){
				if(map1.containsKey(n)){
					map1.put(n, map1.get(n)+1);
				}else{
					map1.put(n, 1);
					int count=0;//自己在对方的列表中出现的次数
					for(int j:num2List){
						if(j==n){
							count++;
						}
					}
					map2.put(n, count);
				}
			}
		}
		System.out.println(map1);
		System.out.println(map2);
		Set<Integer> keySet = map1.keySet();
		Iterator<Integer> it=keySet.iterator();
		int res=0;
		while(it.hasNext()){
			int key=it.next();
			res+=map1.get(key)*map2.get(key);
		}
		System.out.println("结果为:"+res);
	}
}

上述代码运行示意图:
在这里插入图片描述
在这里插入图片描述

38.考勤信息

题目描述

公司用一个字符串来表示员工的出勤信息

  • absent:缺勤
  • late:迟到
  • leaveearly:早退
  • present:正常上班

现需根据员工出勤信息,判断本次是否能获得出勤奖,能获得出勤奖的条件如下:

  • 缺勤不超过一次;
  • 没有连续的迟到/早退;
  • 任意连续7次考勤,缺勤/迟到/早退不超过3次。

如:

2
present
present absent present present leaveearly present absent

输入描述

用户的考勤数据字符串

  • 记录条数 >= 1;
  • 输入字符串长度 < 10000;
  • 不存在非法输入;

输出描述

根据考勤数据字符串,如果能得到考勤奖,输出”true”;否则输出”false”,
对于输入示例的结果应为:

true false

用例

输入2
present
present present
输出true
true
说明
输入2
present
present absent present present leaveearly present absent
输出true
false
说明

源码和解析
解析:

这个题做好逻辑判断就行了

示例代码:

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

//考勤问题
public class T38 {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int num = Integer.parseInt(sc.nextLine());// 记录条数
		for (int i = 0; i < num; i++) {
			String input = sc.nextLine();
			String kqArr[] = input.split(" ");
			// 一、缺勤不超过1次
			int queqinCoun = 0; // 不超过1次
			for (String kq : kqArr) {
				if (kq.equals("absent")) {
					queqinCoun++;
					if (queqinCoun > 1) {
						break;
					}
				}
			}
			if (queqinCoun > 1) {
				System.out.println("false");
				break;
			}
			// 二、没有连续的迟到/早退 迟到和早退 也是连续的 相当于一天干了俩个事情 也算
			List<String> cList = new ArrayList<String>();
			cList.add("late");
			cList.add("leaveearly");
			boolean isLateOrLeaveEarly = false;
			for (int j = 1; j < kqArr.length; j++) {
				if (cList.contains(kqArr[j]) && cList.contains(kqArr[j - 1])) {
					// 出现连续的迟到/早退
					isLateOrLeaveEarly = true;
					break;
				}
			}
			if (isLateOrLeaveEarly) {
				System.out.println("false");
				break;
			}
			// 三、任意连续的7次考勤 ,缺勤/迟到/早退不超过3次 即 缺勤次数+迟到次数+早退次数<=3
			// 双指针做法
			int start = 0;
			int end = kqArr.length >= 7 ? 6 : kqArr.length;
			int count = 0;// 违规次数
			cList.add("absent");
			// 判断连个指针之间的初始值内是否已经满足false情况
			for (int k = start; k < end; k++) {
				if (cList.contains(kqArr[k])) {
					count++;
				}
			}
			if (count > 3) {
				System.out.println("false");
				break;
			}
			while (end < kqArr.length - 1) {
				// 指针右侧
				end++;
				if (cList.contains(kqArr[end])) {
					count++;// 加进来的是满足的
				}
				if (cList.contains(kqArr[start])) {
					count--;// 移出去的也是满足的
				}
				start++; // 左指针也需要右移1位 保持两个指针之间能取得连续7位
				if (count > 3) {
					break;
				}
			}
			if (count > 3) {
				System.out.println("false");
				break;
			}
			System.out.println("true");
		}
	}
}

运行示意图:
在这里插入图片描述

40.计算最大乘积

题目描述

给定一个元素类型为小写字符串的数组,请计算两个没有相同字符的元素长度乘积的最大值,

如果没有符合条件的两个元素,返回0。

输入描述

输入为一个半角逗号分隔的小写字符串的数组,2 <= 数组长度<=100,0 < 字符串长度<= 50。

输出描述

两个没有相同字符的元素 长度乘积的最大值

用例

输入iwdvpbn,hk,iuop,iikd,kadgpf
输出14
说明

数组中有5个元素。

iwdvpbn与hk无相同的字符,满足条件,iwdvpbn的长度为7,hk的长度为2,乘积为14(7*2)。

iwdvpbn与iuop、iikd、kadgpf均有相同的字符,不满足条件。

iuop与iikd、kadgpf均有相同的字符,不满足条件。

iikd与kadgpf有相同的字符,不满足条件。

因此,输出为14。

源码和解析
解析:

这个题 也是属于偏简单题,只需要逻辑判断处理好。知道怎么求两个字符串的交集即可。如果实在不会做,看懂我提供的代码即可明白。

示例代码:

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

//最大乘积
public class T39 {
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		String input = scanner.nextLine();
		String wordArr[] = input.split(",");
		List<List<Character>> keySetList = new ArrayList<List<Character>>();
		for (String word : wordArr) {
			List<Character> cList = new ArrayList<Character>();
			for (char c : word.toCharArray()) {
				cList.add(c);
			}
			keySetList.add(cList);
		}
		System.out.println(keySetList);
		int start = 0;
		int end = 1;
		int max = 0;
		while (end < keySetList.size()) {
			List<Character> startList = keySetList.get(start);
			List<Character> endtList = keySetList.get(end);
			boolean flag = true;
			for (Character c : startList) {
				if (endtList.contains(c)) {
					flag = false;
					break;
				}
			}
			if (flag && max < startList.size() * endtList.size()) {
				max = startList.size() * endtList.size();
			}
			if (end == keySetList.size() - 1) {
				// 最后一位
				start++;
				end = start + 1;
			} else {
				end++;
			}
		}
		System.out.println(max);
	}
}

运行示例1:
在这里插入图片描述
运行示例2
在这里插入图片描述

数列描述

题目描述

有一个数列a[N] (N=60),从a[0]开始,每一项都是一个数字。数列中a[n+1]都是a[n]的描述。其中a[0]=1。规则如下:

  • a[0]:1
  • a[1]:11(含义:其前一项a[0]=1是1个1,即“11”。表示a[0]从左到右,连续出现了1次“1”)
  • a[2]:21(含义:其前一项a[1]=11,从左到右:是由两个1组成,即“21”。表示a[1]从左到右,连续出现了两次“1”)
  • a[3]:1211(含义:其前一项a[2]=21,从左到右:是由一个2和一个1组成,即“1211”。表示a[2]从左到右,连续出现了1次“2”,然后又连续出现了1次“1”)
  • a[4]:111221(含义:其前一项a[3]=1211,从左到右:是由一个1、一个2、两个1组成,即“111221”。表示a[3]从左到右,连续出现了1次“1”,连续出现了1次“2”,连续出现了两次“1”)

请输出这个数列的第n项结果(a[n],0≤n≤59)。

输入描述

数列的第n项(0≤n≤59) 例如
4

输出描述

数列的内容
111221

源码和解析
解析:

这个题重在理解 即求连续字符的个数与字符组成的新数字
如第一项为1 (1个1) 那么下一项是11 (2个1),下一项是21(1个2 和1个1)=>1211
(1个1,1个2,2个1)=>111221(3个1,2个2,1个1)=?312211…
这个题其实也还是偏逻辑性强一些,当然其也涉及到DP算法(动态规划算法),算法不知道无所谓,但是要有这种处理问题的思维。
注意:

  1. 如果这个题用String或者int来存储值,那么结果是打印不出来的。
    因为最大字符长度(65535) ,可以通过Integer.MAX_VALUE得到。
  2. 我这里使用的List来进行存储的。因为其长度可以自动扩展
  3. 如果使用的是Eclipse软件,那么其控制台打印字符长度也不能过长,否则就是空白。不利于测试。所以需要对Eclipse进行配置。
  4. 原本打算使用一个List内部装一个List,这样可以实现将前面所有产生的过程数据都存储起来,但是这样有点耗费内存。所以用一个List存储结果即可。

示例代码:

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class T40 {
	public static void main(String[] args) {
		List<Character> first = new ArrayList<Character>();
		first.add('1');
		Scanner sc = new Scanner(System.in);
		int num = sc.nextInt();
		for (int i = 1; i < num + 1; i++) {
			first=next(first);
		}
		// String 超出长度就不显示了 大概37就超出了
		// StringBuilder是能存的 但是toString后就空白了
		System.out.println(first.size());
		for(Character c:first){
			System.out.print(c);
		}
	}

	public static List<Character> next(List<Character> number) {
		List<Character> numList = new ArrayList<Character>();
		if (number.size() == 1) {
			numList.add('1');
			numList.add(number.get(0));
			return numList;
		}
		int start = 0;
		int end = 1;
		while (end < number.size()) {
			if (number.get(end) == number.get(start)) {
				if (end == number.size() - 1) {
					// 相等且到达最后一位
					String num = "" + (end - start + 1);
					for (int i = 0; i < num.length(); i++) {
						numList.add(num.charAt(i));
					}
					numList.add(number.get(start));
				}
				end++;
			} else {
				String num = "" + (end - start);
				for (int i = 0; i < num.length(); i++) {
					numList.add(num.charAt(i));
				}
				numList.add(number.get(start));
				start = end;
				end++;// 有可能直接到达末尾
				if (end == number.size()) {
					String num1 = "" + (end - start);
					for (int i = 0; i < num1.length(); i++) {
						numList.add(num1.charAt(i));
					}
					numList.add(number.get(start));
				}
			}
		}
		return numList;
	}
}

示例图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当输入的索引为59时,其产生的序列长度12680852,一千多万的长度。用字符串怎么存??
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值