校招算法笔面试 | 校招笔面试真题-小红的有序数组

校招算法笔面试真题解析

题目## 题目

题目链接

题目链接

小红的有序数组

题目描述

小红有一个长度为 n n n 的排列,她每次可以选择两个数 a i a_i ai a j a_j aj,要求 a i a_i ai a j a_j aj 的奇偶性相同,然后交换这两个数。问最少需要多少次操作才能使得数组变成有序的,如果不能变的有序,输出 -1。

输入:

  • 第一行输入一个整数 n n n,表示数组的长度
  • 第二行输入 n n n 个整数 a i a_i ai,表示数组的元素

输出:

  • 如果能变成有序的,输出最少需要的操作次数,否则输出 -1

解题思路

这是一个贪心问题,可以通过以下步骤解决:

  1. 关键发现:

    • 只能交换相同奇偶性的数字
    • 这意味着奇数和偶数的相对位置不能改变
    • 最终数组要升序排列
  2. 解题策略:

    • 分别记录奇数和偶数的位置
    • 检查奇数和偶数的相对位置是否能满足最终升序要求
    • 如果可以,计算需要的最小交换次数
  3. 具体步骤:

    • 分别提取奇数序列和偶数序列
    • 检查每个序列内部是否可以通过交换变为有序
    • 计算每个序列内部需要的最小交换次数
    • 如果不能变为有序,输出-1

代码

#include <bits/stdc++.h>
using namespace std;

// 计算序列变为有序的最小交换次数
int minSwaps(vector<int>& arr) {
    int n = arr.size();
    vector<pair<int, int>> pos(n);
    for(int i = 0; i < n; i++) {
        pos[i] = {arr[i], i};
    }
    sort(pos.begin(), pos.end());
    
    vector<bool> vis(n, false);
    int ans = 0;
    
    for(int i = 0; i < n; i++) {
        if(vis[i] || pos[i].second == i) continue;
        
        int cycle_size = 0;
        int j = i;
        while(!vis[j]) {
            vis[j] = true;
            j = pos[j].second;
            cycle_size++;
        }
        ans += cycle_size - 1;
    }
    return ans;
}

int main() {
    int n;
    cin >> n;
    vector<int> a(n);
    for(int i = 0; i < n; i++) {
        cin >> a[i];
    }
    
    vector<int> odd, even;
    vector<int> odd_pos, even_pos;
    
    // 分离奇数和偶数
    for(int i = 0; i < n; i++) {
        if(a[i] % 2) {
            odd.push_back(a[i]);
            odd_pos.push_back(i);
        } else {
            even.push_back(a[i]);
            even_pos.push_back(i);
        }
    }
    
    // 检查是否可能变为有序
    vector<int> sorted = a;
    sort(sorted.begin(), sorted.end());
    for(int i = 0; i < n; i++) {
        if((sorted[i] % 2) != (a[i] % 2)) {
            cout << -1 << endl;
            return 0;
        }
    }
    
    // 计算最小交换次数
    int ans = minSwaps(odd) + minSwaps(even);
    cout << ans << endl;
    
    return 0;
}
import java.util.*;

public class Main {
    // 计算序列变为有序的最小交换次数
    static int minSwaps(List<Integer> arr) {
        int n = arr.size();
        int[][] pos = new int[n][2];
        for(int i = 0; i < n; i++) {
            pos[i][0] = arr.get(i);
            pos[i][1] = i;
        }
        Arrays.sort(pos, (a, b) -> a[0] - b[0]);
        
        boolean[] vis = new boolean[n];
        int ans = 0;
        
        for(int i = 0; i < n; i++) {
            if(vis[i] || pos[i][1] == i) continue;
            
            int cycleSize = 0;
            int j = i;
            while(!vis[j]) {
                vis[j] = true;
                j = pos[j][1];
                cycleSize++;
            }
            ans += cycleSize - 1;
        }
        return ans;
    }
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        List<Integer> a = new ArrayList<>();
        for(int i = 0; i < n; i++) {
            a.add(sc.nextInt());
        }
        
        List<Integer> odd = new ArrayList<>();
        List<Integer> even = new ArrayList<>();
        
        // 分离奇数和偶数
        for(int x : a) {
            if(x % 2 == 1) odd.add(x);
            else even.add(x);
        }
        
        // 检查是否可能变为有序
        List<Integer> sorted = new ArrayList<>(a);
        Collections.sort(sorted);
        for(int i = 0; i < n; i++) {
            if(sorted.get(i) % 2 != a.get(i) % 2) {
                System.out.println(-1);
                return;
            }
        }
        
        // 计算最小交换次数
        int ans = minSwaps(odd) + minSwaps(even);
        System.out.println(ans);
    }
}
def min_swaps(arr):
    n = len(arr)
    pos = list(enumerate(arr))  # (index, value)
    pos.sort(key=lambda x: x[1])
    
    vis = [False] * n
    ans = 0
    
    for i in range(n):
        if vis[i] or pos[i][0] == i:
            continue
            
        cycle_size = 0
        j = i
        while not vis[j]:
            vis[j] = True
            j = pos[j][0]
            cycle_size += 1
        ans += cycle_size - 1
    
    return ans

n = int(input())
a = list(map(int, input().split()))

# 分离奇数和偶数
odd = []
even = []
for x in a:
    if x % 2:
        odd.append(x)
    else:
        even.append(x)

# 检查是否可能变为有序
sorted_a = sorted(a)
for x, y in zip(sorted_a, a):
    if x % 2 != y % 2:
        print(-1)
        exit()

# 计算最小交换次数
ans = min_swaps(odd) + min_swaps(even)
print(ans)

