(字典序问题) nyoj1542-最小字符串

你说的 “字典序问题” —— NYOJ1542《最小字符串》,一般是要对字符串进行某种变换,最后得到字典序最小的结果。


题目背景(一般套路)

给定一个字符串,可以进行某种操作(比如交换、旋转、插入等),要求在所有可能的结果中找到字典序最小的字符串。


常见解题思路

  • 贪心策略:优先选择字典序更小的字符或子串。
  • 双指针:维护两个索引,比较两侧的字符,决定选择哪边字符放入结果。
  • 使用栈:通过单调栈保持最小字典序。
  • KMP / 最小表示法:对旋转类问题,找到最小表示。

假设题目是“给定字符串,允许从左边或右边选择一个字符拼接到结果字符串末尾,求最终字典序最小的字符串”。


典型解法代码(Java)

 

import java.util.Scanner;

public class NYOJ1542_MinString {

public static void main(String[] args) {

Scanner sc = new Scanner(System.in);

int n = sc.nextInt();

String s = sc.next();

char[] chars = s.toCharArray();

int i = 0, j = n - 1;

StringBuilder result = new StringBuilder();

while (i <= j) {

boolean leftIsSmaller = false;

int left = i, right = j;

while (left <= right) {

if (chars[left] < chars[right]) {

leftIsSmaller = true;

break;

} else if (chars[left] > chars[right]) {

leftIsSmaller = false;

break;

}

left++;

right--;

}

if (leftIsSmaller) {

result.append(chars[i++]);

} else {

result.append(chars[j--]);

}

}

System.out.println(result.toString());

sc.close();

}

}

php

1.02 KB

© 菜鸟-创作你的创作


代码说明

  • 双指针分别从字符串两端开始比较。
  • 当左侧字典序较小时,选择左端字符,否则选择右端字符。
  • 直到指针相遇,构造出字典序最小的字符串。

好的!下面我帮你详细讲解这道题的双指针解法思路,为什么能保证得到字典序最小字符串,并给你附上 C++ 版本的完整代码示例。


题目简述(字典序最小字符串)

给定一个长度为 n 的字符串 s,你可以从字符串的两端取字符,依次组成一个新的字符串。
问:如何选择左右端字符,能使得最终组成的字符串字典序最小?


解题思路详解

为什么要比较两端字符?

  • 我们每次只能从左端或右端取字符。
  • 为了保证最终结果字典序最小,每次取字符时要优先选择更“字典序小”的那个端。

怎么比较?

  • 如果左端字符比右端字符小,当然先取左端。
  • 如果右端字符比左端字符小,先取右端。
  • 如果两端字符相等怎么办?

这时不能简单只选一端,需要继续往内层比较对应的字符(即从左端向右,从右端向左)逐个比较,直到能判断哪边字典序更小,或者中间遇到相等的字符一直到两指针相遇。


算法核心

  1. 定义两个指针:i(从左端开始),j(从右端开始)
  2. 在 i <= j 条件下:
    • 比较 s[i] 和 s[j]
    • 若不等,字典序小的端先取出字符,移动对应指针
    • 若相等,则用两个临时指针 l = ir = j 向中间推进,比较 s[l] 和 s[r],判断哪边字典序更小
  3. 将选择的字符依次拼接到结果字符串尾部

C++ 代码实现

 

#include <iostream>

#include <string>

using namespace std;

int main() {

int n;

cin >> n;

string s;

cin >> s;

int i = 0, j = n - 1;

string result;

while (i <= j) {

bool takeLeft = false;

int l = i, r = j;

// 继续向中间比较,直到找到不等字符或者指针相遇

while (l <= r) {

if (s[l] < s[r]) {

takeLeft = true;

break;

} else if (s[l] > s[r]) {

takeLeft = false;

break;

}

l++;

r--;

}

if (takeLeft) {

result += s[i++];

} else {

result += s[j--];

}

}

// 输出结果,每行输出不超过80个字符(题目常见要求)

for (size_t k = 0; k < result.size(); k++) {

cout << result[k];

if ((k + 1) % 80 == 0) cout << '\n';

}

cout << '\n';

return 0;

}

php

865 Bytes

© 菜鸟-创作你的创作


