华为OD算法题汇总

1、最远足迹问题

题目
某探险队对地下洞穴进行探险。
探险队成员在进行探险什务时,随身携带的记录器会不定期地记录自身的坐标,但在记录的间隙中也会记录其他数据。
探索工作结束后,探险队需要获取到某成员在探险过程中相对于探险队总部的最远的足迹位置。
仪器记录坐标时,坐标的数据格式为(x,y),如(1,2)、(100,200),其中 0<x<1000,0<y<1000。
同时存在非法坐标,如(011)、(1,01)、(0,100)属于非法坐标。
设定探险队总部的坐标为(0,0),某位置相对总部的距离为:xx+yy。
若两个座标的相对总部的距离相同,则第一次到达的坐标为最远的足迹。
若记录仪中的坐标都不合法,输出总部坐标(0,0)。
备注:
不需要考虑双层括号嵌套的情况,比如 sfsdfsd((1,2))。

# 测试用例1
# 输入
ferga13fdsf3(100,200)f2r3rfasf(300,400)
# 输出
(300,400)

# 测试用例2
# 输入
ferg(3,10)a13fdsf3(3,4)f2r3rfasf(5,10)
# 输出
(5,10)

解题思路

坐标距离总部的距离=xx + yy,所以关键就是从字符串中,提取合法的(x, y),比取出x、y的值,然后算出距离,最后排序输出最大的。
所以这是一道操作字符串的题目,我是通过逐个遍历,定位每一组’(‘、’)'的下标,放入list<list>中,然后遍历list,用String的split按照逗号分割出两个坐标,算距离。

java代码

private static void queryMethod(String params){
        int leftIndex = 0;
        int rightIndex = 0;
        List<List<Integer>> all = new ArrayList<>();
        char[] chars = params.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            if('('==(chars[i])){
                leftIndex = i+1;
            }else if(')'==(chars[i])){
                rightIndex = i;

                String[] split = params.substring(leftIndex, rightIndex).split(",");
                int left = Integer.parseInt(split[0]);
                int right = Integer.parseInt(split[1]);
                if(split[0].charAt(0)!='0' && split[1].charAt(0)!='0' && left>0 && left<1000 && right>0 && right<1000){
                    List<Integer> abList = new ArrayList<>();
                    abList.add(left);
                    abList.add(right);
                    all.add(abList);
                }
            }
        }

        if (all.size()>0){
            all.sort((a, b) -> {
                int ax = a.get(0) * a.get(0) + a.get(1) * a.get(1);
                int bx = b.get(0) * b.get(0) + b.get(1) * b.get(1);
                if(ax >= bx){
                    return -1;
                }else {
                    return 1;
                }
            });
            System.out.println("(" + all.get(0).get(0) + "," + all.get(0).get(1) + ")");
        }else {
            System.out.println("(0,0)");
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String params = sc.nextLine();
        if (params==null || params.length()<1){
            System.out.println("(0,0)");
        }
        queryMethod(params);
    }

2、射击比赛

题目
给定一个射击比赛成绩单,包含多个选手若干次射击的成绩分数,请对每个选手按其最高三个分数之和进行降序排名,输出降序排名后的选手 ID 序列。

条件如下:
1.一个选手可以有多个射击成绩的分数、且次序不固定
2.如果一个选手成绩小于三个则认为选手的所有成绩无效排名忽略该选手
3.如果选手的成绩之和相等,则成绩相等的选手按照其 ID 降序排列

输入
输入第一行:一个整数 N
表示该场比赛总共进行了N次射击,产生N个成绩分数,2<=N<=100;
输入第二行:一个长度为 N 的整数序列,表示参与本次射击的选手id,8 <= ID <= 99;
输入第三行是长度为N的整数序列,表示参与每次射击的选手对应的成绩0<= 成绩<= 100
输出
符合题设条件的降序排名后的选手 ID 序列

测试用例
# 输入
13
3,3,7,4,4,4,4,7,7,3,5,5,5
53,80,68,24,39,76,66,16,100,55,53,80,55
# 输出
5,3,7,4

解题思路

1)用数组接收入参,然后按(id,list<成绩>)放入map中;
2)遍历map,把成绩个数大于等于3的放入list<list>中,内层integer放id、总成绩;
3)对list按成绩、id排序,降序输出;

java代码

	public static void main(String[] args) {
        try {
            Scanner sc = new Scanner(System.in);
            int num = Integer.parseInt(sc.nextLine());
            String[] idStrs = sc.nextLine().split(",");
            String[] scoreStrs = sc.nextLine().split(",");

            Map<Integer, List<Integer>> idScoreMap = new HashMap<>();
            for (int i = 0; i < idStrs.length; i++) {
                int id = Integer.parseInt(idStrs[i]);
                int score = Integer.parseInt(scoreStrs[i]);
                if(score<0 || score>100){
                    continue;
                }
                if(idScoreMap.containsKey(id)){
                    idScoreMap.get(id).add(score);
                }else {
                    List<Integer> scoreList = new ArrayList<>();
                    scoreList.add(score);
                    idScoreMap.put(id, scoreList);
                }
            }

            List<List<Integer>> allList = new ArrayList<>();
            idScoreMap.forEach((id, scores) -> {
                if(scores.size() >= 3){
                    Collections.sort(scores);
                    int scoreSum = scores.get(scores.size()-1) + scores.get(scores.size()-2) + scores.get(scores.size()-3);
                    List<Integer> list = new ArrayList<>();
                    list.add(id);
                    list.add(scoreSum);
                    allList.add(list);
                }
            });

            allList.sort((a, b) -> {
                if (!a.get(1).equals(b.get(1)) && a.get(1).compareTo(b.get(1))>0) {
                    return -1;
                }else if(a.get(1).equals(b.get(1)) && a.get(0).compareTo(b.get(0))>0){
                    return -1;
                }
                return 1;
            });

            StringBuffer sb = new StringBuffer();
            for (List<Integer> list : allList) {
                sb.append(list.get(0)).append(",");
            }
            sb.deleteCharAt(sb.length()-1);
            System.out.println(sb.toString());
        } 
    } catch (Exception e) {
            e.printStackTrace();
            System.out.println("error");
	}

3、篮球争霸-最多MVP

题目
在篮球赛对抗赛中,某战队希望每个队员都能拿到 MVP,MVP 的条件是单场最高分获得者,且mvp可以并列;所以宇宙战队决定在比赛中尽可能让更多队员上场,并且让所有得分的选手得分都相同,然而比赛过程中的每1分钟的得分都只能由某一个人包揽。