算法及复杂度

  • 算法:贪心 + 最小交换次数
  • 时间复杂度: O ( n log ⁡ n ) \mathcal{O}(n \log n) O(nlogn) - 主要来自排序
  • 空间复杂度: O ( n ) \mathcal{O}(n) O(n) - 需要存储奇偶数组和辅助数组

题目链接

题目链接

小红的最大中位数

题目描述

小红拿到了一个数组,她准备选择一个子序列,使得该子序列的中位数尽可能大。小红想知道,一共有多少种方案?

  • 奇数长度的子序列中位数为中间的那个数
  • 偶数长度的子序列中位数为中间两个数的平均数

输入:

  • 第一行输入一个正整数 n n n,代表数组大小
  • 第二行输入 n n n 个正整数,代表小红拿到的数组

输出:

  • 一个整数,代表选择的方案数(对 10 9 + 7 10^9+7 109+7 取模)

解题思路

这是一个组合计数问题,可以通过以下步骤解决:

  1. 关键发现:

    • 要使子序列的中位数最大,那么中位数一定是原数组中的最大值
    • 只需要统计以最大值为中位数的所有子序列数量
  2. 预处理:

    • 对数组排序,找到最大值
    • 统计小于最大值的数的个数 cnt
    • 统计等于最大值的数的个数 num
  3. 计算方案数:

    • 对于每个合法的子序列,它必须包含至少一个最大值
    • 使用组合数计算:
      • 先计算选择小于最大值的数的方案数(组合数的前缀和)
      • 再计算选择最大值的方案数
      • 两者相乘得到总方案数

代码

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int MOD = 1e9 + 7;

// 快速幂,计算 (a^b) % mod
LL qpow(LL a, LL b) {
    LL res = 1;
    while (b) {
        if (b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}

int main() {
    int n;
    cin >> n;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    sort(a.begin(), a.end());
    
    // 统计最大值的个数和其他数的个数
    int cnt = 0, num = 0;
    for (int i = 1; i <= n; i++) {
        if (a[i] != a[n]) cnt++;
        else num++;
    }
    
    // 计算组合数的前缀和
    vector<LL> comb(n + 1), sum_comb(n + 1);
    sum_comb[0] = comb[0] = 1;
    for (int i = 1; i <= n; i++) {
        if (i <= cnt) comb[i] = (comb[i-1] * (cnt - i + 1) % MOD) * qpow(i, MOD - 2) % MOD;
        sum_comb[i] = (sum_comb[i-1] + comb[i]) % MOD;
    }
    
    // 计算最大值的组合数
    vector<LL> D(num + 1);
    D[0] = 1;
    LL ans = 0;
    for (int i = 1; i <= num; i++) {
        D[i] = (D[i-1] * (num - i + 1) % MOD) * qpow(i, MOD - 2) % MOD;
        ans = (ans + D[i] * sum_comb[i-1]) % MOD;
    }
    
    cout << ans << endl;
    return 0;
}
import java.util.*;

public class Main {
    static final int MOD = 1000000007;
    
    static long qpow(long a, long b) {
        long res = 1;
        while (b > 0) {
            if ((b & 1) == 1) res = res * a % MOD;
            a = a * a % MOD;
            b >>= 1;
        }
        return res;
    }
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] a = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            a[i] = sc.nextInt();
        }
        Arrays.sort(a);
        
        // 统计最大值的个数和其他数的个数
        int cnt = 0, num = 0;
        for (int i = 1; i <= n; i++) {
            if (a[i] != a[n]) cnt++;
            else num++;
        }
        
        // 计算组合数的前缀和
        long[] comb = new long[n + 1];
        long[] sum_comb = new long[n + 1];
        sum_comb[0] = comb[0] = 1;
        for (int i = 1; i <= n; i++) {
            if (i <= cnt) comb[i] = (comb[i-1] * (cnt - i + 1) % MOD) * qpow(i, MOD - 2) % MOD;
            sum_comb[i] = (sum_comb[i-1] + comb[i]) % MOD;
        }
        
        // 计算最大值的组合数
        long[] D = new long[num + 1];
        D[0] = 1;
        long ans = 0;
        for (int i = 1; i <= num; i++) {
            D[i] = (D[i-1] * (num - i + 1) % MOD) * qpow(i, MOD - 2) % MOD;
            ans = (ans + D[i] * sum_comb[i-1]) % MOD;
        }
        
        System.out.println(ans);
    }
}
MOD = 10**9 + 7
n = int(input())
a = [0] + list(map(int, input().split()))
a.sort()

# 统计最大值的个数和其他数的个数
cnt = num = 0
for i in range(1, n + 1):
    if a[i] != a[n]:
        cnt += 1
    else:
        num += 1

# 计算组合数的前缀和
comb = [0] * (n + 1)
sum_comb = [0] * (n + 1)
sum_comb[0] = comb[0] = 1
for i in range(1, n + 1):
    if i <= cnt:
        comb[i] = (comb[i-1] * (cnt - i + 1) % MOD) * pow(i, MOD - 2, MOD) % MOD
    sum_comb[i] = (sum_comb[i-1] + comb[i]) % MOD

# 计算最大值的组合数
D = [0] * (num + 1)
D[0] = 1
ans = 0
for i in range(1, num + 1):
    D[i] = (D[i-1] * (num - i + 1) % MOD) * pow(i, MOD - 2, MOD) % MOD
    ans = (ans + D[i] * sum_comb[i-1]) % MOD

print(ans)

算法及复杂度

  • 算法:组合数学 + 快速幂
  • 时间复杂度: O ( n log ⁡ n ) \mathcal{O}(n \log n) O(nlogn) - 主要来自排序和组合数计算
  • 空间复杂度: O ( n ) \mathcal{O}(n) O(n) - 需要存储组合数数组和前缀和数组
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值