校招算法笔面试 | 校招笔面试真题-冒泡排序

题目## 题目

题目链接

解题思路

本题可以使用动态规划来解决。核心思路是计算通过翻转操作能减少的最大逆序对数量。

关键点

  1. 状态定义:dp[i][k] 表示数组前i个元素进行不超过k次翻转操作后能减少的最大逆序对数量
  2. 最终答案:原数组的总逆序对数 - dp[n-1][k]
  3. 对于每个位置 i i i 和操作次数 k k k,需要枚举所有可能的翻转区间 [ t , i ] [t,i] [t,i]

解决方案

  1. 计算原数组的总逆序对数
  2. 使用动态规划计算最大可减少的逆序对数
  3. 状态转移时考虑是否翻转当前区间

代码

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

class Solution {
private:
    // 计算区间[l,r]内的逆序对数量
    int countInversions(vector<int>& arr, int l, int r, bool needReverse) {
        vector<int> temp = arr;
        if (needReverse) {
            reverse(temp.begin() + l, temp.begin() + r + 1);
        }
        
        int count = 0;
        for (int i = l; i <= r; i++) {
            for (int j = i + 1; j <= r; j++) {
                if (temp[i] > temp[j]) {
                    count++;
                }
            }
        }
        return count;
    }

public:
    int solve(vector<int>& arr, int k) {
        int n = arr.size();
        vector<vector<int>> dp(n, vector<int>(k + 1, 0));
        
        // 计算dp数组
        for (int i = 1; i < n; i++) {
            for (int j = 1; j <= k; j++) {
                // 不进行新的翻转操作
                dp[i][j] = dp[i-1][j];
                
                // 尝试所有可能的翻转区间[t,i]
                for (int t = 0; t <= i; t++) {
                    int diff = countInversions(arr, t, i, false) - 
                             countInversions(arr, t, i, true);
                    if (diff < 0) diff = 0;
                    
                    int prevValue = (t > 0) ? dp[t-1][j-1] : 0;
                    dp[i][j] = max(dp[i][j], prevValue + diff);
                }
            }
        }
        
        // 计算原数组的总逆序对数
        int totalInversions = countInversions(arr, 0, n-1, false);
        
        // 返回最终结果
        return totalInversions - dp[n-1][k];
    }
};

int main() {
    int n, k;
    cin >> n >> k;
    
    vector<int> arr(n);
    for (int i = 0; i < n; i++) {
        cin >> arr[i];
    }
    
    Solution solution;
    cout << solution.solve(arr, k) << endl;
    
    return 0;
}
import java.util.*;

public class Main {
    static class Solution {
        // 计算区间[l,r]内的逆序对数量
        private int countInversions(int[] arr, int l, int r, boolean needReverse) {
            int[] temp = arr.clone();
            if (needReverse) {
                reverse(temp, l, r);
            }
            
            int count = 0;
            for (int i = l; i <= r; i++) {
                for (int j = i + 1; j <= r; j++) {
                    if (temp[i] > temp[j]) {
                        count++;
                    }
                }
            }
            return count;
        }
        
        private void reverse(int[] arr, int l, int r) {
            while (l < r) {
                int temp = arr[l];
                arr[l] = arr[r];
                arr[r] = temp;
                l++;
                r--;
            }
        }
        
        public int solve(int[] arr, int k) {
            int n = arr.length;
            int[][] dp = new int[n][k + 1];
            
            // 计算dp数组
            for (int i = 1; i < n; i++) {
                for (int j = 1; j <= k; j++) {
                    // 不进行新的翻转操作
                    dp[i][j] = dp[i-1][j];
                    
                    // 尝试所有可能的翻转区间[t,i]
                    for (int t = 0; t <= i; t++) {
                        int diff = countInversions(arr, t, i, false) - 
                                 countInversions(arr, t, i, true);
                        if (diff < 0) diff = 0;
                        
                        int prevValue = (t > 0) ? dp[t-1][j-1] : 0;
                        dp[i][j] = Math.max(dp[i][j], prevValue + diff);
                    }
                }
            }
            
            // 计算原数组的总逆序对数
            int totalInversions = countInversions(arr, 0, n-1, false);
            
            // 返回最终结果
            return totalInversions - dp[n-1][k];
        }
    }
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int k = sc.nextInt();
        