输入
第一行为一个数字t,表示有得分的分钟数,1≤t≤50
第二行为t个数字,代表每一分钟的得分p,1≤t≤50
输出
输出有得分的队员都是 MVP 时,最少的 MVP 得分
示例一

# 输入
9
5 2 1 5 2 1 5 2 1

# 输出
6

解题思路

当得分的队员尽量多、且都一样时,mvp得分最少;
得分队员最多为t个、即每分钟一个人得分,且每分钟得分相同;
由大到小穷举每一个可能的队员数n,就可得到每个队员的得分subsum=sum / n,如果sum能除清n,证明这个n有验证的必要。

然后,问题就转换为,将一个数组array,分割为n个小数组,且每个数组的和均为sum/n。
这个问题比较经典,我们专门写一个方法来实现。
为了方便解释这个方法的思路,我假设每个数组元素是一个质量为array[i]的小球,我们要把它们放入n个桶里面,每放一个检查桶里的总质量是否小于等于subsum,如果小于等于就接着放下一个小球,如果大于证明当前这种方法不行、把球取出尝试放入下一个桶…
直到所有的球都放入桶,那就是把数列成功分为n个和相等的子数列了。

java代码

public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int t = sc.nextInt();
        int[] scores = new int[t];
        int sum = 0;
        for (int i = 0; i < t; i++) {
            scores[i] = sc.nextInt();
            sum += scores[i];
        }
        //逆序排,提高检查效率,不加也能实现功能
        Arrays.sort(scores);
        int a = 0;
        int b = scores.length-1;
        while (a < b){
            int num = scores[a];
            scores[a] = scores[b];
            scores[b] = num;
            a++;
            b--;
        }

        for (int j = t; j > 1; j--) {
            if(sum % j == 0 && check(scores, 0, new int[j], sum/j)){
                System.out.println(sum/j);
                return;
            }
        }
        System.out.println(sum);
    }

    private static boolean check(int[] scores, int index, int[] group, int subSum){
        if(scores.length == index) return true;//index最多length-1,当index==lenth,说明所有元素都已放完;所有元素都放完,就返回true

        int selected = scores[index];
        for (int k = 0; k < group.length; k++) {
            if(k>0 && group[k] == group[k-1]) continue;
            group[k] += selected;
            if(group[k] <= subSum) {
                if(check(scores, index + 1, group, subSum)) return true;
            }
            group[k] -= selected;
        }

        return false;
    }

4、水仙花数

题目
给定一个非空字符串 s,将该字符串分割只成一些子串,使每个子串的 ASCI 码值的和均为水仙花数。
1.若分割不成功则返回 0
2.若分割成功且分割结果不唯一则返回 -1
3.若分割成功且分割结果唯一则返回分割后子串的数目
输入
输入字符串的最大长度为 200
输出
根据题目中的情况返回响应结果

测试用例1
# 输入
abc
# 输出
0

测试用例2
# 输入
f3@d5a8
# 输出
-1

测试用例3
# 输入
AXdddF
# 输出
2

解题思路

注意题目要求“每个子串的acsii码和都为水仙花数”,
则必然,0 -> x间的子串(首子串),必为水仙花数才行;
可设计一个递归方法,把原串s传入:
从0 ~ i 找第一个ascii码和为水仙花数的子串,可得
str1 = s.sub(0,i)
str2 = s.sub(i)
若str1和str2均为水仙花数,则为一种分法;
接着,把str2作为入参,重复调用递归方法,若找到,分法+1;

java代码

	private static LinkedList<Integer> cutNumList = new LinkedList<>();

    private static boolean checkWaterNumber(String param) {
        char[] chars = param.toCharArray();
        int num = 0;
        for (char c : chars) {
            num += c;
        }

        int g = num % 10;
        int s = num / 10 % 10;
        int b = num / 100;

        return g * g * g + s * s *s + b * b * b == num;
    }

    private static void cut(String inputStr, int cutNum) {

        for (int i = 1; i < inputStr.toCharArray().length; i++) {
            String subStrOne = inputStr.substring(0, i);
            String subStrTwo = inputStr.substring(i);
            if(checkWaterNumber(subStrOne)){
                if(checkWaterNumber(subStrTwo)){
                    cutNumList.add(cutNum);
                } else {
                    cut(subStrTwo, cutNum++);
                }
            }
        }
    }

    public static void main(String[] args) {
        try {
            Scanner sc = new Scanner(System.in);
            String str = sc.nextLine();
            if (str == null || str.length() == 0) {
                System.out.println("input error");
                return;
            }

            cut(str, 2);

            int result = 0;
            if(cutNumList.size() > 1) {
                result = -1;
            } else if(cutNumList.size() == 1){
                result = cutNumList.get(0);
            }

            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("error");
        }
    }

小知识点

1)水仙花数,是自幂数的一种,自幂数就是各位的n次方之和、正好等于这个数本身,n为这个数的位数;水仙花数特指三位数的自幂数,即从100~999间的自幂数就叫水仙花数。
2)清楚字符串“子串”、“子序列”的区别,子串是连续的、子序列可以不连续但要保证相对位置一致与原串一致;子串是子序列的一种,子序列包含子串。

5、礼品发放最小分组数

题目
又到了一年的末尾,项目组让小明负责新年晚会的小礼品发放工作。
为使得参加晚会的同事,所获得的小礼品价值相对平衡,需要把小礼品根据价格进行分组,但每组最多只能包括两件小礼品,并且每个分组
的价格总和不能超过一个价格上限。
为了保证发放小礼品的效率,小明需要找到分组数目最少的方案。
你的任务是写一个程序,找出分组数最少的分组方案,并输出最少的分组数目
输入
第一行数据为分组礼品价格之和的上限
第二行数据为每个小礼品的价格,按照空格隔开,每个礼品价格不超过分组价格和的上限
输出
输出最小分组数量

解题思路

简单题,读懂题意即可;
因为:
1)每组最多2个礼品;
2)尽可能少的分组;
3)总价不高于价格上限;
可得结论:
尽可能躲的2个一组,且不超价格上限。

排序 - 使用双指针,起始双指针分别指向最小、最大值,如果和小于价格上限,就分为一组;如果大于价格上限,则右指针向左移,再校验新的组合。

