跳转至

题解:P12289 [蓝桥杯 2024 国 Java A] 修改数位

\(\mathbf{upd\ 2025/6/13}\)减少了一些废话,精简了文章内容。

思路与算法

题目大意

题目要求我们找到一个长度为 \(10\) 的子串,使得该子串包含从 \(0\)\(9\) 的所有数字,且每个数字恰好出现一次。

思路

为了满足要求,我们可以修改原数的某些数字,但需要最小化修改的代价(即修改的数字差值之和)。同时,修改后的数仍需满足没有前导零的限制。

不是我说,我的算法倒还挺暴力的。

我的做法是现选出一个字串,然后再使用类似于桶排序的方法统计数字个数。由于这里字串长度是固定的,且是由左侧向右侧扫描,就是滑动窗口的写法。

每次桶排序统计结束后,我们能够得到哪些数字被多算了,哪些数字被少算了,这时,我们不断保证被替换的的数字与替换后的数字之差最小即可。

这个时候要注意两个特殊情况: 1. 原串长度小于 \(10\),也就是说根本不可能出现 \(10\) 个不同的数字。 2. 当扫描第一个长度为 \(10\) 的字串时,要注意第一个数不能被修改为 \(0\)(即不能有前导零)。

算法

由算法标签可知

从上面的推导,我们能知道要写以下几个算法:

1.滑动窗口

遍历长度为 \(10\) 的所有子串。其中,对每个子串,计算其是否已经是一个排列。如果是,则代价为 \(0\),直接返回结果。如果不是,则计算将其变为一个排列的最小代价。

tip:这里不是真正意义上的的双指针,实际上是单指针。

2.桶排序

统计每个数字的出现次数。

这里我们还做了一些其他的优化(类似于 KMP?):

1.减少重复计算

滑动窗口可以通过增量更新频率数组(就是当它往后移动的时候,直接加减新进入窗口中的数字与离开窗口中的数字),避免重复计算。

2.排序

对于多余和缺失的数字,可以排序后匹配,确保代价最小。

代码

