【Py/Java/C++三种语言OD2023C卷真题】20天拿下华为OD笔试之【回溯】2023C-田忌赛马【欧弟算法】全网注释最详细分类最全的华为OD真题题解

题目描述与示例

题目描述

给定两个只包含数字的数组 a, b,调整数组 a 里面数字的顺序,使得尽可能多的 a[i] > b[i]。数组 ab 中的数字各不相同。

输出所有可以达到最优结果的 a 数组数量

输入描述

输入的第一行是数组 a 中的数字

输入的第二行是数组 b 中的数字

其中只包含数字,每两个数字之间相隔一个空格,ab 数组大小不超过 10

输出描述

输出所有可以达到最优结果的 a 数组数量

示例一

输入
11 8 20
10 13 7
输出
1
说明

最优结果只有一个,a = [11, 20, 8],故输出 1

示例二

输入
11 12 20
10 13 7
输出
2
说明

有两个 a 数组的排列可以达到最优结果,[12, 20, 11][11, 20, 12],故输出 2

示例三

输入
1 2 3
4 5 6
输出
0
说明

a 无论如何都会全输,所以无最优解。

解题思路

最优结果的胜出组数

经过调整后,a数组中胜利的最大组数max_win_num,很容易想到用贪心来解决。

max_win_num = 0

a.sort(reverse=True)
b.sort(reverse=True)
idx_a, idx_b = 0, 0

while idx_b < n:
    if a[idx_a] > b[idx_b]:
        idx_a += 1
        idx_b += 1
        max_win_num += 1
    else:
        idx_b += 1

达到最优结果的数组数量

注意到ab 数组大小不超过 10,很容易想到可以用回溯枚举的方式,列举出所有具有max_win_num组胜利的数组a的排列。

这里的回溯无非是在数组a的全排列的基础上,加上若干题意的限制条件。

数组大小最大为10,一共存在10! = 3628800 ~= 10^6种排列情况,如果不加以剪枝,全部枚举出来,可能会导致超时。

对于每一层递归,考虑剩余可选择的元素个数rest_not_used = used.count(False)。本题存在两种剪枝策略:

  1. rest_not_used个元素全部都选上,数组a的胜利总组数rest_not_used + win_num都无法到达max_win_num组时,在当前状态继续进行递归已经没有意义了,可以直接剪枝
  2. 当当前胜利组数已经等于最大胜利组数,即win_num == max_win_num成立时,剩余的rest_not_used个元素一共可以带来factorial(rest_not_used)种排列,无需通过回溯获得这些排列的具体结果,直接令答案数量加上factorial(rest_not_used)即可。
from math import factorial

def dfs(a, b, idx_b, n, win_num, used):
    global ans
    rest_not_used = used.count(False)
    if max_win_num > win_num + rest_not_used:
        return
    if win_num == max_win_num:
        ans += factorial(rest_not_used)
        return
    for idx_a in range(n):
        if used[idx_a]:
            continue
        used[idx_a] = True
        dfs(a, b, idx_b+1, n, win_num + int(a[idx_a] > b[idx_b]), used)
        used[idx_a] = False

代码

Python

# 题目:【回溯】2023C-田忌赛马
# 分值:200
# 作者:许老师-闭着眼睛学数理化
# 算法:贪心/回溯
# 代码看不懂的地方,请直接在群上提问


# 导入计算阶乘的函数
from math import factorial


def dfs(a, b, idx_b, n, win_num, used):
    global ans
    # 数组a中,还没选择的个数rest_not_used
    # 即为used中值为False的个数
    # 或者n-idx_b,也是rest_not_used的值
    rest_not_used = used.count(False)
    # 剪枝:
    # 如果rest_not_used个元素全部都选上
    # 都无法到达max_win_num组,则无需继续考虑,直接剪枝
    if max_win_num > win_num + rest_not_used:
        return
    # 如果当前排列能够胜利的组数win_num等于max_win_num
    # 说明剩余尚未选择的rest_not_used个元素,
    # 无论如何排列,都一定是一组要求的答案
    # 根据排列组合,剩余的rest_not_used个元素,
    # 一共有factorial(rest_not_used)中排列方式
    if win_num == max_win_num:
        ans += factorial(rest_not_used)
        return
    # 考虑所有位置
    for idx_a in range(n):
        # 如果已经使用,则直接跳过
        if used[idx_a]:
            continue
        # 状态更新
        used[idx_a] = True
        # a[idx_a]和b[idx_b]进行比较
        # 如果前者大于后者,则win_num需要+1,否则不变
        # 同时idx_b需要+1
        dfs(a, b, idx_b+1, n, win_num + int(a[idx_a] > b[idx_b]), used)
        # 回滚
        used[idx_a] = False


# 输入a,b数组
a = list(map(int, input().split()))
b = list(map(int, input().split()))
n = len(a)

# 基于贪心思想,计算出a数组经过排序之后的最大胜利组数
max_win_num = 0

# 对数组a、b进行从大到小的排序
a.sort(reverse=True)
b.sort(reverse=True)
# 初始化两个指针,分别指向a和b的开头位置
idx_a, idx_b = 0, 0