java代码

	public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        //nextInt或者nextLine只能用同一种,否则会有重复读入的问题
        int priceMax = Integer.parseInt(sc.nextLine());
        
        //因为不知道有多少个礼品,所以用字符串接收参数
        String[] pricestr = sc.nextLine().split(" ");
        
        int[] prices = new int[pricestr.length];
        for (int i = 0; i < pricestr.length; i++) {
            prices[i] = Integer.parseInt(pricestr[i]);
            if(prices[i] < 0 || prices[i] > priceMax) {
                System.out.println("input error");
                return;
            }
        }

        Arrays.sort(prices);

        int a = 0;
        int b = prices.length - 1;
        int result = 0;
        while (a <= b) {
            if(a == b) {
                result++;
                break;
            }
            if(prices[a] + prices[b] <= priceMax) {
                a++;
                b--;
                result++;
            } else {
                b--;
                result++;
            }
        }
        System.out.println(result);
    }

7、乱序整数序列,和的绝对值最小

题目
给定一个随机的整数(可能存在正整数和负整数)数组 nums,请你在该数组中找出两个数,其和的绝对值(| nums[x]+nums[y] |)为最小值,并返回这个两个数(按从小到大返回)以及绝对值。
每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
输入
一个通过空格分割的有序整数序列字符串,最多 1000 个整数,且整数数值范围是[-65535,65535].
输出
两数之和绝对值最小值

测试用例
# 输入
-1 -3 7 5 11 15
# 输出
-3 5 2

解题思路

1)注意,是和的绝对值最小,不是绝对值的和;
2)回溯全排列,求出所有可能,放入list<list>中,内部list有3个元素a,b,|a+b|,按list.get(2)排序,输出最小的即可。

不用回溯,好像用双循环也可以,因为题目说保证只有一个答案。

java代码

	public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String[] arrStr = sc.nextLine().split(" ");
        if(arrStr.length > 1000) {
            System.out.println("input error");
            return;
        }
        int[] arr = new int[arrStr.length];
        for (int i = 0; i < arrStr.length; i++) {
            arr[i] = Integer.parseInt(arrStr[i]);
            if(arr[i] > 65535 || arr[i] < -65535) {
                System.out.println("input error");
                return;
            }
        }

        Arrays.sort(arr);
        List<List<Integer>> allList = new ArrayList<>();
        for (int x = 0; x < arr.length - 1; x++) {
            for (int y = x + 1; y < arr.length; y++) {
                int sum = arr[x] + arr[y] >= 0 ? arr[x] + arr[y] : -(arr[x] + arr[y]);
                List<Integer> list = new ArrayList<>();
                list.add(arr[x]);
                list.add(arr[y]);
                list.add(sum);
                allList.add(list);
            }
        }

        //这个好像还能简化,但这已经是我能想到的最简单的了
        allList.sort((m, n) -> m.get(2) - n.get(2));

        System.out.println(allList.get(0).get(0) + " " + allList.get(0).get(1) + " " + allList.get(0).get(2));
    }

9、交换一次字符,使字符串最小

题目
给定一个字符串 s,变化规则:
交换字符串中任意两个不同位置的字符M;
s 都是小写字符组成;
1<=s.length<= 1000
输入
一串小写字母组成的字符串
输出
按照要求变换得到最小字符串

测试用例1:
# 输入
abcdef
# 输出
abcdef

测试用例2:
# 输入
bcdefa
# 输出
acdefb

解题思路

第一步,将字符串->字符数组 ->从小到大排序;
第二步,遍历字符数组,对比每个array[i] 与 s.charAt(i),找到第一个两者不相等的i记为index,并记录元素array[i]为ch;
第三步,将原串s最后一个ch与index处的元素换位,所得字符串即为“交换一次字符,所能得到的最小字符串”;

java代码

public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String input = sc.nextLine();
        char[] chars = input.toCharArray();
        Arrays.sort(chars);

        int indexOne = -1;
        for (int i = 0; i < chars.length; i++) {
            if(chars[i] != input.charAt(i)) {
                indexOne = i;
                break;
            }
        }

        if(indexOne != -1) {
            for (int j = input.length() - 1; j >= 0; j--) {
                if(input.charAt(j) == chars[indexOne]) {
                    char[] charArray = input.toCharArray();
                    char temp = charArray[indexOne];
                    charArray[indexOne] = charArray[j];
                    charArray[j] = temp;
                    input = new String(charArray);
                    break;
                }
            }
        }

        System.out.println(input);

    }

11、完美走位

题目
在第一人称射击游戏中,玩家通过键盘的A、S、D、W四个按键控制游戏人物分别向左、向后、向右、向前进行移动,从而完成走位。
假设玩家每按动一次键盘,游戏人物会向某个方向移动一步,如果玩家在操作一定次数的键盘并且各个方向的步数相同时,此时游戏人物必定会回到原点,则称此次走位为完美走位。
现给定玩家的走位(例如: ASDA),通过更换其中一段连续走位的方式使得原走位能够变成一个完美走位。
其中待更换的连续走位可以是相同长度的任何走位。
请返回待更换的连续走位的最小可能长度。
若果原走位本身是一个完美走位,则返回0。
输入
输入为由键盘字母表示的走位s,例如:ASDA
输出
输出为待更换的连续走位的最小可能长度
备注
1.走位长度 1≤s.length≤105
2.s.length 是4的倍数
3.s中只含有 A,S,D,W四种字符

测试用例1:
# 输入
ASDW
# 输出
0

测试用例2:
# 输入
AASW
# 输出
1

测试用例3:
# 输入
AAAA
# 输出
3

解题思路

简单题,只要读懂题意就可以了;
完美走位的要求有两个:
1)各方向步数相同;
2)回到原点;
有没有各方向步数相同这个条件很关键

解题步骤:
1)hashmap统计各方向步数;
2)步数>length/4的,统计多出来的步数(也可统计不足len/4);
3)这些多出来的步数之和,即为最少要变化的步数;

