引言
想象一下,你手握一串数字,像解开古老符文的探险家;或是在字符的海洋中,用指针跳起一支优雅的华尔兹。
今天,我们将用数学的魔法赋予数字新的排列生命,再用指针的舞步颠倒文字乾坤。
无需魔法书,只需逻辑与想象力——这场数字与字符的双重奏,现在开始!
正文
一、谜题一:下一个更大元素 III(数学排列+指针)
题目核心
给定一个32位整数,将其数字重新排列成下一个更大的整数。若不存在则返回-1。
限制条件:仅通过指针操作模拟数字交换,不可暴力枚举所有排列。
算法解密
关键思路:模拟 next_permutation
算法,寻找“最小增量式”排列。
-
逆向扫描:从右向左找到第一个“下降点”
nums[i] < nums[i+1]
,标记为分界点。 -
交换策略:在分界点右侧找到比
nums[i]
大的最小数字nums[j]
,交换二者。 -
局部反转:将分界点右侧的数字升序排列(反转即可),确保新数是“最小更大值”。
示例解析
输入 12
:
-
下降点为
1
(对应数字1),右侧找到最小更大值2,交换得21
。
输入21
:无下降点,直接返回-1。
时间复杂度:O(n)
,仅需两次线性扫描和一次局部反转。
/* 标准输入输出头文件,提供printf/scanf等函数 */
#include <stdio.h>
/* 动态内存管理头文件,提供malloc/free等函数 */
#include <stdlib.h>
/* 整型极限值定义头文件,包含INT_MAX等常量 */
#include <limits.h>
/* 字符串操作头文件,提供strlen/strcpy等函数 */
#include <string.h>
/**
* 计算下一个更大排列的核心算法
* @param n 输入的正整数
* @return 下一个更大排列的数值,不存在则返回-1
*/
int nextGreaterElement(int n) {
/* 处理非正整数的情况(负数与零无有效排列) */
if (n <= 0) return -1; // 直接返回-1,因为题目要求正整数
/* 将整数转换为字符数组便于处理单个数字 */
char s[20]; // 预留20字节空间(考虑符号位和结束符)
sprintf(s, "%d", n); // 将整数格式化为字符串存入s数组
int len = strlen(s); // 计算数字字符串的实际长度
/* ==== 阶段一:寻找第一个下降点 ==== */
int i; // 声明下降点索引变量
/* 从倒数第二个字符开始向左扫描 */
for (i = len - 2; i >= 0; --i) { // len-2因为要比较i和i+1
/* 发现前一个字符小于后一个字符时停止 */
if (s[i] < s[i + 1]) break; // 找到递减趋势的转折点
}
/* 未找到下降点时返回-1(已是最大排列) */
if (i < 0) return -1; // 全部数字呈递减排列,没有更大可能
/* ==== 阶段二:寻找最小交换点 ==== */
int j; // 声明交换点索引变量
/* 从字符串末尾向左扫描找第一个大于s[i]的字符 */
for (j = len - 1; j > i; --j) { // 限定在下降点右侧寻找
if (s[j] > s[i]) break; // 找到最小的较大值
}
/* ==== 执行数字交换操作 ==== */
char temp = s[i]; // 临时存储要交换的字符
s[i] = s[j]; // 将较大的字符换到前位置
s[j] = temp; // 将原字符换到后位置
/* ==== 阶段三:反转右侧数字序列 ==== */
/* 使用双指针法反转i+1到末尾的字符 */
int left = i + 1; // 左指针从下降点右侧开始
int right = len - 1; // 右指针指向字符串末尾
while (left < right) { // 当左右指针未相遇时持续交换
temp = s[left]; // 临时存储左指针字符
s[left] = s[right]; // 将右指针字符赋给左位置
s[right] = temp; // 将临时字符赋给右位置
left++; // 左指针右移
right--; // 右指针左移
}
/* ==== 结果验证与转换 ==== */
/* 将字符数组转换为长整型(防止32位溢出) */
char* endptr; // 用于检测转换错误的指针
long result = strtol(s, &endptr, 10); // 十进制转换
/* 验证转换有效性及数值范围 */
if (*endptr != '\0' || // 存在非法字符(理论上不会触发)
result > INT_MAX || // 超过32位有符号整数最大值
result <= n) { // 结果不大于原始值(理论上不会触发)
return -1; // 返回错误标识
}
return (int)result; // 安全转换为int类型后返回
}
/**
* 主函数:处理程序输入输出
* @return 程序退出状态码
*/
int main() {
int input_num; // 声明存储用户输入的变量
/* 获取用户输入 */
printf("Please enter a 32-bit integer: ");
scanf("%d", &input_num); // 读取整数值到input_num
printf("The initial values are:%d\n", input_num); // 回显输入
/* 计算并输出结果 */
int result = nextGreaterElement(input_num); // 调用核心算法
printf("The next larger permutation is: %d\n", result);
return 0; // 正常退出程序
}
输出结果:
二、谜题二:全指针字符串单词反转
题目核心
原地反转字符串中的单词顺序(如 "hello world" → "world hello"
),仅用指针操作,禁止库函数或额外空间。
算法解密
关键思路:两次反转法,用指针精准切割单词边界。
-
全局反转:先反转整个字符串,将单词顺序倒置(如
"hello world" → "dlrow olleh"
)。 -
单词级反转:
-
用双指针定位单词的起止位置(跳过空格)。
-
逐个反转单词,恢复其原始顺序(如
"dlrow" → "world"
,"olleh" → "hello"
)。
-
关键难点:
-
指针移动时需处理连续空格和字符串末尾。
-
必须完全依赖指针算术,无临时变量存储位置。
示例解析
输入 "hello world"
:
-
全局反转 →
"dlrow olleh"
。 -
反转单词
"dlrow" → "world"
,"olleh" → "hello"
,最终得"world hello"
。
时间复杂度:O(n)
,两次线性遍历。
#include <stdio.h> // 引入标准输入输出库,用于printf函数
// 定义反转函数,接收两个指针参数:start(起始位置)和end(结束位置)
void reverse(char *start, char *end) {
// 当start指针地址小于end时,持续交换字符,直到指针相遇或交叉
while (start < end) {
char temp = *start; // 临时变量存储start当前指向的字符
*start = *end; // 将end指向的字符赋值给start位置
*end = temp; // 将临时变量中的字符赋值给end位置
start++; // 将start指针向后移动一位
end--; // 将end指针向前移动一位
}
}
int main() {
char str[] = "hello world"; // 定义并初始化可修改的字符数组(注意:不能用指针形式,否则无法修改)
printf("The initial string is: %s\n", str); // 打印原始字符串
// 步骤1:全局反转整个字符串
char *start = str; // 定义起始指针,指向字符串开头
char *end = str; // 定义结束指针,初始同样指向开头
// 将end指针移动到字符串末尾(即'\0'的位置)
while (*end != '\0') {
end++; // 逐字符向后移动,直到遇到字符串结束符
}
// 处理非空字符串的情况(避免对空字符串操作导致错误)
if (end > str) { // 如果end指针地址大于start(即字符串非空)
end--; // end回退一位,指向最后一个有效字符(跳过'\0')
reverse(start, end); // 调用反转函数,反转整个字符串
}
// 步骤2:逐个反转单词,恢复单词原始顺序
char *p = str; // 定义遍历指针p,从字符串开头开始扫描
while (*p != '\0') { // 循环直到字符串结束
// 跳过连续的空格,定位到单词的起始位置
while (*p == ' ') {
p++; // 若当前字符是空格,继续向后移动指针
}
if (*p == '\0') { // 若跳过空格后直接到达字符串末尾,说明没有单词需要处理
break; // 退出循环
}
// 定位单词的起始和结束位置
char *word_start = p; // 记录单词起始位置(此时p指向单词的第一个字符)
// 移动指针p直到遇到空格或字符串结束符
while (*p != ' ' && *p != '\0') {
p++; // 持续后移,p最终指向单词后的空格或'\0'
}
char *word_end = p - 1; // 单词的最后一个字符是p的前一个位置
reverse(word_start, word_end); // 反转当前单词,恢复其原始顺序
}
// 输出最终结果
printf("The reversed string is: %s\n", str);
return 0; // 程序正常退出
}
输出结果为:
三、对比分析:数学之静 vs 指针之动
对比维度 | 下一个更大元素 III | 全指针单词反转 |
---|---|---|
核心技巧 | 数学排列的贪心策略 | 指针操作的精确舞蹈 |
时间复杂度 | O(n) | O(n) |
空间复杂度 | O(1) | O(1) |
关键突破点 | 逆向扫描与局部最优交换 | 全局与局部反转的嵌套逻辑 |
适用场景 | 数字序列的增量式生成 | 字符串原地操作的极致优化 |
四、总结
-
下一个更大数问题:通过逆向思维与局部调整,展现了数学排列的简洁美学。
-
单词反转问题:用指针的“双重反转”策略,诠释了空间压缩与操作优雅性的平衡。
共同启示:无论是数字还是字符,算法本质是逻辑与操作的精准交响。
谢言
感谢你与我共舞于数字与字符的奇幻世界!
若你想挑战更高难度,不妨尝试:
-
将「下一个更大数」扩展到负数或浮点数。
-
用纯指针实现多语言混合字符串的反转(如中英文混杂)。
记住:算法的边界,只由想象力定义。
下期预告:递归分形与量子漫步——我们不见不散!
互动彩蛋
评论区挑战:若允许数字中有前导零,下一个更大数的算法需要如何调整?指针反转法能否处理包含标点符号的句子?分享你的脑洞!