#P2647. 消消乐算法设计
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 273
            Accepted: 56
            Difficulty: 5
            
          
          
          
                       所属公司 : 
                              华为
                                
            
                        
              时间 :2025年1月8日-留学生
                              
                      
          
 
- 
                        算法标签>双指针          
 
消消乐算法设计
题解
题面描述
设计一种消消乐的算法,使用数字代表不同类型的格子。拥有一种道具,可以对某个格子进行加一或减一的变化。给定一个整数数组 nums 表示当前的格子状态,以及一个整数 k 表示道具的数量。最多可以使用 k 个道具,最终返回同类型格子最多的个数作为最终得分。
思路
通过排序数组并应用滑动窗口技术,选择一个目标值(通常为中位数),计算将窗口内所有数字调整到该目标值所需的操作次数,确保总操作不超过k次。使用前缀和和双指针方法优化计算过程,逐步扩大窗口以找到包含最多相同元素的最大子集,从而实现高效且准确的优化目标。
具体步骤如下:
- 
排序:首先对数组
nums进行排序,这样相同或接近的数字会聚集在一起,便于计算操作次数。 - 
前缀和:计算数组的前缀和,以便快速计算任意区间内数字的总和。
 - 
滑动窗口:使用滑动窗口的方法,维护一个窗口
[left, right],窗口内的数字通过操作可以全部变为窗口内的中位数。计算将窗口内所有数字变为中位数所需的操作次数,如果超过k,则缩小窗口左边界。 - 
更新结果:在遍历过程中,不断更新最大的窗口大小,即为最终结果。
 
cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    vector<ll> nums;
    ll num;
    
    // 读取第一行的 nums 数组
    string line1;
    getline(cin, line1);
    stringstream ss1(line1);
    while(ss1 >> num){
        nums.push_back(num);
    }
    
    // 读取第二行的 k
    string line2;
    getline(cin, line2);
    ll k = 0;
    if(!line2.empty()){
        stringstream ss2(line2);
        ss2 >> k;
    }
    
    // 排序
    sort(nums.begin(), nums.end());
    
    int n = nums.size();
    // 计算前缀和
    vector<ll> prefix_sum(n +1, 0);
    for(int i=0;i<n;i++) prefix_sum[i+1] = prefix_sum[i] + nums[i];
    
    ll max_len =1;
    int left =0;
    for(int right=0; right<n; right++){
        // 计算当前窗口 [left, right] 的中位数
        int mid = left + (right - left) /2;
        ll m = nums[mid];
        
        // 计算左半部分的操作次数
        ll sum_left = m * (mid - left) - (prefix_sum[mid] - prefix_sum[left]);
        
        // 计算右半部分的操作次数
        ll sum_right = (prefix_sum[right+1] - prefix_sum[mid+1]) - m * (right - mid);
        
        // 总操作次数
        ll total = sum_left + sum_right;
        
        // 如果操作次数超过 k,移动左指针
        while(total >k){
            left++;
            if(left > mid){
                mid = left + (right - left) /2;
                m = nums[mid];
            }
            else{
                mid = left + (right - left) /2;
                m = nums[mid];
            }
            sum_left = m * (mid - left) - (prefix_sum[mid] - prefix_sum[left]);
            sum_right = (prefix_sum[right+1] - prefix_sum[mid+1]) - m * (right - mid);
            total = sum_left + sum_right;
        }
        // 更新最大长度
        max_len = max(max_len, (ll)(right - left +1));
    }
    
    cout << max_len;
}
python
import sys
def main():
    import sys
    nums = []
    
    # 读取第一行的 nums 数组
    line1 = sys.stdin.readline()
    nums = list(map(int, line1.strip().split()))
    
    # 读取第二行的 k
    line2 = sys.stdin.readline()
    if line2:
        k = int(line2.strip())
    else:
        k =0
    
    nums.sort()
    n = len(nums)
    prefix_sum = [0]*(n+1)
    for i in range(n):
        prefix_sum[i+1] = prefix_sum[i] + nums[i]
    max_len =1
    left=0
    for right in range(n):
        # 计算当前窗口 [left, right] 的中位数
        mid = left + (right - left) //2
        m = nums[mid]
        # 计算左半部分的操作次数
        sum_left = m * (mid - left) - (prefix_sum[mid] - prefix_sum[left])
        # 计算右半部分的操作次数
        sum_right = (prefix_sum[right+1] - prefix_sum[mid+1]) - m * (right - mid)
        # 总操作次数
        total = sum_left + sum_right
        # 如果操作次数超过 k,移动左指针
        while total >k:
            left +=1
            if left > mid:
                mid = left + (right - left) //2
                m = nums[mid]
            else:
                mid = left + (right - left) //2
                m = nums[mid]
            sum_left = m * (mid - left) - (prefix_sum[mid] - prefix_sum[left])
            sum_right = (prefix_sum[right+1] - prefix_sum[mid+1]) - m * (right - mid)
            total = sum_left + sum_right
        # 更新最大长度
        max_len = max(max_len, right - left +1)
    print(max_len)