java代码

	public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String inputStr = sc.nextLine();
        int length = inputStr.length();
        if(length<1 || length>10*10*10*10*10 || length%4!=0) {
            System.out.println("input error");
            return;
        }
        Map<Character, Integer> countMap = new HashMap<>();
        countMap.put('A', 0);
        countMap.put('S', 0);
        countMap.put('D', 0);
        countMap.put('W', 0);
        for (char c : inputStr.toCharArray()) {
            if (c != 'A' && c!='S' && c!='D' && c!='W') {
                System.out.println("input error");
                return;
            }

            if(c == 'A') {
                Integer a = countMap.get('A');
                countMap.put('A', ++a);
            }
            if(c == 'S') {
                Integer s = countMap.get('S');
                countMap.put('S', ++s);
            }
            if(c == 'D') {
                Integer d = countMap.get('D');
                countMap.put('D', ++d);
            }
            if(c == 'W') {
                Integer  w= countMap.get('W');
                countMap.put('W', ++w);
            }
        }

        //因为各个方向走位“步数相同”,所以要想完美走位,那各个方向的走位步数应该都=length/4
        //超过length/4的,就是需要改成其它走位的,把它们都加起来就是最少要变的数目
        List<Integer> list = new LinkedList<>();
        countMap.forEach((k, v) -> {
            if(v-length/4 > 0) {
                list.add(v-length/4);
            }
        });

        int result = 0;
        if(list.size() > 0){
            for (Integer i : list) {
                result += i;
            }
        }

        System.out.println(result);

    }

12、去除多余空格

题目
去除文本多余空格,但不去除配对单引号之间的多余空格。给出关键词的起始和结束下标,去除多余空格后刷新关键词的起始和结束下标。

条件约束:
1.不考虑关键词起始和结束位置为空格的场景;
2.单词的的开始和结束下标保证涵盖一个完整的单词,即一个坐标对开始和结束下标之间不会有多余的空格;
3.如果有单引号,则用例保证单引号成对出现;
4.关键词可能会重复;
5.文本字符长度 length 取值范围:[0,100000]
输入
输入为两行字符串:
第一行:待去除多余空格的文本,用例保证如果有单引号,则单引号成对出现,且单引号可能有多对
第二行:关键词的开始和结束坐标,关键词间以逗号区分,关键词内的开始和结束位置以单空格区分
例如:

Life is painting a  picture, not doing 'a  sum'.
8 15,20 26,43 45

关键词为painting picture sum

输出
输出为两行字符串:
第一行:去除多余空格后的文本
第二行:去除多余空格后,关键词新的开始和结束下标

测试用例1:
# 输入
Life is painting a  picture, not doing 'a  sum'.
8 15,20 26,43 45
# 输出
Life is painting a picture, not doing 'a  sum'.
[8, 15][19, 25][42, 44]

测试用例2:
# 输入
Life is painting a picture, not doing 'a  sum'.
8 15,19 25,42 44
# 输出
Life is painting a picture, not doing 'a  sum'.
[8, 15][19, 25][42, 44]

解题思路

学会以下几个知识点即可:
1)会去除、加开关控制不去除某段字符串,单引号加反斜杠;
通过逐个遍历 + 拼接新串实现。
2)直到String的indexOf()方法,是找字符串首字符下标的;并会根据首字符下标计算尾字符下标index+length-1;
3)会处理要找字符串可能重复的情况;
通过逐个找、每次限定要找的范围不包含上一个已找出的子串,即:
indexOf(keyStr, fromIndex)

java代码

	public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String inputStr = sc.nextLine();
        if (inputStr == null || inputStr.length() == 0 || inputStr.length() > 100000) {
            System.out.println("input error");
            return;
        }

        String indexStr = sc.nextLine();
        String[] indexLRStr = indexStr.split(",");
        String[] keyStr = new String[indexLRStr.length];

        for (int i = 0; i < keyStr.length; i++) {
            String[] indexArr = indexLRStr[i].split(" ");
            int leftIndex = Integer.parseInt(indexArr[0]);
            int rightIndex = Integer.parseInt(indexArr[1]);
            keyStr[i] = inputStr.substring(leftIndex, rightIndex + 1);
        }

        //注意这里去除多余空格的方法
        StringBuilder sb = new StringBuilder();
        boolean mark = false;
        char[] chars = inputStr.toCharArray();
        for (int i = 0; i < inputStr.length(); i++) {
            char inputChar = chars[i];
            if (inputChar == '\'') {
                mark = !mark;
            }
            if (mark) {
                sb.append(inputChar);
                continue;
            }
            if (inputChar == ' ' && chars[i - 1] == ' ') {
                continue;
            }
            sb.append(inputChar);
        }
        System.out.println(sb);

        int fromIndex = 0;
        for (String key : keyStr) {
            int leftIndex = sb.indexOf(key, fromIndex);
            int rightIndex = leftIndex + key.length() - 1;
            int[] indexArr = {leftIndex, rightIndex};
            System.out.print(indexArr.toString());
            fromIndex = rightIndex;
        }

    }

13、整数编码

题目
实现一种整数编码方法,使得待编码的数字越小,编码后所占用的字节数越小。
编码规则如下:
1、编码时7位一组,每个字节的低7位用于存储待编码数字的补码;
2、字节的最高位表示后续是否还有字节,置1表示后面还有更多的字节,置0 表示当前字节为最后一个字节;
3、采用小端序编码,低位和低字节放在低地址上;
4、编码结果按 16 进制数的字符格式输出,小写字母需转换为大写字母;
输入
输入的为一个字符串表示的非负整数
输出
输出一个字符串,表示整数编码的 16 进制码流

测试用例1:
# 输入
0
# 输出
00

测试用例2:
# 输入
100
# 输出
64

测试用例3:
# 输入
1000
# 输出
E807

说明
1000 的二进制表示为 0011 1110 1000,至少需要两个字节进行编码;
第一个字节最高位置1,剩余的7位存储数字 1000 的第一个低7位(110 1000),所以第一个字节的二进制为 1110 1000,即 E8;
第二个字节最高位置 0,剩余的7 位存储数字 1000 的第二个低7位(000 0111),所以第一个字节的二进制为 0000 0111,即 07;
采用小端序编码,所以低字节 E8 输出在前,高字节 07 输出在后。

备注
待编码的数字取值范围为[0,1<=64-1]

解题思路

1)思路:
10进制数
-> 转二进制字符串
-> 从后往前取7位,不足7位左补0
-> 根据有无剩余决定前补1 or 补0
-> 转16进制放入list
-> 因要求小端序,故list按顺序输出即可得到要求答案

2)技巧:
N进制字符串与十进制数互相转换:
n进制字符串转换为10进制数 -> Integer.parseInt(N进制字符串, n),
也可以 new BigInteger(num, n);
十进制数转n进制字符串:num.toString(n),
对于二进制也可以 Integer.toBinaryString(num)