总结

  • 双指针向内推进,逐层比较,确保每次选字典序最小的边。
  • 解决了两端字符相等时的判定问题。
  • 代码效率 O(n2) 对一般数据量可接受,若数据量大可用字符串哈希等优化。

好嘞!我给你写一个详细注释的 C++ 代码版本,并加上用字符串哈希优化比较部分的思路,方便你理解和进一步优化。


带详细注释的 C++ 代码(普通双指针)

 

#include <iostream>

#include <string>

using namespace std;

int main() {

int n;

cin >> n;

string s;

cin >> s;

int i = 0, j = n - 1;

string result;

while (i <= j) {

bool takeLeft = false;

int l = i, r = j;

// 向中间比较,判断选择左边还是右边

while (l <= r) {

if (s[l] < s[r]) {

// 左边字符更小,取左边

takeLeft = true;

break;

} else if (s[l] > s[r]) {

// 右边字符更小,取右边

takeLeft = false;

break;

}

// 相等时继续向中间比较

l++;

r--;

}

if (takeLeft) {

result += s[i++];

} else {

result += s[j--];

}

}

// 按题目常见要求,每80个字符换行

for (size_t k = 0; k < result.size(); k++) {

cout << result[k];

if ((k + 1) % 80 == 0) cout << '\n';

}

cout << '\n';

return 0;

}

php

935 Bytes

© 菜鸟-创作你的创作


字符串哈希优化思路

  • 上述代码在最坏情况下两指针比较可能退化成 O(n2)
  • 用 滚动哈希 或 双哈希 预处理字符串,使任意区间比较 O(1)
  • 比较左侧子串 s[i..i+k] 和右侧子串 s[j−k..j] 时,通过哈希值判断大小,避免逐字符比较

哈希优化示例思路(伪代码)

 

// 预处理字符串哈希(前缀哈希)

// 比较s[i..i+len-1] 和 s[j-len+1..j]

// if hash(s[i..i+len-1]) < hash(s[j-len+1..j])

// 说明左侧字典序更小

// else

// 右侧更小或相等

// 利用二分查找最早不相等字符位置

php

161 Bytes

© 菜鸟-创作你的创作

好的!下面给你写一个基于 滚动哈希 + 二分比较 的 C++ 优化版本代码,解决两端字典序比较时的效率问题,适合大数据量。


代码说明

  • 使用一个哈希函数(取模+乘基数)预处理字符串前缀哈希
  • 利用二分查找比较两个子串的字典序
  • 最终用双指针和哈希加速判断每次该选左边还是右边字符

完整代码

 

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int MAXN = 100000 + 10;

const ll MOD1 = 1000000007;

const ll BASE1 = 131;

int n;

string s;

ll hash1[MAXN], power1[MAXN];

// 计算前缀哈希

void buildHash() {

hash1[0] = 0;

power1[0] = 1;

for (int i = 1; i <= n; i++) {

hash1[i] = (hash1[i - 1] * BASE1 + s[i - 1]) % MOD1;

power1[i] = (power1[i - 1] * BASE1) % MOD1;

}

}

// 取区间哈希值 [l,r],1-based索引

ll getHash(int l, int r) {

return (hash1[r] - (hash1[l - 1] * power1[r - l + 1]) % MOD1 + MOD1) % MOD1;

}

// 比较区间 s[l1..r1] 和 s[l2..r2] 的字典序,返回 true 表示左边更小

bool lessOrEqual(int l1, int r1, int l2, int r2) {

int len = min(r1 - l1 + 1, r2 - l2 + 1);

int low = 0, high = len;

while (low < high) {

int mid = (low + high + 1) / 2;

if (getHash(l1, l1 + mid - 1) == getHash(l2, l2 + mid - 1)) {

low = mid;

} else {

high = mid - 1;

}

}

// low是最大公共前缀长度

if (low == len) {

// 短的字符串是另一个的前缀,短的字典序更小

return (r1 - l1) <= (r2 - l2);

}

// 比较第一个不同字符

return s[l1 + low - 1] < s[l2 + low - 1];

}