if __name__ == "__main__":
    main()
java
import java.util.*;
import java.io.*;
public class Main {
    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        
        // 读取第一行的 nums 数组
        String line1 = br.readLine();
        String[] parts1 = line1.trim().split("\\s+");
        ArrayList<Long> numsList = new ArrayList<>();
        for(String s : parts1){
            if(!s.isEmpty()){
                numsList.add(Long.parseLong(s));
            }
        }
        
        // 读取第二行的 k
        String line2 = br.readLine();
        long k =0;
        if(line2 != null && !line2.trim().isEmpty()){
            k = Long.parseLong(line2.trim());
        }
        
        // 将 nums 转为数组并排序
        int n = numsList.size();
        long[] nums = new long[n];
        for(int j=0; j<n; j++) nums[j] = numsList.get(j);
        Arrays.sort(nums);
        
        // 计算前缀和
        long[] prefix_sum = new long[n+1];
        for(int j=0; j<n; j++) prefix_sum[j+1] = prefix_sum[j] + nums[j];
        
        long max_len =1;
        int left=0;
        for(int right=0; right<n; right++){
            // 计算当前窗口 [left, right] 的中位数
            int mid = left + (right - left) /2;
            long m = nums[mid];
            // 计算左半部分的操作次数
            long sum_left = m * (mid - left) - (prefix_sum[mid] - prefix_sum[left]);
            // 计算右半部分的操作次数
            long sum_right = (prefix_sum[right+1] - prefix_sum[mid+1]) - m * (right - mid);
            // 总操作次数
            long total = sum_left + sum_right;
            // 如果操作次数超过 k,移动左指针
            while(total >k){
                left++;
                if(left > mid){
                    mid = left + (right - left) /2;
                    m = nums[mid];
                }
                else{
                    mid = left + (right - left) /2;
                    m = nums[mid];
                }
                sum_left = m * (mid - left) - (prefix_sum[mid] - prefix_sum[left]);
                sum_right = (prefix_sum[right+1] - prefix_sum[mid+1]) - m * (right - mid);
                total = sum_left + sum_right;
            }
            // 更新最大长度
            max_len = Math.max(max_len, (long)(right - left +1));
        }
        System.out.println(max_len);
    }
}
        题目内容
现在要设计一种消消乐的算法,我们用数字代表一种类型格子,有一种道且可以对某个格子进行加一或者减一的变化。 现在给你一个整数数组nums代表当前的格子状态和一个整数k代表道具数量。你最多可以使用k个道具,最终返回同类型格子最多的个数作为你的最终得分
输入描述
一个整数数组nums代表当前的格子状态
一个整数k代表道具数量
1<=nums.length<=105
1<=nums[i]<=109
0<=k<=109
输出描述
使用道具后同类型格子最多的个数
样例1
输入
1 2 6 4
3
输出
3
说明
有1,2,6,4四种格子
有3个道具可以使用
使用后最多可以有3个相同格子(2,2,6,2)
样例2
输入
1 2 3 4
0
输出
1
说明
有1,2,3,4四种格子
有0个道具可以使用
使用后最多可以有1个相同格子 (1,2,3,4)