java代码

	public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int inputNum = sc.nextInt();
        String inputNumStr = Integer.toBinaryString(inputNum);
        if(inputNum < 0) {
            System.out.println("input error");
            return;
        }

        while (inputNumStr.length() > 0) {
            if(inputNumStr.length() < 7) {
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < 7 - inputNumStr.length(); i++) {
                    sb.append("0");
                }
                inputNumStr = sb.append(inputNumStr).toString();
            }

            String substring = inputNumStr.substring(inputNumStr.length() - 7);
            inputNumStr = inputNumStr.substring(0, inputNumStr.length() - 7);

            if(inputNumStr.length() > 0) {
                substring = "1" + substring;
            }else {
                substring = "0" + substring;
            }

            String hexString = Integer.toHexString(Integer.parseInt(substring, 2)).toUpperCase();

            if(hexString.length() < 2) {
                hexString = "0" + hexString;
            }

            System.out.print(hexString);
        }
    }

60、计算网络信号

题目

网络信号经过传递会逐层衰减,且遇到阻隔物无法直接穿透,在此情况下需要计算某个位置的网络信号值。注意:网络信号可以绕过阻隔物
array[m][n],二维数组代表网格地图
array[i][j]=0,代表i行j列是空旷位置
array[i][j]= x,(x为正整数)代表i行j列是信号源,信号强度是x,
array[i][j]=-1, 代表i行j列是阻隔物
信号源只有1个,阻隔物可能有0个或多个;
网络信号袁减是上下左右相邻的网格衰减1现要求输出对应位置的网络信号值。

输入
输入为三行,
第一行为 m、n,代表输入是一个mxn 的数组,
第二行是一串 mxn 如个用空格分隔的整数每连续n个数代表一行,再往后n个代表下一行,以此类推。对应的值代表对应的网格是空矿位置,还是信号源,还是阻隔物,
第三行是ì、j,代表需要计算 array[i][j] 的网络信号值。注意:此处i和j均从 0开始,即第一行i为0;

例如:

6 5
0 0 0 -1 0 0 0 0 0 0 0 0 -1 4 0 0 0 0 0 0 0 0 0 0 -1 0 0 0 0 0
1 4

输出
输出对应位置的网络信号值,如果网络信号未覆盖到,也输出0。
一个网格如果可以途径不同的传播衰减路径传达,取较大的值作为其信号值。

解题思路

把信号源向上下左右四个方向扩散,并把满足条件的扩散点作为新的信号源继续扩散,直到信号值为0、或者扩散到“坐标系”边缘

6 5
0 0 0 -1 0 0 0 0 0 0 0 0 -1 4 0 0 0 0 0 0 0 0 0 0 -1 0 0 0 0 0

对于以上输入,得到的二维数组(可视为坐标系)为

0 0  0 -1  0 
0 0  0  0  0 
0 0 -1  4  0 
0 0  0  0  0 
0 0  0  0 -1 
0 0  0  0  0

扩散后,最终为

0 0  1 -1  1 
0 1  2  3  2 
0 0 -1  4  3 
0 1  2  3  2 
0 0  1  2 -1 
0 0  0  1  0

这里一定要注意,二维数组和我们上学时常用的xy轴坐标系还是有区别的,它们的原点不同,下面简化一下以上二维数组坐标系示意图

在这里插入图片描述

6 5
0 0 0 -1 0 0 0 0 0 0 0 0 -1 4 0 0 0 0 0 0 0 0 0 0 -1 0 0 0 0 0
1 4

这个输入,要求的输出array[1][4],它的结果是2;
我一开始把二维数组和上学时的xy轴坐标系给搞混了,死活想不明白(1,4)为啥是2,而不是1?

java代码

没有作输入的合法性校验,有兴趣的可以自己补充一下;
变量名起的也比较随意,大家不要学我

import java.util.LinkedList;
import java.util.Scanner;

public class A60计算网络信号 {

    private static LinkedList<Danyuange> list = new LinkedList<>();
    private static int[][] zhouweiArr = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int m = sc.nextInt();
        int n = sc.nextInt();
        int[][] inputArr = new int[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                int d = sc.nextInt();
                inputArr[i][j] = d;
                if(d > 0) {
                    Danyuange yuan = new Danyuange();
                    yuan.setX(i);
                    yuan.setY(j);
                    yuan.setD(d);
                    list.add(yuan);
                }
            }
        }
        int queryX = sc.nextInt();
        int queryY = sc.nextInt();

        while (list.size() > 0) {
            Danyuange danyuange = list.removeFirst();
            kuosanMethod(inputArr, danyuange);
        }

        System.out.println(inputArr[queryX][queryY]);
    }

    private static void kuosanMethod(int[][] inputArr, Danyuange danyuange) {
        int x = danyuange.getX();
        int y = danyuange.getY();
        int d = danyuange.getD();
        for (int i = 0; i < 4; i++) {
            int newX = x + zhouweiArr[i][0];
            int newY = y + zhouweiArr[i][1];
            if(newX >= 0 && newX < inputArr.length && newY >= 0 && newY < inputArr[0].length) {
                if(inputArr[newX][newY] == 0) {
                    inputArr[newX][newY] = d - 1;
                }
                if(inputArr[newX][newY] < d && inputArr[newX][newY] >= 2 && inputArr[newX][newY] != -1) {
                    Danyuange newDanyuange = new Danyuange();
                    newDanyuange.setX(newX);
                    newDanyuange.setY(newY);
                    newDanyuange.setD(d-1);
                    list.add(newDanyuange);
                }
            }

        }
    }

    private static class Danyuange {
        int x;
        int y;
        int d;

        public int getX() {
            return x;
        }

        public void setX(int x) {
            this.x = x;
        }

        public int getY() {
            return y;
        }

        public void setY(int y) {
            this.y = y;
        }

        public int getD() {
            return d;
        }

        public void setD(int d) {
            this.d = d;
        }
    }
}

61、简易内存池

题目

请实现一个简易内存池根据请求命令完成内存分配和释放内存池支持两种操作命令REQUEST和 RELEASE ;
其格式为:
REQUEST=请求的内存大小
表示请求分配指定大小内存
如果分配成功,返回分配到的内存首地址;
如果内存不足,或指定的大小为零则输出 error;
RELEASE=释放的内存首地址
表示释放掉之前分配的内存,释放成功无需输出如果,
释放不存在的首地址则输出 error

注意:
1.内存池总大小为 100 字节
2.内存池地址分配必须是连续内存,并优先从低地址分配
3.内存释放后可被再次分配,已释放的内存在空闲时不能被二次释放4.不会释放已申请的内存块的中间地址
5.释放操作只是针对首地址所对应的单个内存块进行操作,不会影响其他内存块