        int[] arr = new int[n];
        for (int i = 0; i < n; i++) {
            arr[i] = sc.nextInt();
        }
        
        Solution solution = new Solution();
        System.out.println(solution.solve(arr, k));
        sc.close();
    }
}
class Solution:
    def count_inversions(self, arr, l, r, need_reverse):
        """计算区间[l,r]内的逆序对数量"""
        temp = arr.copy()
        if need_reverse:
            temp[l:r+1] = temp[l:r+1][::-1]
        
        count = 0
        for i in range(l, r + 1):
            for j in range(i + 1, r + 1):
                if temp[i] > temp[j]:
                    count += 1
        return count
    
    def solve(self, arr, k):
        n = len(arr)
        dp = [[0] * (k + 1) for _ in range(n)]
        
        # 计算dp数组
        for i in range(1, n):
            for j in range(1, k + 1):
                # 不进行新的翻转操作
                dp[i][j] = dp[i-1][j]
                
                # 尝试所有可能的翻转区间[t,i]
                for t in range(i + 1):
                    diff = self.count_inversions(arr, t, i, False) - \
                          self.count_inversions(arr, t, i, True)
                    if diff < 0:
                        diff = 0
                    
                    prev_value = dp[t-1][j-1] if t > 0 else 0
                    dp[i][j] = max(dp[i][j], prev_value + diff)
        
        # 计算原数组的总逆序对数
        total_inversions = self.count_inversions(arr, 0, n-1, False)
        
        # 返回最终结果
        return total_inversions - dp[n-1][k]

def main():
    n, k = map(int, input().split())
    arr = list(map(int, input().split()))
    
    solution = Solution()
    print(solution.solve(arr, k))

if __name__ == "__main__":
    main()

算法及复杂度

  • 算法:动态规划
  • 时间复杂度: O ( n 3 ∗ k ) \mathcal{O}(n^3 * k) O(n3k),其中 n n n 是数组长度, k k k 是最大操作次数
  • 空间复杂度: O ( n ∗ k ) \mathcal{O}(n * k) O(nk),用于存储dp数组

题目链接

解题思路

  1. 题目分析:

    • n n n 个灯泡从左到右排列
    • Alice先手,Bob后手
    • 每次操作必须选择一个亮着的灯泡
    • 选中的灯泡及其右边的灯泡状态都会改变
    • 所有灯泡熄灭时游戏结束
  2. 解题策略:

    • 关键观察:最后一个灯泡的状态决定胜负
    • 如果最后一个灯泡是亮的(1),Alice必胜
    • 如果最后一个灯泡是灭的(0),Bob必胜

代码

#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;
    
    // 读入灯泡状态
    int last_bulb;
    for(int i = 0; i < n; i++) {
        int state;
        cin >> state;
        if(i == n-1) last_bulb = state;
    }
    
    // 根据最后一个灯泡状态判断胜负
    cout << (last_bulb ? "Alice" : "Bob") << endl;
    
    return 0;
}
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        
        // 读入灯泡状态
        int lastBulb = 0;
        for(int i = 0; i < n; i++) {
            int state = sc.nextInt();
            if(i == n-1) lastBulb = state;
        }
        
        // 根据最后一个灯泡状态判断胜负
        System.out.println(lastBulb == 1 ? "Alice" : "Bob");
    }
}
n = int(input())
bulbs = list(map(int, input().split()))

# 根据最后一个灯泡状态判断胜负
print("Alice" if bulbs[-1] == 1 else "Bob")

算法及复杂度

  • 算法:数学推理
  • 时间复杂度: O ( n ) \mathcal{O}(n) O(n) - 只需要读取输入
  • 空间复杂度: O ( 1 ) \mathcal{O}(1) O(1) - 只需要存储最后一个灯泡的状态
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值