int main() {

ios::sync_with_stdio(false);

cin.tie(nullptr);

cin >> n >> s;

buildHash();

int i = 1, j = n; // 1-based索引

string result;

while (i <= j) {

if (lessOrEqual(i, j, n - j + 1, n - i + 1)) {

result.push_back(s[i - 1]);

i++;

} else {

result.push_back(s[j - 1]);

j--;

}

}

// 每80字符换行

for (int k = 0; k < (int)result.size(); k++) {

cout << result[k];

if ((k + 1) % 80 == 0) cout << '\n';

}

cout << '\n';

return 0;

}

php

1.67 KB

© 菜鸟-创作你的创作


关键点说明

  • 预处理字符串 s 的前缀哈希 hash1[] 和幂次数组 power1[]
  • getHash(l,r) 用来快速计算子串哈希,时间 O(1)
  • lessOrEqual(l1,r1,l2,r2) 用二分查找最长公共前缀,再比较第一个不同字符,时间 O(log⁡n)
  • 字符串索引均用1-based方便哈希计算,转换时注意减1访问字符串 s

这个方案能将原本 O(n2) 字符逐个比较降低到 O(nlog⁡n),适合大数据量处理。

好的!这里给你加上双哈希版本,防止哈希碰撞,代码结构和单哈希版本类似,只是多了第二套哈希参数和比较时两个哈希值都相等才判定相等。


双哈希版本 C++ 代码

 

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int MAXN = 100000 + 10;

// 第一组哈希参数

const ll MOD1 = 1000000007;

const ll BASE1 = 131;

// 第二组哈希参数

const ll MOD2 = 1000000009;

const ll BASE2 = 137;

int n;

string s;

ll hash1_1[MAXN], power1_1[MAXN];

ll hash1_2[MAXN], power1_2[MAXN];

// 计算前缀哈希

void buildHash() {

hash1_1[0] = 0; power1_1[0] = 1;

hash1_2[0] = 0; power1_2[0] = 1;

for (int i = 1; i <= n; i++) {

hash1_1[i] = (hash1_1[i - 1] * BASE1 + s[i - 1]) % MOD1;

power1_1[i] = (power1_1[i - 1] * BASE1) % MOD1;

hash1_2[i] = (hash1_2[i - 1] * BASE2 + s[i - 1]) % MOD2;

power1_2[i] = (power1_2[i - 1] * BASE2) % MOD2;

}

}

// 取区间哈希值 [l,r],1-based索引

pair<ll,ll> getHash(int l, int r) {

ll res1 = (hash1_1[r] - (hash1_1[l - 1] * power1_1[r - l + 1]) % MOD1 + MOD1) % MOD1;

ll res2 = (hash1_2[r] - (hash1_2[l - 1] * power1_2[r - l + 1]) % MOD2 + MOD2) % MOD2;

return {res1, res2};

}

// 比较区间 s[l1..r1] 和 s[l2..r2] 的字典序,返回 true 表示左边更小

bool lessOrEqual(int l1, int r1, int l2, int r2) {

int len = min(r1 - l1 + 1, r2 - l2 + 1);

int low = 0, high = len;

while (low < high) {

int mid = (low + high + 1) / 2;

if (getHash(l1, l1 + mid - 1) == getHash(l2, l2 + mid - 1)) {

low = mid;

} else {

high = mid - 1;

}

}

if (low == len) {

return (r1 - l1) <= (r2 - l2);

}

return s[l1 + low - 1] < s[l2 + low - 1];

}

int main() {

ios::sync_with_stdio(false);

cin.tie(nullptr);

cin >> n >> s;

buildHash();

int i = 1, j = n;

string result;

while (i <= j) {

if (lessOrEqual(i, j, n - j + 1, n - i + 1)) {

result.push_back(s[i - 1]);

i++;

} else {

result.push_back(s[j - 1]);

j--;

}

}

for (int k = 0; k < (int)result.size(); k++) {

cout << result[k];

if ((k + 1) % 80 == 0) cout << '\n';

}

cout << '\n';

return 0;

}

php

1.97 KB

© 菜鸟-创作你的创作


说明

  • 双哈希避免单哈希偶发冲突,安全性更高
  • 时间复杂度 O(nlog⁡n),适合大数据量
  • 代码结构清晰易改

https://www.52runoob.com/archives/5466

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值