输入:
首行为整数 N,表示操作命令的个数取值范围 8<N<=100
接下来的N行,每行将给出一个操作命令;
操作命令和参数之间用“=”分割

输出:
见题目输出要求

示例1:

输入:
2
REQUEST=10
REQUEST=20
输出:
0
10

示例2:

输入:
6
REQUEST=10
REQUEST=20
RELEASE=0
RELEASE=20
REQUEST=20
REQUEST=10
输出:
0
10
error
30
0

解题思路

1)用二维数组收录输入,会非常便于后续操作;当然也可以用单独的数组;
2)用treemap模拟内存(必须用treemap,需要key有序);

  • RELEASE释放比较简单,包含key就remove,不包含就输出error;
  • REQUEST单独一个方法处理,会使代码逻辑很清晰;自定义变量left初始值为0,表示与下一个已用内存首地址之间、未使用内存的首地址;
	0————k1-v1————k2-v2————...————kn-vn————max
  • 如上图示意,k-left就代表空闲内存的长度,k1-0就是最前边的空闲内存,从前往后、循环测试map的所有空闲内存key-left是否大于等于新内存l;
  • ps:学会取map的key放入list的方法 List keyList = new ArrayList<>(map.keySet())
  • 如果这个长度大于等于要存入的长度l,map直接put(left, left + l),并结束方法return;
  • ps:还是为了方便,v不存真实的尾地址索引,而是直接存(索引+1),即占用内存长度是 [k, v)
  • 如果这个长度小于要存入的长度l,则left = v,继续下一个循环;
  • 当map的所有key都测试过,仍未找到可以放新内存l的地方,测试 max-left(此时left等于vn)是否大于等于l,
  • 如果大于等于,put(left, left + l)
  • 如果小于,输出error;

java代码


public class A61简易内存池 {
    private static Map<Integer, Integer> map = new TreeMap<>();

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int orderNum = Integer.parseInt(sc.nextLine());

        String[][] orders = new String[orderNum][];
        for (int i = 0; i < orderNum; i++) {
            orders[i] = sc.nextLine().split("=");
        }

        for (int j = 0; j < orderNum; j++) {
            String order = orders[j][0];
            int length = Integer.parseInt(orders[j][1]);
            if("REQUEST".equals(order)) {
                requestMethod(length);
            } else {
                if(map.containsKey(length)) {
                    map.remove(length);
                } else {
                    System.out.println("error");
                }
            }
        }
    }

    private static void requestMethod(int length) {
        int left = 0;
        if(map.size() == 0){
            map.put(length, left + length);//包含左、不包含右
        }
        List<Integer> keyList = new ArrayList<>(map.keySet());
        for (Integer key : keyList) {
            if(key - left >= length) {
                map.put(left, left + length);//包含左、不包含右
                System.out.println(left);
                return;
            } else {
                left = map.get(key);
            }
        }
        //循环结束,表示所有kv之间,都没有找到能放length的地方;检查剩余字节
        if(100 - left >= length) {
            map.put(left, left + length);//包含左、不包含右
            System.out.println(left);
        } else {
            System.out.println("error");
        }
    }
}

80、机器人走迷宫

题目

1.房间由 xγ 的方格组成,例如下图为 64 的大小。每一个方格以坐标(x,y)描述。
2.机器人固定从方格(0,0)出发,只能向东或者向北前进,出口固定为房间的最东北角,如下图的方格(5,3)。用例保证机器人可以从入口走到出口。
3.房间有些方格是墙壁,如(4,1),机器人不能经过那儿。
4.有些地方是一旦到达就无法走到出口的,如标记为B的方格,称之为陷阱方格。
5.有些地方是机器人无法达到的,如标记为A的方格,称之为不可达方格,不可达方格不包括墙壁所在的位置。
6.如下实例图中,陷阱方格有2个,不可达方格有3个。
7.请为该机器人实现路径规划功能:给定房间大小,墙壁位置,请计算出陷阱方格与不可达方格分别有多少个
在这里插入图片描述
输入
1.第一行为房间的x和y(0<x,y<= 1000)
2.第二行为房间中墙壁的个数 N(0<= N<x*γ)
3. 接着下面会有 N行墙壁的坐标
同一行中如果有多个数据以一个空格隔开,用例保证所有的输入数据均合法(结尾不带回车换行)

6 4
5
0 2
1 2
2 2
4 1
5 1

输出
陷阱方格与不可达方格数量,两个信息在一行中输出,以一个空格隔开。(结尾不带回车换行)

2 3

示例二

#输入
6 4
4
2 0
2 1
3 0
3 1

#输出
0 4

解题思路

写一个方法solve(),功能为从给定起点开始,向北、向东扫描整个棋盘;
假设以(0, 0)为起点扫描,其扫描效果如下:
在这里插入图片描述
上图中标记的点位,即为可以扫到的地方,存入checkedSet中;
并且由图可知:
要求的“不可到达点” = 6 * 4 - checkedSet.size() - wallSet.size()

另外,在这个solve()方法中,还可以顺带得到一个集合stopedSet,用来存放往上/往东一步就撞墙的所有点位,如下图
在这里插入图片描述

从图中可以看出,如果我们再以stopedSet中的点位为起点,再次调用solve方法,即以起点向北、向东扫描;只有t1、t2两点,运行后得到的checked不包含终点。

注意:为了方便操作,我写了自定义类存放每个点对象;并且solve方法,用了递归调用,可能会有很多重复的点放入同一集合中,所以各个集合都用set存(可以自动去重);也正因如此,自定义点类,必须重写equals方法,以便set底层确认两个点对象是否相等。

java代码

import java.util.HashSet;
import java.util.Objects;
import java.util.Scanner;
import java.util.Set;

public class A80机器人走迷宫 {

    private static int xLength = 0;
    private static int yLength = 0;
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        xLength = sc.nextInt();
        yLength = sc.nextInt();
        int wallNum = sc.nextInt();
        Set<DianModel> wallSet = new HashSet<>();
        for (int i = 0; i < wallNum; i++) {
            int a = sc.nextInt();
            int b = sc.nextInt();
            wallSet.add(new DianModel(a, b));
        }

        Set<DianModel> checkedSet = new HashSet<>();
        Set<DianModel> stopedSet = new HashSet<>();
        solve(0, 0, wallSet, checkedSet, stopedSet);
        int cantGo = xLength * yLength - wallSet.size() - checkedSet.size();