# 循环
# 贪心策略:拿当前a中最大的值a[idx_a]
# 和b中小于a[idx_a]的数中的最大值,进行配对
while idx_b < n:
    # 如果当前a的最大值大于当前b的最大值
    # 让a[idx_a]和b[idx_b]进行匹配
    if a[idx_a] > b[idx_b]:
        idx_a += 1
        idx_b += 1
        max_win_num += 1
    # 否则让idx_b右移,寻找一个能够跟a[idx_a]匹配的最大值
    else:
        idx_b += 1

ans = 0

# 如果a全输,则没有最优策略,直接输出0
if max_win_num == 0:
    print(ans)
# 否则,才有进行回溯,计算具有最优策略的a数组排列的数量
else:
    used = [False] * n
    dfs(a, b, 0, n, 0, used)
    print(ans)

Java

import java.util.*;

public class Main {
    static int maxWinNum = 0;
    static int ans = 0;

    static void dfs(ArrayList<Integer> a, ArrayList<Integer> b, int idxB, int n, int winNum, boolean[] used) {
        int restNotUsed = 0;
        for (boolean val : used) {
            if (!val) {
                restNotUsed++;
            }
        }
        if (maxWinNum > winNum + restNotUsed) {
            return;
        }
        if (winNum == maxWinNum) {
            ans += factorial(restNotUsed);
            return;
        }
        for (int idxA = 0; idxA < n; ++idxA) {
            if (!used[idxA]) {
                used[idxA] = true;
                dfs(a, b, idxB + 1, n, winNum + (a.get(idxA) > b.get(idxB) ? 1 : 0), used);
                used[idxA] = false;
            }
        }
    }

    static int factorial(int n) {
        if (n <= 1) {
            return 1;
        }
        return n * factorial(n - 1);
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        ArrayList<Integer> a = new ArrayList<>();
        ArrayList<Integer> b = new ArrayList<>();
        
        String[] inputs = scanner.nextLine().split(" ");
        for (String input : inputs) {
            a.add(Integer.parseInt(input));
        }
        
        inputs = scanner.nextLine().split(" ");
        for (String input : inputs) {
            b.add(Integer.parseInt(input));
        }
        
        int n = a.size();

        boolean[] used = new boolean[n];

        Collections.sort(a, Collections.reverseOrder());
        Collections.sort(b, Collections.reverseOrder());

        int idxA = 0, idxB = 0;
        while (idxB < n) {
            if (a.get(idxA) > b.get(idxB)) {
                idxA++;
                idxB++;
                maxWinNum++;
            } else {
                idxB++;
            }
        }

        if (maxWinNum == 0) {
            System.out.println(ans);
        } else {
            dfs(a, b, 0, n, 0, used);
            System.out.println(ans);
        }
    }
}

C++

#include <iostream>
#include <sstream>
#include <vector>
#include <algorithm>

using namespace std;

int maxWinNum = 0;
int ans = 0;

void dfs(vector<int>& a, vector<int>& b, int idxB, int n, int winNum, vector<bool>& used) {
    int restNotUsed = 0;
    for (bool val : used) {
        if (!val) {
            restNotUsed++;
        }
    }
    if (maxWinNum > winNum + restNotUsed) {
        return;
    }
    if (winNum == maxWinNum) {
        int fact = 1;
        for (int i = 1; i <= restNotUsed; ++i) {
            fact *= i;
        }
        ans += fact;
        return;
    }
    for (int idxA = 0; idxA < n; ++idxA) {
        if (!used[idxA]) {
            used[idxA] = true;
            dfs(a, b, idxB + 1, n, winNum + (a[idxA] > b[idxB] ? 1 : 0), used);
            used[idxA] = false;
        }
    }
}

int main() {
    vector<int> a, b;
    string input;
    
    getline(cin, input);
    stringstream stream(input);
    int num;
    while (stream >> num) {
        a.push_back(num);
    }
    
    getline(cin, input);
    stringstream stream2(input);
    while (stream2 >> num) {
        b.push_back(num);
    }
    
    int n = a.size();
    vector<bool> used(n, false);

    sort(a.begin(), a.end(), greater<int>());
    sort(b.begin(), b.end(), greater<int>());

    int idxA = 0, idxB = 0;
    while (idxB < n) {
        if (a[idxA] > b[idxB]) {
            idxA++;
            idxB++;
            maxWinNum++;
        } else {
            idxB++;
        }
    }

    if (maxWinNum == 0) {
        cout << ans << endl;
    } else {
        dfs(a, b, 0, n, 0, used);
        cout << ans << endl;
    }
    return 0;
}

时空复杂度

时间复杂度:O(N*N!)。由于剪枝的存在,这是一个宽松的上界。状态树的深度最大为N,一共有N!种不同的排列。

空间复杂度:O(N)


华为OD算法/大厂面试高频题算法练习冲刺训练

  • 华为OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务100+同学成功上岸!

  • 课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化

  • 每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!

  • 60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁

  • 可上全网独家的欧弟OJ系统练习华子OD、大厂真题

  • 可查看链接 大厂真题汇总 & OD真题汇总(持续更新)

  • 绿色聊天软件戳 od1336了解更多

  • 24
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值