这里献上自己的代码(Python 超时了,就不放出来了):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <bits/stdc++.h>
using namespace std;
int main() {
    string m_str;
    cin >> m_str;
    int n = m_str.size();
    if (n < 10) {
        cout << -1 << endl;
        return 0;
    }
    int min_cost = INT_MAX;
    // 滑动窗口遍历所有长度为 10 的子串
    for (int i = 0; i <= n - 10; i++) {
        string window = m_str.substr(i, 10);
        int freq[10] = {0};
        // 统计子串中每个数字的频率
        for (char d : window) {
            freq[d - '0']++;
        }
        // 检查是否已经是一个排列
        bool is_permutation = true;
        for (int d = 0; d <= 9; d++) {
            if (freq[d] != 1) {
                is_permutation = false;
                break;
            }
        }
        // 如果是排列,直接输出结果
        if (is_permutation) {
            cout << 0 << endl;
            return 0;
        }
        // 记录多余和缺失的数字
        char dup[10], miss[10];
        int dup_cnt = 0, miss_cnt = 0;

        for (int d = 0; d <= 9; d++) {
            int cnt = freq[d];
            if (cnt > 1) {
                for (int j = 1; j < cnt; j++) {
                    dup[dup_cnt++] = d + '0';
                }
            } else if (cnt == 0) {
                miss[miss_cnt++] = d + '0';
            }
        }
        // 计算代价
        bool mnz = (window[0] == '0');
        // 是否有前导零
        int cost = 0;
        if (mnz) {
            // 特殊处理前导零
            bool found = false;
            for (int j = 0; j < dup_cnt; j++) {
                if (dup[j] == window[0]) {
                    char best_d = '0';
                    int min_diff = INT_MAX;
                    for (int k = 0; k < miss_cnt; k++) {
                        if (miss[k] != '0') {
                            int diff = abs(miss[k] - window[0]);
                            if (diff < min_diff) {
                                min_diff = diff;
                                best_d = miss[k];
                            }
                        }
                    }
                    if (best_d == '0') {
                        cost = INT_MAX;
                        break;
                    }
                    cost += min_diff;
                    found = true;
                    break;
                }
            }
            if (!found) continue;
        }
        // 匹配多余和缺失的数字
        sort(dup, dup + dup_cnt);
        sort(miss, miss + miss_cnt);
        for (int j = 0; j < dup_cnt; j++) {
            cost += abs(miss[j] - dup[j]);
        }
        // 更新最小代价
        min_cost = min(min_cost, cost);
    }
    cout << min_cost << endl;
    return 0;
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
using System;
using System.Linq;

class Program
{
    static void Main()
    {
        string m_str = Console.ReadLine();
        int n = m_str.Length; -1
        if (n < 10)
        {
            Console.WriteLine(-1);
            return;
        }
        int min_cost = int.MaxValue;
        for (int i = 0; i <= n - 10; i++)
        {
            string window = m_str.Substring(i, 10);
            int[] freq = new int[10];
            // 统计子串中每个数字的频率
            foreach (char d in window)
            {
                freq[d - '0']++;
            }
            // 检查是否已经是一个排列
            bool is_permutation = true;
            for (int d = 0; d <= 9; d++)
            {
                if (freq[d] != 1)
                {
                    is_permutation = false;
                    break;
                }
            }
            // 如果是排列,直接输出结果
            if (is_permutation)
            {
                Console.WriteLine(0);
                return;
            }
            // 记录多余和缺失的数字
            char[] dup = new char[10];
            char[] miss = new char[10];
            int dup_cnt = 0, miss_cnt = 0;
            for (int d = 0; d <= 9; d++)
            {
                int cnt = freq[d];
                if (cnt > 1)
                {
                    for (int j = 1; j < cnt; j++)
                    {
                        dup[dup_cnt++] = (char)(d + '0');
                    }
                }
                else if (cnt == 0)
                {
                    miss[miss_cnt++] = (char)(d + '0');
                }
            }
            // 计算代价
            bool mnz = (window[0] == '0'); // 是否有前导零
            int cost = 0;
            if (mnz)
            {
                // 特殊处理前导零
                bool found = false;
                for (int j = 0; j < dup_cnt; j++)
                {
                    if (dup[j] == window[0])
                    {
                        char best_d = '0';
                        int min_diff = int.MaxValue;
                        for (int k = 0; k < miss_cnt; k++)
                        {
                            if (miss[k] != '0')
                            {
                                int diff = Math.Abs(miss[k] - window[0]);
                                if (diff < min_diff)
                                {
                                    min_diff = diff;
                                    best_d = miss[k];
                                }
                            }
                        }
                        if (best_d == '0')
                        {
                            cost = int.MaxValue;
                            break;
                        }
                        cost += min_diff;
                        found = true;
                        break;
                    }
                }
                if (!found) continue;
            }
            // 匹配多余和缺失的数字
            Array.Sort(dup, 0, dup_cnt);
            Array.Sort(miss, 0, miss_cnt);
            for (int j = 0; j < dup_cnt; j++)
            {
                cost += Math.Abs(miss[j] - dup[j]);
            }
            // 更新最小代价
            min_cost = Math.Min(min_cost, cost);
        }
        Console.WriteLine(min_cost);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String m_str = scanner.nextLine();
        int n = m_str.length();
        if (n < 10) {
            System.out.println(-1);
            return;
        }
        int min_cost = Integer.MAX_VALUE;
        for (int i = 0; i <= n - 10; i++) {
            String window = m_str.substring(i, i + 10);
            int[] freq = new int[10];
            // 统计子串中每个数字的频率
            for (char d : window.toCharArray()) {
                freq[d - '0']++;
            }
            // 检查是否已经是一个排列
            boolean is_permutation = true;
            for (int d = 0; d <= 9; d++) {
                if (freq[d] != 1) {
                    is_permutation = false;
                    break;
                }
            }
            // 如果是排列,直接输出结果
            if (is_permutation) {
                System.out.println(0);
                return;
            }
            // 记录多余和缺失的数字
            char[] dup = new char[10];
            char[] miss = new char[10];
            int dup_cnt = 0, miss_cnt = 0;
            for (int d = 0; d <= 9; d++) {
                int cnt = freq[d];
                if (cnt > 1) {
                    for (int j = 1; j < cnt; j++) {
                        dup[dup_cnt++] = (char) (d + '0');
                    }
                } else if (cnt == 0) {
                    miss[miss_cnt++] = (char) (d + '0');
                }
            }
            // 计算代价
            boolean mnz = (window.charAt(0) == '0'); // 是否有前导零
            int cost = 0;
            if (mnz) {
                // 特殊处理前导零
                boolean found = false;
                for (int j = 0; j < dup_cnt; j++) {
                    if (dup[j] == window.charAt(0)) {
                        char best_d = '0';
                        int min_diff = Integer.MAX_VALUE;
                        for (int k = 0; k < miss_cnt; k++) {
                            if (miss[k] != '0') {
                                int diff = Math.abs(miss[k] - window.charAt(0));
                                if (diff < min_diff) {
                                    min_diff = diff;
                                    best_d = miss[k];
                                }
                            }
                        }
                        if (best_d == '0') {
                            cost = Integer.MAX_VALUE;
                            break;
                        }
                        cost += min_diff;
                        found = true;
                        break;
                    }
                }
                if (!found) continue;
            }
            // 匹配多余和缺失的数字
            Arrays.sort(dup, 0, dup_cnt);
            Arrays.sort(miss, 0, miss_cnt);
            for (int j = 0; j < dup_cnt; j++) {
                cost += Math.abs(miss[j] - dup[j]);
            }
            // 更新最小代价
            min_cost = Math.min(min_cost, cost);
        }
        System.out.println(min_cost);
    }
}