        int xianjin = 0;
        for (DianModel model : stopedSet) {
            Set<DianModel> newCheckedSet = new HashSet<>();
            Set<DianModel> newStopedSet = new HashSet<>();
            solve(model.x, model.y, wallSet, newCheckedSet, newStopedSet);
            if(!newCheckedSet.contains(new DianModel(xLength-1, yLength-1))){
                xianjin++;
            }
        }

        System.out.println(xianjin + " " + cantGo);
    }

    private static class DianModel {
        int x;
        int y;

        public DianModel(int x, int y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            DianModel dianModel = (DianModel) o;
            return x == dianModel.x &&
                    y == dianModel.y;
        }

        @Override
        public int hashCode() {
            return Objects.hash(x, y);
        }
    }

    private static void solve(int x, int y, Set<DianModel> wallSet, Set<DianModel> checkedSet, Set<DianModel> stopedSet) {
        if(x >= xLength || y >= yLength) {
            return;
        }
        checkedSet.add(new DianModel(x, y));
        //向北
        if(wallSet.contains(new DianModel(x, y+1))) {
            stopedSet.add(new DianModel(x, y));
        } else {
            solve(x, y+1, wallSet, checkedSet, stopedSet);
        }
        //向东
        if(wallSet.contains(new DianModel(x+1, y))) {
            stopedSet.add(new DianModel(x, y));
        } else {
            solve(x+1, y, wallSet, checkedSet, stopedSet);
        }
    }
}

82、任务混部

83、最多等和连续不相交子序列

题目

某给定一个数组,我们称其中连续的元素为连续子序列,称这些元素的和为连续子序列的和。
数组中可能存在几组连续子序列,组内的连续子序列互不相交且有相同的和。
求一组连续子序列,组内子序列的数目最多;输出这个数目。

测试用例1

#输入
10
8 8 9 1 9 6 3 9 1 0
#输出
4

说明:4个子序列左坐标、右坐标
2 2
4 4
5 6
7 7

测试用例2

#输入
10
-1 0 4 -3 6 5 -6 5 -7 -3
#输出
3

说明:3个子序列左坐标、右坐标
3 3
5 8
9 9

解题思路

要求最终答案,我们至少需要存这么几个参数:

  • 子序列和,这是一组子序列的标识(类似id);
  • 每个子序列的最左、最右元素坐标;

首先,要想好这些参数用什么存储?
坐标用数组存比较方便,a[0]是左坐标,a[1]是右坐标;
所有元素和相同的子序列需要放到一起,我们把坐标放入ArrayList,正好后面需要根据左坐标做个排序,arraylist排序也比较好操作;
最后,根据子序列和与子序列、一对多的关系,选用HashMap把序列和、坐标整合到一起,即
Map<Integer, ArrayList<int[]>> map = new HashMap<>()。

第二,怎么找所有的子序列?
这个比较简单,用双循环即可实现;第一个循环确定最左元素,第二个循环确认最右元素,子序列就是它们之间的所有元素;

for (int i = 0; i < n; i++) {
	for (int j = i; j < n; j++) {
               
	}
}

第三,上面这种方式,找出的坐标集合,会有相交的情况;比如数组{5,3,5},(0,1)和(1,2)子序列和都是8,所以都会放入一个list。
针对这种情况,需要写一个单独的方法:去掉list中坐标相交的子序列,返回剩余子序列数量;
而去掉相交、统计剩余数量,其实就是 -> 只统计不相交的子序列数量。

第四,可以看出,在找子序列的过程中,会需要计算大量子序列的和;为了提升效率,最好提前准备DP表避免重复计算。

java代码

public class A83最多等和不相交连续子序列my {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] inputArr = new int[n];
        for (int i = 0; i < n; i++) {
            inputArr[i] = sc.nextInt();
        }
        //为避免大量重复求数组和的运算,提前准备好dp表;这部分是为了提升效率,图简单可以用一个求数组的方法替换(但不建议)
        int[] dp = new int[n];
        dp[0] = inputArr[0];
        for (int i = 1; i < n; i++) {
            dp[i] = inputArr[i] + dp[i-1];
        }
        //求全部子序列可能,把子序列和、子序列开始、结束坐标,放入map
        Map<Integer, ArrayList<int[]>> map = new HashMap<>();
        for (int i = 0; i < n; i++) {
            for (int j = i; j < n; j++) {
                if(i == 0) {
                    int sum = dp[j];
                    map.put(sum, map.getOrDefault(sum, new ArrayList<>()));
                    map.get(sum).add(new int[]{i, j});
                } else {
                    int sum = dp[j] - dp[i-1];
                    map.put(sum, map.getOrDefault(sum, new ArrayList<>()));
                    map.get(sum).add(new int[]{i, j});
                }
            }
        }
        //找出不相交子序列最多的数量
        int maxNum = 0;
        for (Integer key : map.keySet()) {
            ArrayList<int[]> list = map.get(key);
            maxNum = Math.max(maxNum, cutOverlapAndGetNum(list));
        }
        //输出结果
        System.out.println(maxNum);
    }

    //去掉list中坐标相交的子序列,返回剩余子序列数量
    private static int cutOverlapAndGetNum (ArrayList<int[]> list) {
        //根据每个子序列左坐标从小到大排序
        list.sort(Comparator.comparingInt(a -> a[0]));
        //去掉相交、统计剩余数量 == 统计不相交数量
        int count = 1;//开始就1个,后面每找到一个加一
        int beforeRight = list.get(0)[1];
        for (int i = 1; i < list.size(); i++) {
            int left = list.get(i)[0];
            int right = list.get(i)[1];
            if(left > beforeRight) {
                count++;
                beforeRight = right;
            }
        }
        return count;
    }
}

87、最短耗时

题目

测试用例:

#输入
2,2,2,3,3,5,5,4,4
2

#输出
9
#输入
1,2
2

#输出
2
#输入
2,2,2,3
2

#输出
7

解题思路

用map存放数据,key为任务编号,value为该任务需要执行次数;
取出value放入一个list,从大到小排序;

因为相同两任务,中间必然有间隔n,所以任务数大于1的任务执行时间为“1 + n”;
为了节省时间,n这几秒时间需要分配给其它不是当前任务的任务,我们优先给要执行次数多的先分配,这样比较好操作;
比如2,2,2,3,3,4,4,5,5
可以2,3,4,5,2,3,4,5,2
也可以2,3,4,5,2,3,4,2,5
这两个执行顺序耗时都是最少的9。

模拟任务执行:
1)遍历放任务数的list(已从大到小排序),while(list.size > 0),不断执行;
2)取出第一个任务,判断它是否大于1;
小于1就直接执行{执行时间+1,这个任务执行完成list.get(0)-1};
大于1,则执行时间+1+n;
并且不仅要执行当前这个任务,后面n个任务都要执行;

	time += 1 + n;
	for (int i = 0; i <= n; i++) {
		if(i < taskNum.size()) {
			taskNum.set(i, taskNum.get(i) - 1);
		}
	}

3)下次执行前,整理放任务数的list:
把任务数等于0的去除;
把剩余的再次从大到小排序;
然后重新赋给list,执行下一次while循环。

4)循环完成,输出总耗时即可。

java代码

public class A87最短耗时 {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String[] split = sc.nextLine().split(",");
        int n = Integer.parseInt(sc.nextLine());

        Map<Integer, Integer> map = new HashMap<>();
        for (String s : split) {
            int key = Integer.parseInt(s);
            map.put(key, map.getOrDefault(key, 0) + 1);
        }
        ArrayList<Integer> taskNum = new ArrayList<>(map.values());
        taskNum.sort((a, b) -> b - a);

        int time = 0;
        while (taskNum.size() > 0) {
            int maxTaskNum = taskNum.get(0);
            if(maxTaskNum > 1) {
                time += 1 + n;
                for (int i = 0; i <= n; i++) {
                    if(i < taskNum.size()) {
                        taskNum.set(i, taskNum.get(i) - 1);
                    }
                }
            } else {
                time ++;
                taskNum.set(0, taskNum.get(0) - 1);
            }

            taskNum = (ArrayList<Integer>) taskNum.stream()
                        .filter(x -> x != 0)
                        .sorted((a, b) -> b - a)
                        .collect(Collectors.toList());
        }

        System.out.println(time);
    }
}

91、考古学家

题目

有一个考古学家发现一个石碑,但是很可惜,发现时其已经断成多段,只剩N个断口整齐的石碑碎片。
为了破解石碑内容,考古学家希望有程序能帮忙计算复原后的石碑文字组合数,你能帮忙吗?
备注: 如果存在石碑碎片内容完全相同,则由于碎片间的顺序不影响复原后的碑文内容,仅相同碎片间的位置变化不影响组合。

输入
第一行输入N,N表示石碑碎片的个数第二行依次输入石碑碎片上的文字内容s共有N组

输出
输出石碑文字的组合(按照升序排列),行尾无多余空格

测试用例

#输入
3
a b c

#输出
abc
acb
bac
bca
cab
cba

#输入
3
a b a

#输出
aab
aba
baa
#输入
3
a b ab

#输出
aabb
abab
abba
baab
baba

解题思路

本题要用到回溯算法,为找到最终解穷举所有可能情况,在过程中如果遇到不满足求解条件的,就回退、尝试其它情况。
比如有数组{a, b, c, d},要从中找出ad两个元素,
先找第一个所有可能为a、b、c、d,
再找第二个,对于第一个为a,第二个所有可能为b、c、d,但是到b时,发现有ad不包含的元素b,则直接回退尝试下一个c…
依此类推,直到尝试完所有结果。

本题,是回溯算法的典型问题之一:全排列问题
这个问题一般来说是这样的:有一个数组arr[ ],要求返回其所有可能的全排列。
而全排列问题,又可根据元素是否有重复,分为“元素有重复”、“元素无重复”两种情况;两种情况都是用回溯算法求解,无重复的简单一些,有重复的难一些,本体是有重复的情况。

1)元素无重复的全排列问题->回溯解法
最好是单独写一个操作方法,然后主程序调用这个方法,这样代码逻辑清晰一些;比如,操作方法叫solve();
我们可以把全排列问题,转换为“安排座位”这个情景:有arr.length个座位、arr.length个同学,排出他们所有可能的坐法。
先安排第一位,arr中的每个元素都可能坐在第一位;
再安排第二位,除第一位外剩余元素,都可能放第二位;
再安排第三位,除第一位、第二位外的剩余元素,可以放第三位;

怎么判断某元素能不能放在当前这个位置呢?
我们可以把arr中的元素取出来,放入list,以此模拟排座位;
这样,就可以通过list.contains(某元素),判断某元素是否已经排过座位、能否放在当前位置了;
因为元素没有重复,才能用list的contain方法作判断。

一直进行到list的size等于arr.length时,说明已经找到一种排法,记录之。

2)元素有重复的全排列问题->回溯解法
元素可能重复,就无法通过contain判断当前元素是否已经被安排过、能否安排在当前位置了;
这种情况不要用逐个取arr元素放入list的方法,而是直接对arr元素进行换位

先把每个元素都换到第一位(当然是逐个进行);
换完后,递归调用本方法,把剩余的每个元素换到第二位(逐个进行);
之后先复元第一位,再尝试其它元素在第一位的所有可能…

终止条件和元素无重复的情况差不多,当发现换位已经到了最后一位时,说明找到一种解法,记录之。
不过,记录方式与无重复的情况不同;先把结果拼接成字符串,再放入set(最好是treeset,不仅去重,还会排序),这样可以避免重复元素造成重复结果的情况。

java代码


public class A91考古学家 {

    private static Set<String> set = new TreeSet<>();
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int length = Integer.parseInt(sc.nextLine());
        String[] arrayStr = sc.nextLine().split(" ");
        solve(0, arrayStr);//从穷举第一位的情况开始回溯算法
        for (String s : set) {
            System.out.println(s);
        }
    }

    private static void solve(int currentIndex, String[] array) {
    	//尝试到最后一个元素就结束
        if(currentIndex == array.length-1) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < array.length; i++) {
                sb.append(array[i]);
            }
            set.add(sb.toString());
            return;
        }
        //把剩余元素,放到当前坐标currentIndex,剩余每个元素在这个位置的情况都要列举到
        for (int i = currentIndex; i < array.length; i++) {
            change(i, currentIndex, array);
            solve(currentIndex+1, array);
            change(i, currentIndex, array);//递归完,复元,尝试下一元素
        }
    }

	//换位的方法
    private static void change(int oneIndex, int twoIndex, String[] array) {
        String temp = array[oneIndex];
        array[oneIndex] = array[twoIndex];
        array[twoIndex] = temp;
    }

}

x

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值