一、字符串
1.1、指定位置字符串插入
两个字符串,s,t;把t字符串插入到s字符串中,s字符串有足够的空间存放t字符串。
cpp
#include <stdio.h>
#include <string.h>
#include <assert.h>
/**
* @brief 将字符串 t 插入到 s 的指定位置 pos
* @param s 目标字符串(需有足够空间)
* @param t 待插入字符串
* @param pos 插入位置(0 ≤ pos ≤ strlen(s))
*/
void insertString(char *s, const char *t, int pos) {
// 1. 合法性检查:s/t 不为空,pos 合法
assert(s != NULL && t != NULL);
int len_s = strlen(s);
int len_t = strlen(t);
if (pos < 0 || pos > len_s) {
printf("错误:插入位置非法!\n");
return;
}
// 2. 将 s 中 pos 后的字符向后移动 len_t 位(为 t 腾出空间)
// 从后往前移动,避免覆盖未处理的字符
for (int i = len_s; i >= pos; i--) {
s[i + len_t] = s[i];
}
// 3. 将 t 拷贝到 s 的 pos 位置
for (int i = 0; i < len_t; i++) {
s[pos + i] = t[i];
}
// 4. 确保字符串以 '\0' 结尾(若 s 空间足够,此步可省略,但建议保留)
s[len_s + len_t] = '\0';
}
/**
* @param dest 目标内存块的起始地址
* @param src 源内存块的起始地址
* @param n 需要拷贝的字节数
* @return 目标内存块的起始地址(dest)
void* memmove(void* dest, const void* src, size_t n);
*/
void insertString(char *s, const char *t, int pos) {
assert(s != NULL && t != NULL);
int len_s = strlen(s);
int len_t = strlen(t);
if (pos < 0 || pos > len_s) return;
// 用 memmove 移动字符(自动处理重叠内存)
memmove(s + pos + len_t, s + pos, len_s - pos + 1); // +1 包含 '\0'
// 用 memcpy 拷贝 t 到指定位置
memcpy(s + pos, t, len_t);
}
// 测试代码
int main() {
// 注意:s 需分配足够空间(此处分配 100 字节,确保能容纳 s 原内容 + t)
char s[100] = "hello world";
const char *t = "beautiful ";
int insert_pos = 6; // 插入到 "world" 前(原 s[6] 是 'w')
printf("插入前 s:%s\n", s);
insertString(s, t, insert_pos);
printf("插入后 s:%s\n", s); // 输出:hello beautiful world
return 0;
}
1.2、查找可能最长相同子串
编写一个 C 函数,该函数在一个字符串中找到可能的最长的子字符串,且该字符串是由同一字符组成的。
cpp
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/**
* @brief 查找字符串中由同一字符组成的最长子字符串
* @param str 输入的目标字符串
* @param max_char 用于存储最长子字符串的字符(输出参数)
* @return 最长子字符串的长度,若字符串为空返回 0
*/
int findLongestSameCharSubstring(const char *str, char *max_char) {
if (str == NULL || *str == '\0') { // 处理空字符串
*max_char = '\0';
return 0;
}
int max_len = 1; // 最长子串长度(至少为 1)
int current_len = 1; // 当前连续字符长度
char current_char = str[0]; // 当前连续字符
*max_char = current_char; // 初始化最长字符
// 遍历字符串(从第二个字符开始)
for (int i = 1; str[i] != '\0'; i++) {
if (str[i] == current_char) {
current_len++; // 字符相同,当前长度+1
// 更新最长子串信息
if (current_len > max_len) {
max_len = current_len;
*max_char = current_char;
}
} else {
// 字符不同,重置当前状态
current_char = str[i];
current_len = 1;
}
}
return max_len;
}
// 测试示例
int main() {
const char *test_str1 = "aaabbbccccdd";
const char *test_str2 = "ab";
const char *test_str3 = "zzzz";
const char *test_str4 = "";
char max_char;
int len;
len = findLongestSameCharSubstring(test_str1, &max_char);
printf("字符串 \"%s\":最长子串为 '%c',长度 %d\n", test_str1, max_char, len);
len = findLongestSameCharSubstring(test_str2, &max_char);
printf("字符串 \"%s\":最长子串为 '%c',长度 %d\n", test_str2, max_char, len);
len = findLongestSameCharSubstring(test_str3, &max_char);
printf("字符串 \"%s\":最长子串为 '%c',长度 %d\n", test_str3, max_char, len);
len = findLongestSameCharSubstring(test_str4, &max_char);
printf("字符串 \"%s\":最长子串长度 %d\n", test_str4, len);
return 0;
}
1.3、搜索最长子串
请编写一个 C 函数,该函数在给定的内存区域搜索给定的字符,并返回该字符所在位置索引值。
cpp
#include <stddef.h>
#include <string.h>
/**
* @brief 在内存区域中搜索单个子串,返回首次出现的起始索引
* @param mem 内存区域起始地址
* @param mem_size 内存区域的字节长度
* @param substr 要搜索的目标子串
* @return 找到则返回起始索引(从0开始);未找到/参数无效返回 -1
*/
int findSubstringInMemory(const void *mem, size_t mem_size, const char *substr) {
// 参数合法性检查
if (mem == NULL || mem_size == 0 || substr == NULL || *substr == '\0') {
return -1;
}
const unsigned char *mem_ptr = (const unsigned char *)mem;
size_t substr_len = strlen(substr);
// 子串长度超过内存区域,直接返回-1
if (substr_len > mem_size) {
return -1;
}
// 滑动窗口匹配子串
for (size_t i = 0; i <= mem_size - substr_len; i++) {
int match = 1;
for (size_t j = 0; j < substr_len; j++) {
if (mem_ptr[i + j] != (unsigned char)substr[j]) {
match = 0;
break;
}
}
if (match) {
return (int)i; // 找到首次匹配,返回索引
}
}
return -1; // 未找到
}
// 测试示例
#include <stdio.h>
int main() {
// 测试场景
const char *mem = "ababcabcd";
size_t mem_size = strlen(mem);
const char *substr = "abc";
int idx = findSubstringInMemory(mem, mem_size, substr);
if (idx != -1) {
printf("子串\"%s\"首次出现的索引:%d\n", substr, idx); // 输出:2
} else {
printf("未找到子串\"%s\"\n", substr);
}
return 0;
}
1.4、写一函数,实现删除字符串str1中含有的字符串str2.
cpp
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/**
* @brief 删除字符串 str1 中所有的子串 str2
* @param str1 源字符串(会被修改,需保证可写且有足够空间)
* @param str2 要删除的子串
* @return 处理后的字符串(即 str1 本身);若参数无效返回 NULL
*/
char *removeSubstring(char *str1, const char *str2) {
// 参数合法性检查
if (str1 == NULL || str2 == NULL || *str2 == '\0') {
return str1; // 空子串无需处理,直接返回原字符串
}
size_t len1 = strlen(str1);
size_t len2 = strlen(str2);
if (len2 > len1) {
return str1; // 子串比源字符串长,无匹配,直接返回
}
char *pos = str1; // 遍历指针
while ((pos = strstr(pos, str2)) != NULL) { // 查找子串 str2 的位置
// 计算剩余字符串长度(子串后的部分)
size_t remaining_len = strlen(pos + len2);
// 将子串后的内容向前移动,覆盖子串 str2
memmove(pos, pos + len2, remaining_len + 1); // +1 包含 '\0'
// 不移动 pos,继续检查当前位置(可能新的匹配出现在此处)
}
return str1;
}
// 测试示例
int main() {
// 注意:str1 需分配可写的内存(不能是字符串常量)
char str1[] = "ababcabcdabcde";
const char *str2 = "abc";
printf("原字符串:%s\n", str1);
removeSubstring(str1, str2);
printf("删除 \"%s\" 后:%s\n", str2, str1); // 输出:abde
// 测试子串出现在开头/结尾
char str3[] = "test123test";
const char *str4 = "test";
removeSubstring(str3, str4);
printf("删除 \"%s\" 后:%s\n", str4, str3); // 输出:123
// 测试无匹配子串
char str5[] = "hello world";
const char *str6 = "test";
removeSubstring(str5, str6);
printf("删除 \"%s\" 后:%s\n", str6, str5); // 输出:hello world
return 0;
}
1.5、比较字符串
写一个函数比较两个字符串str1和str2的大小,若相等返回0,若str1大于str2返回1,若str1小于str2返回-1
cpp
#include <stddef.h>
/**
* @brief 比较两个字符串的大小
* @param str1 第一个字符串
* @param str2 第二个字符串
* @return 相等返回0,str1>str2返回1,str1<str2返回-1
* @note 逐字符对比ASCII值,遇到不同字符或字符串结束时返回结果
*/
int strCompare(const char *str1, const char *str2) {
// 同时遍历两个字符串,逐字符比较
while (*str1 != '\0' && *str2 != '\0') {
if (*str1 > *str2) {
return 1;
} else if (*str1 < *str2) {
return -1;
}
// 字符相等则继续下一个
str1++;
str2++;
}
// 处理字符串长度不同的情况
if (*str1 != '\0') {
// str1还有剩余字符,str1更大
return 1;
} else if (*str2 != '\0') {
// str2还有剩余字符,str1更小
return -1;
}
// 两个字符串完全相等
return 0;
}
// 测试示例
#include <stdio.h>
int main() {
const char *s1 = "apple";
const char *s2 = "banana";
const char *s3 = "apple";
const char *s4 = "app";
const char *s5 = "apples";
printf("\"%s\" vs \"%s\": %d\n", s1, s2, strCompare(s1, s2)); // -1
printf("\"%s\" vs \"%s\": %d\n", s1, s3, strCompare(s1, s3)); // 0
printf("\"%s\" vs \"%s\": %d\n", s1, s4, strCompare(s1, s4)); // 1
printf("\"%s\" vs \"%s\": %d\n", s1, s5, strCompare(s1, s5)); // -1
return 0;
}
1.6、找出两个字符串中最大公共子字符串,如"abccade","dgcadde"的最大子串为"cad"
cpp
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/**
* @brief 找出两个字符串的最大公共子字符串
* @param str1 第一个字符串
* @param str2 第二个字符串
* @return 最大公共子字符串(需外部释放);若无公共子串返回空字符串
*/
char *findMaxCommonSubstring(const char *str1, const char *str2) {
if (str1 == NULL || str2 == NULL || *str1 == '\0' || *str2 == '\0') {
char *empty = (char *)malloc(1);
empty[0] = '\0';
return empty;
}
int len1 = strlen(str1);
int len2 = strlen(str2);
int max_len = 0; // 最大公共子串长度
int max_end_idx = 0; // 最大公共子串在str1中的结束索引
// 创建动态规划表:dp[i][j]表示以str1[i-1]和str2[j-1]结尾的公共子串长度
int **dp = (int **)malloc((len1 + 1) * sizeof(int *));
for (int i = 0; i <= len1; i++) {
dp[i] = (int *)calloc(len2 + 1, sizeof(int));
}
// 填充动态规划表
for (int i = 1; i <= len1; i++) {
for (int j = 1; j <= len2; j++) {
if (str1[i-1] == str2[j-1]) {
dp[i][j] = dp[i-1][j-1] + 1;
// 更新最大长度和结束索引
if (dp[i][j] > max_len) {
max_len = dp[i][j];
max_end_idx = i - 1; // str1的当前字符索引
}
} else {
dp[i][j] = 0; // 字符不相等,公共子串长度重置为0
}
}
}
// 提取最大公共子字符串
char *result = (char *)malloc(max_len + 1);
if (max_len > 0) {
strncpy(result, str1 + (max_end_idx - max_len + 1), max_len);
}
result[max_len] = '\0';
// 释放动态规划表内存
for (int i = 0; i <= len1; i++) {
free(dp[i]);
}
free(dp);
return result;
}
// 测试示例
int main() {
const char *str1 = "abccade";
const char *str2 = "dgcadde";
char *max_sub = findMaxCommonSubstring(str1, str2);
printf("字符串1:%s\n", str1);
printf("字符串2:%s\n", str2);
printf("最大公共子字符串:%s\n", max_sub); // 输出:cad
free(max_sub);
return 0;
}
1.7、把十进制数(long型)分别以二进制和十六进制形式输出,不能使用printf系列库函数
cpp
#include <stdio.h>
void put_char(char c) {
putchar(c);
}
/**
* @brief 将十进制长整型转换为二进制字符串输出
* @param num 待转换的长整型数
*/
void printBinary(long num) {
if (num == 0) { // 处理0的情况
put_char('0');
return;
}
// 用于存储二进制位(long型最多64位)
char bits[64];
int idx = 0;
// 处理负数(补码形式,此处简化为绝对值转换后加负号)
int is_negative = 0;
if (num < 0) {
is_negative = 1;
num = -num; // 注意:long最小值取反会溢出,此处简化处理
}
// 逐位提取二进制
while (num > 0) {
bits[idx++] = (num % 2) + '0';
num /= 2;
}
// 输出负号
if (is_negative) {
put_char('-');
}
// 逆序输出二进制位
if (idx == 0) {
put_char('0');
} else {
for (int i = idx - 1; i >= 0; i--) {
put_char(bits[i]);
}
}
}
/**
* @brief 将十进制长整型转换为十六进制字符串输出
* @param num 待转换的长整型数
*/
void printHex(long num) {
if (num == 0) { // 处理0的情况
put_char('0');
return;
}
// 用于存储十六进制位
char hex_chars[] = "0123456789ABCDEF";
char hex[16];
int idx = 0;
// 处理负数(补码形式,此处简化为绝对值转换后加负号)
int is_negative = 0;
if (num < 0) {
is_negative = 1;
num = -num; // 注意:long最小值取反会溢出,此处简化处理
}
// 逐位提取十六进制
while (num > 0) {
hex[idx++] = hex_chars[num % 16];
num /= 16;
}
// 输出负号和0x前缀
if (is_negative) {
put_char('-');
}
put_char('0');
put_char('x');
// 逆序输出十六进制位
if (idx == 0) {
put_char('0');
} else {
for (int i = idx - 1; i >= 0; i--) {
put_char(hex[i]);
}
}
}
// 测试示例
int main() {
long num = 12345;
long num_neg = -12345;
// 输出二进制
printBinary(num);
put_char('\n');
printBinary(num_neg);
put_char('\n');
// 输出十六进制
printHex(num);
put_char('\n');
printHex(num_neg);
put_char('\n');
return 0;
}
1.8、判断一个字符串是不是回文
cpp
#include <stdio.h>
#include <string.h>
#include <ctype.h>
/**
* @brief 判断字符串是否为回文(区分大小写,包含所有字符)
* @param str 输入字符串
* @return 是回文返回1,否则返回0;空字符串返回1
*/
int isPalindromeBasic(const char *str) {
if (str == NULL) return 0; // 空指针返回0
int len = strlen(str);
if (len <= 1) return 1; // 空字符串或单个字符是回文
int left = 0;
int right = len - 1;
while (left < right) {
if (str[left] != str[right]) {
return 0; // 字符不相等,不是回文
}
left++;
right--;
}
return 1;
}
/**
* @brief 判断字符串是否为回文(忽略大小写,忽略非字母数字字符)
* @param str 输入字符串
* @return 是回文返回1,否则返回0
*/
int isPalindromeEnhanced(const char *str) {
if (str == NULL) return 0;
int left = 0;
int right = strlen(str) - 1;
while (left < right) {
// 跳过左侧非字母数字字符
while (left < right && !isalnum((unsigned char)str[left])) {
left++;
}
// 跳过右侧非字母数字字符
while (left < right && !isalnum((unsigned char)str[right])) {
right--;
}
// 转换为小写后比较
if (tolower((unsigned char)str[left]) != tolower((unsigned char)str[right])) {
return 0;
}
left++;
right--;
}
return 1;
}
// 测试示例
int main() {
const char *test1 = "abba";
const char *test2 = "abcba";
const char *test3 = "abcde";
const char *test4 = "A man, a plan, a canal: Panama";
const char *test5 = "";
const char *test6 = "a";
printf("\"%s\" 是否为回文(基础版):%s\n", test1, isPalindromeBasic(test1) ? "是" : "否");
printf("\"%s\" 是否为回文(基础版):%s\n", test2, isPalindromeBasic(test2) ? "是" : "否");
printf("\"%s\" 是否为回文(基础版):%s\n", test3, isPalindromeBasic(test3) ? "是" : "否");
printf("\"%s\" 是否为回文(增强版):%s\n", test4, isPalindromeEnhanced(test4) ? "是" : "否");
printf("\"%s\" 是否为回文(基础版):%s\n", test5, isPalindromeBasic(test5) ? "是" : "否");
printf("\"%s\" 是否为回文(基础版):%s\n", test6, isPalindromeBasic(test6) ? "是" : "否");
return 0;
}
1.9、已知strcpy函数的原型是:char * strcpy(char * strDest,const char * strSrc);
1.不调用库函数,实现strcpy函数。
2.解释为什么要返回char *。
cpp
#include <stddef.h> // 用于 NULL 定义
char *strcpy(char *strDest, const char *strSrc) {
// 检查参数合法性(防止空指针)
if (strDest == NULL || strSrc == NULL) {
return NULL;
}
// 保存目标字符串起始地址(用于最终返回)
char *dest = strDest;
// 逐字节拷贝,直到遇到源字符串的结束符 '\0'
while ((*strDest++ = *strSrc++) != '\0');
// 返回目标字符串起始地址
return dest;
}
// 测试示例
#include <stdio.h>
int main() {
char dest[100];
const char *src = "Hello, strcpy!";
strcpy(dest, src);
printf("拷贝结果:%s\n", dest); // 输出:Hello, strcpy!
return 0;
}
1.10、在一个字符串中找到第一个只出现一次的字符。如输入abaccdeff,则输出b。
分析:这道题是2006年google的一道笔试题。
cpp
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/**
* @brief 找到字符串中第一个只出现一次的字符
* @param str 输入字符串
* @return 第一个只出现一次的字符;若不存在返回 '\0'
*/
char findFirstUniqueChar(const char *str) {
if (str == NULL || *str == '\0') {
return '\0'; // 空字符串返回空字符
}
// 方法1:ASCII数组统计字符出现次数(空间换时间)
int count[256] = {0}; // ASCII字符共256个,初始化为0
// 第一次遍历:统计每个字符出现次数
for (int i = 0; str[i] != '\0'; i++) {
count[(unsigned char)str[i]]++;
}
// 第二次遍历:找到第一个出现次数为1的字符
for (int i = 0; str[i] != '\0'; i++) {
if (count[(unsigned char)str[i]] == 1) {
return str[i];
}
}
return '\0'; // 无唯一字符
}
// 测试示例
int main() {
const char *test1 = "abaccdeff";
const char *test2 = "aabbcc";
const char *test3 = "hello world";
char result1 = findFirstUniqueChar(test1);
char result2 = findFirstUniqueChar(test2);
char result3 = findFirstUniqueChar(test3);
printf("字符串 \"%s\" 中第一个只出现一次的字符:%c\n", test1, result1); // 输出 b
printf("字符串 \"%s\" 中第一个只出现一次的字符:%c\n", test2, result2); // 输出 '\0'(无)
printf("字符串 \"%s\" 中第一个只出现一次的字符:%c\n", test3, result3); // 输出 h
return 0;
}
1.11、有效括号对数
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_STACK_SIZE 100
// 栈结构定义
typedef struct {
char data[MAX_STACK_SIZE];
int top; // 栈顶指针
} Stack;
// 初始化栈
void initStack(Stack *stack) {
stack->top = -1;
}
// 判断栈是否为空
int isStackEmpty(Stack *stack) {
return stack->top == -1;
}
// 判断栈是否已满
int isStackFull(Stack *stack) {
return stack->top == MAX_STACK_SIZE - 1;
}
// 入栈操作
int push(Stack *stack, char c) {
if (isStackFull(stack)) {
return 0; // 栈满,入栈失败
}
stack->data[++stack->top] = c;
return 1;
}
// 出栈操作
char pop(Stack *stack) {
if (isStackEmpty(stack)) {
return '\0'; // 栈空,返回空字符
}
return stack->data[stack->top--];
}
// 获取栈顶元素
char getTop(Stack *stack) {
if (isStackEmpty(stack)) {
return '\0';
}
return stack->data[stack->top];
}
/**
* @brief 判断括号字符串是否有效,并统计有效括号对数
* @param str 输入的括号字符串
* @param pair_count 输出有效括号对数(需传入指针)
* @return 有效返回1,无效返回0
*/
int isValidParentheses(const char *str, int *pair_count) {
if (str == NULL || *str == '\0') {
*pair_count = 0;
return 1; // 空字符串视为有效
}
Stack stack;
initStack(&stack);
*pair_count = 0;
for (int i = 0; str[i] != '\0'; i++) {
char c = str[i];
// 左括号入栈
if (c == '(' || c == '[' || c == '{') {
push(&stack, c);
}
// 右括号匹配
else if (c == ')' || c == ']' || c == '}') {
if (isStackEmpty(&stack)) {
*pair_count = 0;
return 0; // 右括号多余,无效
}
char top = getTop(&stack);
// 判断括号是否匹配
if ((c == ')' && top == '(') ||
(c == ']' && top == '[') ||
(c == '}' && top == '{')) {
pop(&stack);
(*pair_count)++; // 匹配成功,对数+1
} else {
*pair_count = 0;
return 0; // 括号类型不匹配,无效
}
}
// 非括号字符忽略(可选,根据需求处理)
}
// 遍历结束后栈为空则有效,否则左括号多余
if (!isStackEmpty(&stack)) {
*pair_count = 0;
return 0;
}
return 1;
}
// 测试示例
int main() {
const char *test1 = "()[]{}";
const char *test5 = "(()";
int count;
if (isValidParentheses(test1, &count)) {
printf("\"%s\" 有效,括号对数:%d\n", test1, count); // 有效,3对
} else {
printf("\"%s\" 无效\n", test1);
}
if (isValidParentheses(test5, &count)) {
printf("\"%s\" 有效,括号对数:%d\n", test5, count);
} else {
printf("\"%s\" 无效\n", test5); // 无效
}
return 0;
}
1.12、加密字符串
给定一个由多个命令字组成的命令字符串:
1、字符串长度小于等于127字节,只包含大小写字母,数字,下划线和偶数个双引号;
2、命令字之间以一个或多个下划线_进行分割;
3、可以通过两个双引号""来标识包含下划线_的命令字或空命令字(仅包含两个双引号的命令字),双引号不会在命令字内部出现; 请对指定索引的敏感字段进行加密,替换为******(6个*),并删除命令字前后多余的下划线_。 如果无法找到指定索引的命令字,输出字符串ERROR。 输入描述 输入为两行,第一行为命令字索引K(从0开始),第二行为命令字符串S。 输出描述 输出处理后的命令字符串,如果无法找到指定索引的命令字,输出字符串ERROR
cpp
#include <stdio.h> // 标准输入输出库
#include <stdlib.h> // 内存分配/释放库(malloc/free)
#include <string.h> // 字符串操作库(strlen/strcpy/strcat等)
#include <stdbool.h> // 布尔类型库(true/false)
#define MAX_LEN 128 // 定义字符串最大长度为128(题目要求≤127)
// 解析命令字符串,提取命令字到二维字符数组中
// 参数:str-输入字符串,commands-输出命令字数组(二级指针)
// 返回:解析出的命令字数量
int parseCommand(const char *str, char ***commands) {
int count = 0; // 记录解析出的命令字数量
int len = strlen(str); // 获取输入字符串长度
*commands = (char **)malloc(MAX_LEN * sizeof(char *)); // 分配命令字数组内存(最多128个命令字)
char *buf = (char *)malloc(MAX_LEN * sizeof(char)); // 临时缓冲区,存储当前解析的命令字
int buf_idx = 0; // 临时缓冲区的索引指针
bool in_quote = false; // 标记是否处于双引号内部
// 遍历字符串(i<=len是为了处理字符串末尾的'\0')
for (int i = 0; i <= len; i++) {
char c = str[i]; // 当前遍历到的字符
// 遇到左双引号,标记进入引号内部
if (c == '"' && !in_quote) {
in_quote = true;
}
// 遇到右双引号,标记退出引号内部
else if (c == '"' && in_quote) {
in_quote = false;
}
// 遇到下划线或字符串结束符,且不在引号内(表示命令字分隔)
else if ((c == '_' || c == '\0') && !in_quote) {
// 条件:缓冲区有内容 或 是""空命令字(前两个字符是"")
if (buf_idx > 0 || (i > 0 && str[i-1] == '"' && str[i-2] == '"')) {
buf[buf_idx] = '\0'; // 给当前命令字添加字符串结束符
(*commands)[count] = (char *)malloc(strlen(buf) + 1); // 为命令字分配内存
strcpy((*commands)[count], buf); // 将缓冲区内容拷贝到命令字数组
count++; // 命令字数量+1
buf_idx = 0; // 重置缓冲区索引
}
// 跳过连续的下划线(多个下划线视为一个分隔符)
while (str[i+1] == '_') i++;
}
// 非分隔符且非结束符,将字符加入缓冲区(构建当前命令字)
else if (c != '\0') {
buf[buf_idx++] = c;
}
}
free(buf); // 释放临时缓冲区内存
return count; // 返回解析出的命令字总数
}
// 将处理后的命令字拼接为结果字符串
// 参数:commands-命令字数组,count-命令字数量,k-需要加密的索引
// 返回:拼接后的结果字符串
char *joinCommands(char **commands, int count, int k) {
char *result = (char *)malloc(MAX_LEN * sizeof(char)); // 分配结果字符串内存
result[0] = '\0'; // 初始化结果字符串为空
// 遍历所有命令字
for (int i = 0; i < count; i++) {
if (i == k) { // 遇到需要加密的索引,替换为******
strcat(result, "******");
} else { // 其他索引直接拼接原命令字
strcat(result, commands[i]);
}
// 不是最后一个命令字,添加下划线分隔符
if (i != count - 1) {
strcat(result, "_");
}
}
return result; // 返回拼接后的结果
}
int main() {
int k; // 输入的加密索引
char s[MAX_LEN]; // 存储输入的命令字符串
scanf("%d", &k); // 读取索引k
getchar(); // 读取scanf后的换行符(避免被fgets读取)
fgets(s, MAX_LEN, stdin); // 读取命令字符串
s[strcspn(s, "\n")] = '\0'; // 去除fgets读取的换行符(保证字符串纯净)
char **commands; // 存储解析后的命令字数组
int count = parseCommand(s, &commands); // 解析字符串,获取命令字数组和数量
// 检查索引是否有效(k超出范围则输出ERROR)
if (k < 0 || k >= count) {
printf("ERROR\n");
} else {
// 拼接处理后的字符串并输出
char *res = joinCommands(commands, count, k);
printf("%s\n", res);
free(res); // 释放拼接结果的内存
}
// 释放命令字数组的内存(避免内存泄漏)
for (int i = 0; i < count; i++) {
free(commands[i]);
}
free(commands);
return 0; // 程序正常结束
}
1.13、寻找字符串ASCII相同节点下标
给定一个字符串,只包含小写字母,字符串长度是 5-30。
求:是否存在两个节点,使得字符串被这两个节点分成三个部分,每个部分的 ASCII 码的值之和都相等。如果存在输出两个节点下标,以逗号隔开。下标从 0 开始,如果不存在,则输出 0,0。如果存在答案,则是唯一解。
示例一:输入
abcbbbcab
输出 2,5
cpp
#include <stdio.h>
#include <string.h>
int main() {
char s[31];
scanf("%s", s);
int len = strlen(s);
int total = 0;
// 计算字符串总ASCII和
for (int i = 0; i < len; i++) {
total += s[i];
}
// 总和不能被3整除,直接返回0,0
if (total % 3 != 0) {
printf("0,0\n");
return 0;
}
int target = total / 3;
int sum1 = 0, split1 = -1, split2 = -1;
// 找第一个分割点(需保留至少两个字符给后两部分)
for (int i = 0; i < len - 2; i++) {
sum1 += s[i];
if (sum1 == target) {
split1 = i;
break;
}
}
// 未找到第一个分割点
if (split1 == -1) {
printf("0,0\n");
return 0;
}
int sum2 = 0;
// 找第二个分割点(需保留至少一个字符给第三部分)
for (int i = split1 + 1; i < len - 1; i++) {
sum2 += s[i];
if (sum2 == target) {
split2 = i;
break;
}
}
// 找到有效分割点则输出,否则输出0,0
if (split2 != -1) {
printf("%d,%d\n", split1, split2);
} else {
printf("0,0\n");
}
return 0;
}
1.14、分割均衡字符串
思路:分别为X和Y都设定计数器以表示其数量,当数量一致时总数加一,XY计数器重置。
cpp
#include <stdio.h>
#include <string.h>
// 分割均衡字符串,返回分割次数(或输出分割位置)
int balancedStringSplit(const char *s) {
int countX = 0, countY = 0; // 两种字符的计数器
int splitCount = 0; // 分割次数
int len = strlen(s);
for (int i = 0; i < len; i++) {
// 假设字符为X和Y,可根据实际字符调整
if (s[i] == 'X') {
countX++;
} else if (s[i] == 'Y') {
countY++;
}
// 当数量相等时,完成一次分割
if (countX == countY && countX > 0) {
splitCount++;
// 若需输出分割位置,此处记录i(子串结束下标)
printf("分割位置:%d\n", i);
countX = 0; // 重置计数器,准备下一次分割
countY = 0;
}
}
return splitCount;
}
int main() {
char s[] = "XXYYXYXY"; // 示例输入
int result = balancedStringSplit(s);
printf("分割次数:%d\n", result); // 输出:3(分割为XXYY、XY、XY)
return 0;
}
1.15、万能字符单词拼写
思路:当前判别的单词,如果字符一样那么字符串需要需要去除这个字符,并且问号也需要考虑进去。当这个单词判别完毕后,下一个单词需要重新对字符串去除字符,所以字符串不能直接去除元素,而是每一次需要复制一个新的字符串用于判别。
cpp
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 检查单个单词能否用源字符串(含?)拼写
int canSpellWord(const char *source, const char *word) {
// 复制源字符串为临时副本(避免修改原字符串)
char *temp = (char *)malloc(strlen(source) + 1);
strcpy(temp, source);
int tempLen = strlen(temp);
for (int i = 0; word[i] != '\0'; i++) {
char target = word[i];
int found = 0;
// 在临时副本中查找匹配字符(普通字符或?)
for (int j = 0; j < tempLen; j++) {
if (temp[j] == target || temp[j] == '?') {
// 移除已匹配的字符(后面字符前移)
memmove(&temp[j], &temp[j+1], tempLen - j);
tempLen--;
found = 1;
break;
}
}
// 未找到匹配字符,单词无法拼写
if (!found) {
free(temp);
return 0;
}
}
free(temp);
return 1; // 所有字符匹配成功
}
int main() {
const char *source = "a?bc"; // 源字符串(含万能字符?)
const char *words[] = {"abc", "abd", "acb", "axc", NULL}; // 目标单词列表
for (int i = 0; words[i] != NULL; i++) {
if (canSpellWord(source, words[i])) {
printf("单词\"%s\"可拼写\n", words[i]);
} else {
printf("单词\"%s\"不可拼写\n", words[i]);
}
}
return 0;
}
1.16、翻转句子中单词的顺序。
题目:输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。句子中单词以空格符隔开。为简单起见,标点符号和普通字母一样处理。例如输入"I am a student.",则输出"student. a am I"。
cpp
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 分割字符串为单词数组
int splitWords(char *str, char ***words) {
int count = 0;
char *token = strtok(str, " ");
*words = (char **)malloc(100 * sizeof(char *)); // 假设最多100个单词
while (token != NULL) {
(*words)[count] = (char *)malloc(strlen(token) + 1);
strcpy((*words)[count], token);
count++;
token = strtok(NULL, " ");
}
return count;
}
// 翻转单词顺序
void reverseSentence(char *sentence) {
char *sentenceCopy = (char *)malloc(strlen(sentence) + 1);
strcpy(sentenceCopy, sentence); // 复制原句避免strtok修改
char **words;
int wordCount = splitWords(sentenceCopy, &words);
// 逆序拼接单词
char result[1000] = {0};
for (int i = wordCount - 1; i >= 0; i--) {
strcat(result, words[i]);
if (i > 0) {
strcat(result, " ");
}
free(words[i]); // 释放单词内存
}
free(words);
free(sentenceCopy);
printf("翻转后:%s\n", result);
}
int main() {
char sentence[] = "I am a student.";
printf("原句:%s\n", sentence);
reverseSentence(sentence);
return 0;
}
1.17、在一个字符串中找到第一个只出现一次的字符。如输入abaccdeff,则输出b。
cpp
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
// 找到第一个只出现一次的字符
char findFirstUniqueChar(const char *str) {
if (str == NULL || *str == '\0') {
return '\0'; // 空字符串返回空字符
}
int count[256] = {0}; // ASCII字符共256个,初始化为0
// 第一次遍历:统计每个字符出现次数
for (int i = 0; str[i] != '\0'; i++) {
count[(unsigned char)str[i]]++;
}
// 第二次遍历:找到第一个出现次数为1的字符
for (int i = 0; str[i] != '\0'; i++) {
if (count[(unsigned char)str[i]] == 1) {
return str[i];
}
}
return '\0'; // 无唯一字符
}
int main() {
const char *test1 = "abaccdeff";
const char *test2 = "aabbcc";
const char *test3 = "hello world";
char result1 = findFirstUniqueChar(test1);
char result2 = findFirstUniqueChar(test2);
char result3 = findFirstUniqueChar(test3);
printf("字符串 \"%s\" 中第一个只出现一次的字符:%c\n", test1, result1); // 输出 b
printf("字符串 \"%s\" 中第一个只出现一次的字符:%c\n", test2, result2); // 输出 '\0'(无)
printf("字符串 \"%s\" 中第一个只出现一次的字符:%c\n", test3, result3); // 输出 h
return 0;
}
1.18、符串中找出连续最长的数字串
写一个函数,它的原形是int continumax(char *outputstr,char *intputstr)
功能:
在字符串中找出连续最长的数字串,并把这个串的长度返回,
并把这个最长数字串付给其中一个函数参数outputstr所指内存。
例如:"abcd12345ed125ss123456789"的首地址传给intputstr后,函数将返回9,
outputstr所指的值为123456789
cpp
#include <stdio.h>
#include <string.h>
#include <ctype.h>
// 函数功能:找出字符串中连续最长的数字串,返回长度并将结果存入outputstr
int continumax(char *outputstr, char *inputstr) {
// 输入合法性校验
if (inputstr == NULL || outputstr == NULL) {
*outputstr = '\0';
return 0;
}
int max_len = 0; // 最长数字串长度
int curr_len = 0; // 当前数字串长度
char *max_start = NULL; // 最长数字串起始地址
char *curr_start = NULL; // 当前数字串起始地址
char *p = inputstr; // 遍历指针
// 遍历输入字符串
while (*p != '\0') {
if (isdigit((unsigned char)*p)) { // 当前字符是数字
if (curr_len == 0) {
curr_start = p; // 标记当前数字串起始位置
}
curr_len++;
} else { // 当前字符非数字
// 比较并更新最长数字串信息
if (curr_len > max_len) {
max_len = curr_len;
max_start = curr_start;
}
curr_len = 0; // 重置当前数字串长度
}
p++;
}
// 处理字符串末尾的数字串(避免最后一段数字串未比较)
if (curr_len > max_len) {
max_len = curr_len;
max_start = curr_start;
}
// 将最长数字串复制到输出缓冲区
if (max_len > 0 && max_start != NULL) {
strncpy(outputstr, max_start, max_len);
outputstr[max_len] = '\0'; // 添加字符串结束符,确保有效性
} else {
*outputstr = '\0'; // 无数字串时清空输出
}
return max_len;
}
// 测试主函数
int main() {
// 测试用例1:常规情况
char input1[] = "abcd12345ed125ss123456789";
char output1[100] = {0};
int len1 = continumax(output1, input1);
printf("测试用例1:\n输入字符串:%s\n最长数字串:%s\n长度:%d\n\n", input1, output1, len1);
// 测试用例2:数字串在开头
char input2[] = "1234abc567890";
char output2[100] = {0};
int len2 = continumax(output2, input2);
printf("测试用例2:\n输入字符串:%s\n最长数字串:%s\n长度:%d\n\n", input2, output2, len2);
// 测试用例3:无数字串
char input3[] = "abcdefg";
char output3[100] = {0};
int len3 = continumax(output3, input3);
printf("测试用例3:\n输入字符串:%s\n最长数字串:%s\n长度:%d\n\n", input3, output3, len3);
// 测试用例4:全为数字串
char input4[] = "123456789";
char output4[100] = {0};
int len4 = continumax(output4, input4);
printf("测试用例4:\n输入字符串:%s\n最长数字串:%s\n长度:%d\n\n", input4, output4, len4);
return 0;
}
1.19、旋转字符串
定义字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部。
如把字符串abcdef左旋转2位得到字符串cdefab。请实现字符串左旋转的函数。
要求时间对长度为n的字符串操作的复杂度为O(n),辅助内存为O(1)。
cpp
#include <stdio.h>
#include <string.h>
// 翻转字符串中[start, end]区间的字符
void reverse(char *str, int start, int end) {
while (start < end) {
char temp = str[start];
str[start] = str[end];
str[end] = temp;
start++;
end--;
}
}
// 字符串左旋转k位(原地操作)
void leftRotateString(char *str, int k) {
if (str == NULL || k <= 0) return;
int len = strlen(str);
k %= len; // 处理k大于字符串长度的情况(如k=8,len=6,等价于k=2)
reverse(str, 0, k-1); // 翻转前k个字符
reverse(str, k, len-1); // 翻转剩余字符
reverse(str, 0, len-1); // 整体翻转
}
int main() {
char str[] = "abcdef";
int k = 2;
printf("原字符串:%s\n", str);
leftRotateString(str, k);
printf("左旋转%d位后:%s\n", k, str); // 输出:cdefab
// 测试k大于字符串长度的情况
char str2[] = "abcdef";
leftRotateString(str2, 8);
printf("左旋转8位后:%s\n", str2); // 输出:cdefab(等价于左旋转2位)
return 0;
}
1.20、匹配字符串
实现一个挺高级的字符匹配算法:
给一串很长字符串,要求找到符合要求的字符串,例如目的串:123
1******3***2 ,12*****3这些都要找出来
cpp
#include <stdio.h>
#include <string.h>
// 查找长字符串中按顺序匹配目标串的所有起始-结束位置
void findMatchedSubsequence(const char *s, const char *target) {
if (s == NULL || target == NULL || *target == '\0') {
printf("输入无效\n");
return;
}
int s_len = strlen(s);
int t_len = strlen(target);
if (t_len > s_len) {
printf("无匹配结果\n");
return;
}
printf("匹配结果:\n");
// 遍历长字符串,尝试从每个位置开始匹配
for (int start = 0; start <= s_len - t_len; start++) {
int j = 0; // 目标串指针
int end = -1; // 匹配结束位置
// 从start开始遍历长字符串
for (int i = start; i < s_len; i++) {
if (s[i] == target[j]) {
j++;
if (j == t_len) { // 目标串全部匹配完成
end = i;
break;
}
}
}
if (end != -1) { // 输出匹配的区间和子串
printf("起始位置:%d,结束位置:%d,子串:", start, end);
for (int k = start; k <= end; k++) {
printf("%c", s[k]);
}
printf("\n");
}
}
}
int main() {
const char *long_str = "1******3***2,12*****3";
const char *target = "123";
printf("长字符串:%s\n目标串:%s\n", long_str, target);
findMatchedSubsequence(long_str, target);
return 0;
}
1.21、字符串的排列。
题目:输入一个字符串,打印出该字符串中字符的所有排列。
例如输入字符串abc,则输出由字符a、b、c所能排列出来的所有字符串。
abc、acb、bac、bca、cab和cba。
cpp
#include <stdio.h>
#include <string.h>
// 交换字符
void swap(char *a, char *b) {
char temp = *a;
*a = *b;
*b = temp;
}
// 递归生成排列
void permute(char *str, int start, int len) {
if (start == len - 1) { // 递归终止:处理到最后一个字符
printf("%s\n", str);
return;
}
for (int i = start; i < len; i++) {
// 跳过重复字符(可选,避免重复排列)
if (i != start && str[i] == str[start]) {
continue;
}
swap(&str[start], &str[i]); // 交换当前字符与后面字符
permute(str, start + 1, len); // 递归处理剩余子串
swap(&str[start], &str[i]); // 回溯:恢复原字符串
}
}
// 生成字符串的所有排列
void permutation(char *str) {
if (str == NULL || *str == '\0') {
return;
}
int len = strlen(str);
permute(str, 0, len);
}
int main() {
char str[] = "abc";
printf("字符串\"%s\"的所有排列:\n", str);
permutation(str);
return 0;
}
1.22、最长公共字串。
题目:如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中, 则字符串一称之为字符串二的子串。 注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中。 请编写一个函数,输入两个字符串,求它们的最长公共子串,并打印出最长公共子串。 例如:输入两个字符串BDCABA和ABCBDAB,字符串BCBA和BDAB都是是它们的最长公共子串, 则输出它们的长度4,并打印任意一个子串。
cpp
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX_LEN 100
// 求最长公共子序列长度并回溯子序列
int longestCommonSubsequence(char *s1, char *s2, char *result) {
int n = strlen(s1);
int m = strlen(s2);
int dp[MAX_LEN][MAX_LEN] = {0}; // 动态规划数组
// 填充dp数组
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (s1[i-1] == s2[j-1]) {
dp[i][j] = dp[i-1][j-1] + 1;
} else {
dp[i][j] = (dp[i-1][j] > dp[i][j-1]) ? dp[i-1][j] : dp[i][j-1];
}
}
}
// 回溯构造最长公共子序列
int i = n, j = m, idx = 0;
while (i > 0 && j > 0) {
if (s1[i-1] == s2[j-1]) {
result[idx++] = s1[i-1];
i--;
j--;
} else if (dp[i-1][j] > dp[i][j-1]) {
i--;
} else {
j--;
}
}
// 反转结果(回溯得到的是逆序)
for (int k = 0; k < idx / 2; k++) {
char temp = result[k];
result[k] = result[idx - 1 - k];
result[idx - 1 - k] = temp;
}
result[idx] = '\0'; // 添加结束符
return dp[n][m]; // 返回最长长度
}
int main() {
char s1[] = "BDCABA";
char s2[] = "ABCBDAB";
char result[MAX_LEN] = {0};
int len = longestCommonSubsequence(s1, s2, result);
printf("最长公共子序列长度:%d\n", len);
printf("最长公共子序列:%s\n", result); // 输出BCBA或BDAB等
return 0;
}
1.23、在字符串中删除特定的字符。
题目:输入两个字符串,从第一字符串中删除第二个字符串中所有的字符。
例如,输入"They are students."和"aeiou",
则删除之后的第一个字符串变成"Thy r stdnts."。
分析:这是一道微软面试题。在微软的常见面试题中,与字符串相关的题目占了很大的一部分,
因为写程序操作字符串能很好的反映我们的编程基本功。
cpp
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
void deleteChars(char *str, const char *del) {
if (str == NULL || del == NULL) return;
bool delMap[256] = {false}; // 标记需删除的字符
int lenDel = strlen(del);
// 标记第二个字符串中的字符
for (int i = 0; i < lenDel; i++) {
delMap[(unsigned char)del[i]] = true;
}
int j = 0; // 新字符串的索引
int lenStr = strlen(str);
// 遍历第一个字符串,过滤需删除的字符
for (int i = 0; i < lenStr; i++) {
if (!delMap[(unsigned char)str[i]]) {
str[j++] = str[i]; // 保留字符,覆盖原字符串
}
}
str[j] = '\0'; // 添加字符串结束符
}
int main() {
char str[] = "They are students.";
const char *del = "aeiou";
printf("原字符串:%s\n", str);
deleteChars(str, del);
printf("删除后:%s\n", str); // 输出:Thy r stdnts.
return 0;
}
1.24、对策字符串的最大长度。
题目:输入一个字符串,输出该字符串中对称的子字符串的最大长度。 比如输入字符串"google",由于该字符串里最长的对称子字符串是"goog",因此输出4。 分析:可能很多人都写过判断一个字符串是不是对称的函数,这个题目可以看成是该函数的加强版。
cpp
#include <stdio.h>
#include <string.h>
// 中心扩展函数:返回以left和right为中心的回文长度
int expandAroundCenter(const char *s, int left, int right) {
while (left >= 0 && right < strlen(s) && s[left] == s[right]) {
left--;
right++;
}
return right - left - 1; // 回文长度
}
// 求最长回文子串的长度
int longestPalindromeLength(const char *s) {
if (s == NULL || *s == '\0') return 0;
int max_len = 1; // 单个字符本身是回文,最小长度为1
int n = strlen(s);
for (int i = 0; i < n; i++) {
// 奇数长度回文(中心为i)
int len1 = expandAroundCenter(s, i, i);
// 偶数长度回文(中心为i和i+1)
int len2 = expandAroundCenter(s, i, i+1);
// 更新最大长度
int curr_max = len1 > len2 ? len1 : len2;
if (curr_max > max_len) {
max_len = curr_max;
}
}
return max_len;
}
int main() {
const char *s1 = "google";
printf("字符串\"%s\"的最长对称子串长度:%d\n", s1, longestPalindromeLength(s1)); // 输出4
const char *s2 = "abcba";
printf("字符串\"%s\"的最长对称子串长度:%d\n", s2, longestPalindromeLength(s2)); // 输出5
const char *s3 = "abccba";
printf("字符串\"%s\"的最长对称子串长度:%d\n", s3, longestPalindromeLength(s3)); // 输出6
return 0;
}
1.25、给出一个函数来复制两个字符串A和B。
字符串A的后几个字节和字符串B的前几个字节重叠。 分析:记住,这种题目往往就是考你对边界的考虑情况。
cpp
#include <stdio.h>
#include <string.h>
// 复制字符串A到B(处理重叠情况)
void safeStrcpy(char *B, const char *A, int len) {
if (B == NULL || A == NULL || len <= 0) return;
// 判断是否重叠:若B的起始地址在A的范围内且重叠
if (B >= A && B < A + len) {
// 从后往前复制
for (int i = len - 1; i >= 0; i--) {
B[i] = A[i];
}
} else {
// 无重叠,正常从前往后复制
for (int i = 0; i < len; i++) {
B[i] = A[i];
}
}
B[len] = '\0'; // 添加结束符(若复制的是字符串)
}
int main() {
// 测试重叠情况:A和B重叠,A="abcdef", B指向A+2(即"cdef"位置)
char buf[20] = "abcdef";
char *A = buf;
char *B = buf + 2; // B起始于A的第3个字符(重叠区域)
int len = strlen(A); // 复制长度为A的长度
printf("复制前:A=%s, B=%s\n", A, B);
safeStrcpy(B, A, len);
printf("复制后:A=%s, B=%s\n", A, B); // B变为"abcdef",A因重叠变为"ababcdef"
return 0;
}
1.26、已知一个字符串,比如asderwsde,寻找其中的一个子字符串比如sde的个数, 如果没有返回0,有的话返回子字符串的个数。
cpp
#include <stdio.h>
#include <string.h>
// 统计子字符串sub在str中的出现次数
int countSubstring(const char *str, const char *sub) {
if (str == NULL || sub == NULL || strlen(sub) == 0 || strlen(str) < strlen(sub)) {
return 0;
}
int count = 0;
int strLen = strlen(str);
int subLen = strlen(sub);
// 遍历主字符串,剩余长度需≥子串长度
for (int i = 0; i <= strLen - subLen; i++) {
// 逐字符匹配子串
int match = 1;
for (int j = 0; j < subLen; j++) {
if (str[i + j] != sub[j]) {
match = 0;
break;
}
}
if (match) {
count++;
// 可选:若子串可能重叠(如"aaa"中找"aa"),则i++;否则i += subLen
// i += subLen; // 非重叠匹配
i++; // 重叠匹配(如"aaaa"中"aa"算3次)
}
}
return count;
}
int main() {
const char *str = "asderwsde";
const char *sub = "sde";
int count = countSubstring(str, sub);
printf("子串\"%s\"出现次数:%d\n", sub, count); // 输出2
const char *str2 = "aaaaa";
const char *sub2 = "aa";
count = countSubstring(str2, sub2);
printf("子串\"%s\"出现次数:%d\n", sub2, count); // 重叠匹配输出4,非重叠输出2
return 0;
}
1.27、判断一字符串是不是对称的,如:abccba
cpp
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
// 判断字符串是否对称(回文)
bool isSymmetric(const char *str) {
if (str == NULL) return false; // 空指针处理
int len = strlen(str);
if (len <= 1) return true; // 空串或单字符为对称
int left = 0;
int right = len - 1;
while (left < right) {
if (str[left] != str[right]) {
return false; // 字符不相等,不对称
}
left++;
right--;
}
return true; // 所有对应字符相等,对称
}
int main() {
const char *test1 = "abccba";
const char *test2 = "abcba";
const char *test3 = "abcdcba";
const char *test4 = "abcdef";
printf("字符串\"%s\"是否对称:%s\n", test1, isSymmetric(test1) ? "是" : "否"); // 是
printf("字符串\"%s\"是否对称:%s\n", test2, isSymmetric(test2) ? "是" : "否"); // 是
printf("字符串\"%s\"是否对称:%s\n", test3, isSymmetric(test3) ? "是" : "否"); // 是
printf("字符串\"%s\"是否对称:%s\n", test4, isSymmetric(test4) ? "是" : "否"); // 否
return 0;
}
1.28、前面的非'*'字符后移,但不能改变非'*'字符的先后顺序,函数返回串中字符'*'的数量。 如原始串为:ab**cd**e*12, 处理后为*****abcde12,函数并返回值为5。
cpp
#include <stdio.h>
#include <string.h>
int moveStarToFront(char *str) {
if (str == NULL || *str == '\0') return 0;
int len = strlen(str);
int count = 0; // 统计*的数量
int j = len - 1; // 非*字符的目标位置指针
// 第一步:统计*的数量,并将非*字符移到末尾
for (int i = len - 1; i >= 0; i--) {
if (str[i] != '*') {
str[j--] = str[i]; // 非*字符移到末尾
} else {
count++; // 统计*的数量
}
}
// 第二步:在前count个位置填充*
for (int i = 0; i < count; i++) {
str[i] = '*';
}
return count;
}
int main() {
char str[] = "ab**cd**e*12";
printf("原字符串:%s\n", str);
int starCount = moveStarToFront(str);
printf("处理后:%s\n", str);
printf("*的数量:%d\n", starCount); // 输出5
return 0;
}
1.29、求最大连续递增数字串(如"ads3sl456789DF3456ld345AA"中的"456789")
cpp
#include <stdio.h>
#include <string.h>
#include <ctype.h>
// 找出最大连续递增数字串,结果存入outputstr,返回长度
int maxContinuousNumStr(char *outputstr, const char *inputstr) {
if (inputstr == NULL || outputstr == NULL) {
*outputstr = '\0';
return 0;
}
int max_len = 0; // 最长数字串长度
int curr_len = 0; // 当前数字串长度
const char *max_start = NULL; // 最长数字串起始地址
const char *curr_start = NULL; // 当前数字串起始地址
const char *p = inputstr;
while (*p != '\0') {
if (isdigit((unsigned char)*p)) {
if (curr_len == 0) {
curr_start = p; // 标记当前数字串起始
}
curr_len++;
} else {
// 更新最长数字串信息
if (curr_len > max_len) {
max_len = curr_len;
max_start = curr_start;
}
curr_len = 0; // 重置当前长度
}
p++;
}
// 处理字符串末尾的数字串
if (curr_len > max_len) {
max_len = curr_len;
max_start = curr_start;
}
// 复制结果到输出缓冲区
if (max_len > 0 && max_start != NULL) {
strncpy(outputstr, max_start, max_len);
outputstr[max_len] = '\0';
} else {
*outputstr = '\0';
}
return max_len;
}
// 测试函数
int main() {
const char *input = "ads3sl456789DF3456ld345AA";
char output[20] = {0};
int len = maxContinuousNumStr(output, input);
printf("最长连续数字串:%s\n", output); // 输出456789
printf("长度:%d\n", len); // 输出6
return 0;
}
1.30、实现strstr功能,即在父串中寻找子串首次出现的位置。
cpp
#include <stdio.h>
#include <string.h>
char *my_strstr(const char *haystack, const char *needle) {
// 子串为空,返回父串起始位置
if (*needle == '\0') {
return (char *)haystack;
}
int haystack_len = strlen(haystack);
int needle_len = strlen(needle);
// 父串比子串短,直接返回NULL
if (haystack_len < needle_len) {
return NULL;
}
// 遍历父串,i为父串起始位置
for (int i = 0; i <= haystack_len - needle_len; i++) {
int j = 0;
// 逐字符匹配子串
while (j < needle_len && haystack[i + j] == needle[j]) {
j++;
}
// 子串全部匹配成功
if (j == needle_len) {
return (char *)(haystack + i);
}
}
// 未找到子串
return NULL;
}
int main() {
const char *haystack = "hello world, hello c";
const char *needle1 = "hello";
const char *needle2 = "world";
const char *needle3 = "test";
char *result1 = my_strstr(haystack, needle1);
char *result2 = my_strstr(haystack, needle2);
char *result3 = my_strstr(haystack, needle3);
if (result1) {
printf("子串\"%s\"首次出现位置:%ld\n", needle1, result1 - haystack); // 输出0
} else {
printf("未找到子串\"%s\"\n", needle1);
}
if (result2) {
printf("子串\"%s\"首次出现位置:%ld\n", needle2, result2 - haystack); // 输出6
} else {
printf("未找到子串\"%s\"\n", needle2);
}
if (result3) {
printf("子串\"%s\"首次出现位置:%ld\n", needle3, result3 - haystack);
} else {
printf("未找到子串\"%s\"\n", needle3); // 输出未找到
}
return 0;
}
1.31、1.a~z包括大小写与0~9组成的N个数
用最快的方式把其中重复的元素挑出来。
cpp
#include <stdio.h>
#include <ctype.h>
// 字符映射到数组索引(0-61)
int charToIndex(char c) {
if (isdigit(c)) {
return c - '0'; // 数字0-9 → 0-9
} else if (islower(c)) {
return 10 + (c - 'a'); // 小写字母a-z → 10-35
} else if (isupper(c)) {
return 36 + (c - 'A'); // 大写字母A-Z → 36-61
}
return -1; // 非法字符(题目保证输入为a-z/A-Z/0-9,此处容错)
}
// 找出重复元素
void findDuplicates(char *arr, int n) {
int count[62] = {0}; // 标记数组,初始化为0
printf("重复元素:");
// 第一步:统计每个字符出现次数
for (int i = 0; i < n; i++) {
int idx = charToIndex(arr[i]);
if (idx != -1) {
count[idx]++;
}
}
// 第二步:输出重复元素(只输出一次)
int printed[62] = {0}; // 避免重复输出同一字符
for (int i = 0; i < n; i++) {
int idx = charToIndex(arr[i]);
if (idx != -1 && count[idx] >= 2 && !printed[idx]) {
printf("%c ", arr[i]);
printed[idx] = 1; // 标记已输出
}
}
printf("\n");
}
int main() {
// 测试用例:包含重复的大小写字母和数字
char arr[] = {'a', 'B', '3', 'a', 'Z', '3', 'B', 'x', '9', 'Z'};
int n = sizeof(arr) / sizeof(arr[0]);
findDuplicates(arr, n); // 输出:a B 3 Z
return 0;
}
1.32、用C语言实现函数void * memmove(void *dest, const void *src, size_t n)。
memmove函数的功能是拷贝src所指的内存内容前n个字节到dest所指的地址上。
分析:
由于可以把任何类型的指针赋给void类型的指针
这个函数主要是实现各种数据类型的拷贝。
cpp
#include <stddef.h> // 包含size_t定义
void *memmove(void *dest, const void *src, size_t n) {
if (dest == NULL || src == NULL || n == 0) {
return dest; // 空指针或n=0直接返回
}
unsigned char *d = (unsigned char *)dest;
const unsigned char *s = (const unsigned char *)src;
// 判断是否重叠:dest在src之后且重叠,从后往前拷贝
if (d > s && d < s + n) {
d += n - 1;
s += n - 1;
while (n--) {
*d-- = *s--;
}
} else { // 无重叠或dest在src之前,从前往后拷贝
while (n--) {
*d++ = *s++;
}
}
return dest;
}
// 测试函数
#include <stdio.h>
#include <string.h>
int main() {
// 测试重叠情况:dest在src之后
char buf[20] = "abcdefghijk";
memmove(buf + 2, buf, 5); // 拷贝"abcde"到buf+2位置
printf("重叠拷贝结果:%s\n", buf); // 输出:ababcdefghijk
// 测试无重叠情况
char src[] = "hello world";
char dest[20];
memmove(dest, src, strlen(src) + 1); // 包含结束符
printf("无重叠拷贝结果:%s\n", dest); // 输出:hello world
return 0;
}
1.33、编码实现字符串转整型的函数(实现函数atoi的功能)
如将字符
串 "+123"123, "-0123"-123, "123CS45"123, "123.45CS"123, "CS123.45"0
cpp
#include <stdio.h>
#include <ctype.h> // 用于isdigit判断
int my_atoi(const char *str) {
if (str == NULL || *str == '\0') {
return 0; // 空指针或空串返回0
}
int result = 0;
int sign = 1; // 符号位,默认正
int i = 0;
// 跳过前导空白(可选,题目示例未包含空白,可保留增强鲁棒性)
while (str[i] == ' ' || str[i] == '\t') {
i++;
}
// 处理符号位
if (str[i] == '+' || str[i] == '-') {
sign = (str[i] == '-') ? -1 : 1;
i++;
}
// 提取数字字符,遇到非数字终止
while (str[i] != '\0' && isdigit(str[i])) {
result = result * 10 + (str[i] - '0'); // 逐位累加
i++;
}
return result * sign; // 应用符号
}
// 测试函数
int main() {
printf("\"+123\" → %d\n", my_atoi("+123")); // 123
printf("\"-0123\" → %d\n", my_atoi("-0123")); // -123
printf("\"123CS45\" → %d\n", my_atoi("123CS45")); // 123
printf("\"123.45CS\" → %d\n", my_atoi("123.45CS"));// 123
printf("\"CS123.45\" → %d\n", my_atoi("CS123.45"));// 0
printf("\"\" → %d\n", my_atoi("")); // 0
printf("\" -456abc\" → %d\n", my_atoi(" -456abc"));// -456(含前导空白)
return 0;
}
1.34、删除字符串中的数字并压缩字符串。
如字符串"abc123de4fg56"处理后变为"abcdefg"。注意空间和效率。
(下面的算法只需要一次遍历,不需要开辟新空间,时间复杂度为O(N))
cpp
#include <stdio.h>
#include <ctype.h> // 用于isdigit判断
void removeDigitsAndCompress(char *str) {
if (str == NULL || *str == '\0') return;
int j = 0; // 非数字字符的目标位置指针
for (int i = 0; str[i] != '\0'; i++) {
if (!isdigit(str[i])) { // 非数字字符保留
str[j++] = str[i];
}
}
str[j] = '\0'; // 截断字符串,完成压缩
}
int main() {
char str[] = "abc123de4fg56";
printf("原字符串:%s\n", str);
removeDigitsAndCompress(str);
printf("处理后:%s\n", str); // 输出:abcdefg
// 测试全数字情况
char str2[] = "123456";
removeDigitsAndCompress(str2);
printf("全数字处理后:%s(空串)\n", str2);
// 测试无数字情况
char str3[] = "abcdefg";
removeDigitsAndCompress(str3);
printf("无数字处理后:%s\n", str3); // 输出:abcdefg
return 0;
}
1.35、输入一个表示整数的字符串,把该字符串转换成整数并输出。
例如输入字符串"345",则输出整数345。
cpp
#include <iostream>
#include <string>
#include <climits> // 用于INT_MAX和INT_MIN
using namespace std;
int strToInt(const string& str) {
if (str.empty()) return 0; // 处理空字符串
int sign = 1; // 符号位,默认正
int result = 0;
int i = 0;
// 处理符号位
if (str[i] == '+' || str[i] == '-') {
sign = (str[i] == '+') ? 1 : -1;
i++;
}
// 遍历数字字符并转换
while (i < str.size()) {
// 非数字字符,终止转换
if (str[i] < '0' || str[i] > '9') {
break;
}
// 处理溢出(可选,根据需求)
if (result > INT_MAX / 10 || (result == INT_MAX / 10 && (str[i] - '0') > INT_MAX % 10)) {
return (sign == 1) ? INT_MAX : INT_MIN;
}
result = result * 10 + (str[i] - '0');
i++;
}
return result * sign;
}
// 测试示例
int main() {
string s1 = "345";
cout << strToInt(s1) << endl; // 输出345
string s2 = "-123";
cout << strToInt(s2) << endl; // 输出-123
string s3 = "+456";
cout << strToInt(s3) << endl; // 输出456
string s4 = "12a3";
cout << strToInt(s4) << endl; // 输出12(遇到非数字字符终止)
string s5 = "2147483648"; // 超过INT_MAX
cout << strToInt(s5) << endl; // 输出INT_MAX(2147483647)
return 0;
}
二:数组
2.1、求子数组的最大和
题目: 输入一个整形数组,数组里有正数也有负数。 数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。 求所有子数组的和的最大值。要求时间复杂度为O(n)。 例如输入的数组为1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为3, 10, -4, 7, 2, 因此输出为该子数组的和18。
cpp
#include <iostream>
#include <vector>
#include <algorithm> // 用于max函数
using namespace std;
int maxSubArraySum(const vector<int>& nums) {
if (nums.empty()) return 0; // 处理空数组
int current_max = nums[0];
int global_max = nums[0];
for (int i = 1; i < nums.size(); ++i) {
// 选择:要么从当前元素重新开始,要么加入前一个子数组
current_max = max(nums[i], current_max + nums[i]);
// 更新全局最大值
global_max = max(global_max, current_max);
}
return global_max;
}
// 测试示例
int main() {
vector<int> nums = {1, -2, 3, 10, -4, 7, 2, -5};
cout << "子数组的最大和为:" << maxSubArraySum(nums) << endl; // 输出18
// 测试边界情况:全负数数组
vector<int> nums2 = {-3, -1, -2};
cout << "全负数数组的最大子数组和:" << maxSubArraySum(nums2) << endl; // 输出-1
return 0;
}
2.2、输入一个已经按升序排序过的数组和一个数字,
在数组中查找两个数,使得它们的和正好是输入的那个数字。
要求时间复杂度是O(n)。如果有多对数字的和等于输入的数字,输出任意一对即可。
例如输入数组1、2、4、7、11、15和数字15。由于4+11=15,因此输出4和11。
cpp
#include <iostream>
#include <vector>
using namespace std;
vector<int> findTwoNumbersWithSum(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while (left < right) {
int currentSum = nums[left] + nums[right];
if (currentSum == target) {
return {nums[left], nums[right]}; // 找到目标数对,返回
} else if (currentSum < target) {
left++; // 和过小,左指针右移
} else {
right--; // 和过大,右指针左移
}
}
return {}; // 未找到,返回空
}
// 测试示例
int main() {
vector<int> nums = {1, 2, 4, 7, 11, 15};
int target = 15;
vector<int> result = findTwoNumbersWithSum(nums, target);
if (!result.empty()) {
cout << "找到的两个数:" << result[0] << " 和 " << result[1] << endl;
} else {
cout << "未找到符合条件的数对" << endl;
}
return 0;
}
2.3、求一个数组的最长递减子序列 比如{9,4,3,2,5,4,3,2}的最长递减子序列为{9,5,4,3,2}
cpp
#include <iostream>
#include <vector>
#include <algorithm> // 用于lower_bound/upper_bound
using namespace std;
// 查找最长递减子序列并返回其元素
vector<int> longestDecreasingSubsequence(vector<int>& nums) {
if (nums.empty()) return {};
vector<int> tails; // 存储长度为i+1的LDS的最后一个元素
vector<int> prevIndices; // 记录每个元素在tails中的前驱索引(用于回溯)
vector<int> indices; // 记录tails中元素对应的原数组索引
for (int i = 0; i < nums.size(); ++i) {
int num = nums[i];
// 用二分查找找到tails中第一个 <= num的位置(递减序列,需反向查找)
auto it = upper_bound(tails.rbegin(), tails.rend(), num);
int pos = tails.rend() - it;
if (pos == tails.size()) {
// num比tails所有元素小,追加到末尾
tails.push_back(num);
// 前驱索引为前一个tails元素的索引(若存在)
prevIndices.push_back(indices.empty() ? -1 : indices.back());
indices.push_back(i);
} else {
// 替换tails[pos]为num,优化子序列
tails[pos] = num;
indices[pos] = i;
// 前驱索引为pos-1的indices(若pos>0)
prevIndices.push_back(pos > 0 ? indices[pos-1] : -1);
}
}
// 回溯获取具体子序列
vector<int> result;
int currentIdx = indices.back();
while (currentIdx != -1) {
result.push_back(nums[currentIdx]);
currentIdx = prevIndices[currentIdx];
}
reverse(result.begin(), result.end()); // 回溯结果是逆序,需反转
return result;
}
// 测试示例
int main() {
vector<int> nums = {9, 4, 3, 2, 5, 4, 3, 2};
vector<int> lds = longestDecreasingSubsequence(nums);
cout << "最长递减子序列:";
for (int num : lds) {
cout << num << " ";
}
cout << "\n长度:" << lds.size() << endl; // 输出5
return 0;
}
2.4、一个数组是由一个递减数列左移若干位形成的,比如{4,3,2,1,6,5}
是由{6,5,4,3,2,1}左移两位形成的,在这种数组中查找某一个数。
cpp
#include <iostream>
#include <vector>
using namespace std;
int searchInRotatedDecreasingArray(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 避免溢出
if (nums[mid] == target) {
return mid; // 找到目标,返回索引
}
// 左半部分递减有序(nums[left] >= nums[mid])
if (nums[left] >= nums[mid]) {
// 目标在左半部分的范围内
if (target <= nums[left] && target >= nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
}
// 右半部分递减有序(nums[mid] >= nums[right])
else {
// 目标在右半部分的范围内
if (target <= nums[mid] && target >= nums[right]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1; // 未找到目标
}
// 测试示例
int main() {
vector<int> nums = {4, 3, 2, 1, 6, 5};
int target1 = 6;
int target2 = 3;
int target3 = 7;
cout << "目标值" << target1 << "的索引:" << searchInRotatedDecreasingArray(nums, target1) << endl; // 输出4
cout << "目标值" << target2 << "的索引:" << searchInRotatedDecreasingArray(nums, target2) << endl; // 输出1
cout << "目标值" << target3 << "的索引:" << searchInRotatedDecreasingArray(nums, target3) << endl; // 输出-1
return 0;
}
2.5、调整数组顺序使奇数位于偶数前面。
题目:输入一个整数数组,调整数组中数字的顺序,使得所有奇数位于数组的前半部分,
所有偶数位于数组的后半部分。要求时间复杂度为O(n)。
cpp
#include <iostream>
#include <vector>
using namespace std;
void reorderOddEven(vector<int>& nums) {
int left = 0;
int right = nums.size() - 1;
while (left < right) {
// 左指针找偶数(跳过奇数)
while (left < right && nums[left] % 2 != 0) {
left++;
}
// 右指针找奇数(跳过偶数)
while (left < right && nums[right] % 2 == 0) {
right--;
}
// 交换奇偶元素
if (left < right) {
swap(nums[left], nums[right]);
}
}
}
// 测试示例
int main() {
vector<int> nums = {1, 2, 3, 4, 5, 6, 7};
reorderOddEven(nums);
cout << "调整后的数组:";
for (int num : nums) {
cout << num << " ";
}
cout << endl; // 输出:1 7 3 5 4 6 2(或其他满足条件的结果)
return 0;
}
2.6、找出数组中两个只出现一次的数字
题目:一个整型数组里除了两个数字之外,其他的数字都出现了两次。
请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
分析:这是一道很新颖的关于位运算的面试题。
cpp
#include <iostream>
#include <vector>
using namespace std;
vector<int> findTwoSingleNumbers(vector<int>& nums) {
int xor_sum = 0;
// 第一步:整体异或,得到两个目标数的异或结果
for (int num : nums) {
xor_sum ^= num;
}
// 第二步:找到最低位的1(用于分组)
int mask = 1;
while ((xor_sum & mask) == 0) {
mask <<= 1;
}
// 第三步:分组异或
int a = 0, b = 0;
for (int num : nums) {
if (num & mask) {
a ^= num; // 该位为1的组异或结果
} else {
b ^= num; // 该位为0的组异或结果
}
}
return {a, b};
}
// 测试示例
int main() {
vector<int> nums = {1, 2, 1, 3, 2, 5};
vector<int> result = findTwoSingleNumbers(nums);
cout << "只出现一次的两个数字:" << result[0] << " 和 " << result[1] << endl; // 输出3和5
nums = {4, 1, 2, 1, 2, 3};
result = findTwoSingleNumbers(nums);
cout << "只出现一次的两个数字:" << result[0] << " 和 " << result[1] << endl; // 输出4和3
return 0;
}
2.7、输入一个正整数数组,将它们连接起来排成一个数,输出能排出的所有数字中最小的一个。
例如输入数组{32, 321},则输出这两个能排成的最小数字32132。
请给出解决问题的算法,并证明该算法。
cpp
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
// 自定义比较函数:a + b < b + a 时,a 排在 b 前
bool compare(const string& a, const string& b) {
return a + b < b + a;
}
string minNumber(vector<int>& nums) {
// 将数字转换为字符串
vector<string> strs;
for (int num : nums) {
strs.push_back(to_string(num));
}
// 按自定义规则排序
sort(strs.begin(), strs.end(), compare);
// 拼接结果
string result;
for (const string& s : strs) {
result += s;
}
// 处理全0情况(如输入{0,0},结果应为"0")
if (!result.empty() && result[0] == '0') {
return "0";
}
return result;
}
// 测试示例
int main() {
vector<int> nums1 = {32, 321};
cout << minNumber(nums1) << endl; // 输出32132
vector<int> nums2 = {10, 2};
cout << minNumber(nums2) << endl; // 输出102
vector<int> nums3 = {0, 0};
cout << minNumber(nums3) << endl; // 输出0
return 0;
}
2.8、旋转数组中的最小元素。
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个排好序的数组的一个旋转,
输出旋转数组的最小元素。例如数组{3, 4, 5, 1, 2}为{1, 2, 3, 4, 5}的一个旋转,该数组的最小值为1。
cpp
#include <iostream>
#include <vector>
using namespace std;
int minNumberInRotateArray(vector<int>& rotateArray) {
int left = 0;
int right = rotateArray.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2; // 避免溢出
if (rotateArray[mid] > rotateArray[right]) {
// 最小元素在mid右侧
left = mid + 1;
} else if (rotateArray[mid] < rotateArray[right]) {
// 最小元素在mid左侧或就是mid
right = mid;
} else {
// 处理重复元素,缩小右边界
right--;
}
}
return rotateArray[left];
}
// 测试示例
int main() {
vector<int> arr1 = {3, 4, 5, 1, 2};
cout << "最小元素:" << minNumberInRotateArray(arr1) << endl; // 输出1
vector<int> arr2 = {2, 2, 2, 0, 1};
cout << "最小元素:" << minNumberInRotateArray(arr2) << endl; // 输出0
vector<int> arr3 = {1, 2, 3, 4, 5}; // 旋转0个元素(原数组)
cout << "最小元素:" << minNumberInRotateArray(arr3) << endl; // 输出1
return 0;
}
2.9、数组中超过出现次数超过一半的数字
题目:数组中有一个数字出现的次数超过了数组长度的一半,找出这个数字。
cpp
#include <iostream>
#include <vector>
using namespace std;
int majorityElement(vector<int>& nums) {
int candidate = nums[0];
int count = 1;
for (int i = 1; i < nums.size(); ++i) {
if (nums[i] == candidate) {
count++;
} else {
count--;
if (count == 0) {
candidate = nums[i];
count = 1;
}
}
}
return candidate;
}
// 测试示例
int main() {
vector<int> nums1 = {1, 2, 3, 2, 2, 2, 5, 4, 2};
cout << "多数元素:" << majorityElement(nums1) << endl; // 输出2
vector<int> nums2 = {3, 3, 4};
cout << "多数元素:" << majorityElement(nums2) << endl; // 输出3
return 0;
}
2.10、一个int数组,里面数据无任何限制,要求求出所有这样的数a[i], 其左边的数都小于等于它,右边的数都大于等于它。
能否只用一个额外数组和少量其它空间实现。
cpp
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
vector<int> findSpecialNumbers(vector<int>& nums) {
if (nums.empty()) return {};
int n = nums.size();
vector<int> right_min(n); // 存储每个位置右侧(含自身)的最小值
vector<int> result;
// 第一步:计算right_min数组(从右往左遍历)
right_min[n-1] = nums[n-1];
for (int i = n-2; i >= 0; --i) {
right_min[i] = min(nums[i], right_min[i+1]);
}
// 第二步:实时计算left_max,并筛选符合条件的元素(从左往右遍历)
int left_max = INT_MIN;
for (int i = 0; i < n; ++i) {
left_max = max(left_max, nums[i]); // left_max为0..i的最大值
// 左侧最大值等于当前数(左侧都≤它),且右侧最小值等于当前数(右侧都≥它)
if (left_max == nums[i] && right_min[i] == nums[i]) {
result.push_back(nums[i]);
}
}
return result;
}
// 测试示例
int main() {
vector<int> nums1 = {2, 3, 1, 8, 9, 10, 5, 7};
vector<int> res1 = findSpecialNumbers(nums1);
cout << "符合条件的数:";
for (int num : res1) cout << num << " "; // 输出:2 8 9 10 7
cout << endl;
vector<int> nums2 = {1, 2, 3, 4, 5};
vector<int> res2 = findSpecialNumbers(nums2);
cout << "符合条件的数:";
for (int num : res2) cout << num << " "; // 输出:1 2 3 4 5
cout << endl;
vector<int> nums3 = {5, 4, 3, 2, 1};
vector<int> res3 = findSpecialNumbers(nums3);
cout << "符合条件的数:";
for (int num : res3) cout << num << " "; // 输出:1
cout << endl;
return 0;
}
2.11、给定一个存放整数的数组,重新排列数组使得数组左边为奇数,右边为偶数。
要求:空间复杂度O(1),时间复杂度为O(n)。
cpp
#include <iostream>
#include <vector>
using namespace std;
void reorderOddEven(vector<int>& nums) {
int left = 0;
int right = nums.size() - 1;
while (left < right) {
// 左指针找偶数(跳过奇数)
while (left < right && nums[left] % 2 != 0) {
left++;
}
// 右指针找奇数(跳过偶数)
while (left < right && nums[right] % 2 == 0) {
right--;
}
// 交换奇偶元素
if (left < right) {
swap(nums[left], nums[right]);
}
}
}
// 测试示例
int main() {
vector<int> nums1 = {1, 2, 3, 4, 5, 6, 7};
reorderOddEven(nums1);
cout << "调整后的数组:";
for (int num : nums1) {
cout << num << " ";
}
cout << endl; // 输出示例:1 7 3 5 4 6 2(奇数在前,偶数在后,顺序不固定)
vector<int> nums2 = {2, 4, 6, 1, 3, 5};
reorderOddEven(nums2);
cout << "调整后的数组:";
for (int num : nums2) {
cout << num << " ";
}
cout << endl; // 输出示例:5 3 1 6 4 2
return 0;
}
2.12、在一个int数组里查找这样的数,它大于等于左侧所有数,小于等于右侧所有数。
直观想法是用两个数组a、b。a[i]、b[i]分别保存从前到i的最大的数和从后到i的最小的数,
一个解答:这需要两次遍历,然后再遍历一次原数组,
将所有data[i]>=a[i-1]&&data[i]<=b[i]的data[i]找出即可。
cpp
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
vector<int> findNumbers(vector<int>& data) {
if (data.empty()) return {};
int n = data.size();
vector<int> left_max(n); // left_max[i]表示data[0..i]的最大值
vector<int> right_min(n); // right_min[i]表示data[i..n-1]的最小值
vector<int> result;
// 第一步:计算左侧最大值数组
left_max[0] = data[0];
for (int i = 1; i < n; ++i) {
left_max[i] = max(left_max[i-1], data[i]);
}
// 第二步:计算右侧最小值数组
right_min[n-1] = data[n-1];
for (int i = n-2; i >= 0; --i) {
right_min[i] = min(right_min[i+1], data[i]);
}
// 第三步:筛选符合条件的数
for (int i = 0; i < n; ++i) {
// 左侧所有数<=data[i](即left_max[i]==data[i]),右侧所有数>=data[i](即right_min[i]==data[i])
if (left_max[i] == data[i] && right_min[i] == data[i]) {
result.push_back(data[i]);
}
}
return result;
}
// 测试示例
int main() {
vector<int> data = {2, 3, 1, 8, 9, 10, 5, 7};
vector<int> res = findNumbers(data);
cout << "符合条件的数:";
for (int num : res) cout << num << " "; // 输出:2 8 9 10 7
return 0;
}
2.13、求随机数构成的数组中找到长度大于=3的最长的等差数列9 d- x' W) w9 ?" o3 b0 R
输出等差数列由小到大:
如果没有符合条件的就输出
格式:
输入[1,3,0,5,-1,6]
输出[-1,1,3,5]
要求时间复杂度,空间复杂度尽量
cpp
#include <iostream>
#include <vector>
#include <unordered_map>
#include <algorithm>
using namespace std;
vector<int> longestArithmeticSeq(vector<int>& nums) {
if (nums.size() < 3) return {};
sort(nums.begin(), nums.end()); // 排序数组
int n = nums.size();
// dp[i]存储:以nums[i]结尾的不同公差对应的数列长度及前驱索引
vector<unordered_map<int, pair<int, int>>> dp(n);
int max_len = 2;
int best_end = -1;
int best_diff = 0;
for (int i = 1; i < n; ++i) {
for (int j = 0; j < i; ++j) {
int diff = nums[i] - nums[j];
// 若j位置存在公差diff的数列,则i位置长度为j位置长度+1,前驱为j
if (dp[j].count(diff)) {
dp[i][diff] = {dp[j][diff].first + 1, j};
} else {
dp[i][diff] = {2, j}; // 初始长度为2(j和i)
}
// 更新最长数列信息
if (dp[i][diff].first > max_len) {
max_len = dp[i][diff].first;
best_end = i;
best_diff = diff;
}
}
}
// 若最长长度<3,返回空
if (max_len < 3) return {};
// 回溯构造等差数列
vector<int> result;
int current = best_end;
while (current != -1) {
result.push_back(nums[current]);
if (dp[current].count(best_diff)) {
current = dp[current][best_diff].second;
} else {
break;
}
}
reverse(result.begin(), result.end()); // 回溯结果逆序,需反转
return result;
}
// 测试示例
int main() {
vector<int> nums = {1, 3, 0, 5, -1, 6};
vector<int> res = longestArithmeticSeq(nums);
cout << "最长等差数列:";
for (int num : res) {
cout << num << " ";
} // 输出:-1 1 3 5
cout << endl;
vector<int> nums2 = {2, 4, 6, 8, 10};
vector<int> res2 = longestArithmeticSeq(nums2);
cout << "最长等差数列:";
for (int num : res2) {
cout << num << " ";
} // 输出:2 4 6 8 10
cout << endl;
return 0;
}
三:链表
3.1、怎么判断链表中是否有环?
cpp
#include <stdio.h>
#include <stdlib.h>
// 链表节点结构
typedef struct ListNode {
int val;
struct ListNode *next;
} ListNode;
// 判断链表是否有环(快慢指针法)
int hasCycle(ListNode *head) {
if (head == NULL || head->next == NULL) {
return 0; // 空链表或只有一个节点,无环
}
ListNode *slow = head;
ListNode *fast = head->next; // 快指针初始领先一步(避免初始相等)
while (slow != fast) {
if (fast == NULL || fast->next == NULL) {
return 0; // 快指针到尾部,无环
}
slow = slow->next; // 慢指针走1步
fast = fast->next->next; // 快指针走2步
}
return 1; // 相遇,有环
}
// 测试示例:创建带环链表
ListNode* createCycleList() {
ListNode *n1 = (ListNode*)malloc(sizeof(ListNode));
ListNode *n2 = (ListNode*)malloc(sizeof(ListNode));
ListNode *n3 = (ListNode*)malloc(sizeof(ListNode));
ListNode *n4 = (ListNode*)malloc(sizeof(ListNode));
n1->val = 1; n1->next = n2;
n2->val = 2; n2->next = n3;
n3->val = 3; n3->next = n4;
n4->val = 4; n4->next = n2; // 环:n4->n2
return n1;
}
int main() {
ListNode *head = createCycleList();
if (hasCycle(head)) {
printf("链表有环\n");
} else {
printf("链表无环\n");
}
return 0;
}
3.2、一个单向链表,不知道头节点,一个指针指向其中的一个节点,问如何删除这个指针指向的节点?
cpp
#include <stdio.h>
#include <stdlib.h>
// 链表节点结构
typedef struct ListNode {
int val;
struct ListNode *next;
} ListNode;
// 删除已知指针指向的节点(非尾节点)
void deleteNode(ListNode *node) {
if (node == NULL || node->next == NULL) {
// 若节点为空或为尾节点,无法用此方法(尾节点需特殊处理)
return;
}
// 1. 用下一个节点的内容覆盖当前节点
ListNode *nextNode = node->next;
node->val = nextNode->val; // 复制值
node->next = nextNode->next; // 复制指针
// 2. 释放下一个节点的内存
free(nextNode);
}
// 打印链表(用于测试)
void printList(ListNode *head) {
while (head != NULL) {
printf("%d ", head->val);
head = head->next;
}
printf("\n");
}
int main() {
// 创建链表:1 -> 2 -> 3 -> 4 -> NULL
ListNode *n1 = (ListNode*)malloc(sizeof(ListNode));
ListNode *n2 = (ListNode*)malloc(sizeof(ListNode));
ListNode *n3 = (ListNode*)malloc(sizeof(ListNode));
ListNode *n4 = (ListNode*)malloc(sizeof(ListNode));
n1->val = 1; n1->next = n2;
n2->val = 2; n2->next = n3;
n3->val = 3; n3->next = n4;
n4->val = 4; n4->next = NULL;
printf("删除前链表:");
printList(n1); // 输出:1 2 3 4
// 删除节点n3(值为3的节点)
deleteNode(n3);
printf("删除后链表:");
printList(n1); // 输出:1 2 4
return 0;
}
3.3、删除双向循环链表的相同值的节点
有双向循环链表结点定义为:
struct node{
int data;
struct node *front,*next;
};
有两个双向循环链表A,B,知道其头指针为:pHeadA,pHeadB,请写一函数将两链表中data值相同的结点删除。
cpp
#include <stdio.h>
#include <stdlib.h>
// 双向循环链表节点结构
struct node {
int data;
struct node *front, *next;
};
// 从双向循环链表中删除指定节点
void deleteNode(struct node **pHead, struct node *target) {
if (*pHead == NULL || target == NULL) return;
// 若链表只有一个节点
if ((*pHead)->next == *pHead && (*pHead)->front == *pHead) {
free(target);
*pHead = NULL;
return;
}
// 若删除的是头节点,更新头指针
if (target == *pHead) {
*pHead = target->next;
}
// 维护双向指针:前驱节点的next指向后继,后继节点的front指向前驱
target->front->next = target->next;
target->next->front = target->front;
free(target); // 释放节点内存
}
// 在双向循环链表中查找并返回第一个data匹配的节点
struct node* findNode(struct node *pHead, int data) {
if (pHead == NULL) return NULL;
struct node *cur = pHead;
do {
if (cur->data == data) {
return cur;
}
cur = cur->next;
} while (cur != pHead); // 循环链表遍历终止条件:回到头节点
return NULL;
}
// 删除两个双向循环链表中data相同的节点
void deleteCommonNodes(struct node **pHeadA, struct node **pHeadB) {
if (*pHeadA == NULL || *pHeadB == NULL) return;
struct node *curA = *pHeadA;
struct node *nextA; // 保存下一个节点(避免删除后丢失遍历路径)
do {
nextA = curA->next; // 先保存下一个节点
struct node *matchB = findNode(*pHeadB, curA->data);
if (matchB != NULL) {
// 删除B中的匹配节点
deleteNode(pHeadB, matchB);
// 删除A中的当前节点
deleteNode(pHeadA, curA);
}
curA = nextA; // 继续遍历A的下一个节点
} while (curA != *pHeadA && *pHeadA != NULL); // A链表未遍历完且非空
}
// ========== 辅助函数:创建/打印链表(用于测试) ==========
// 创建双向循环链表节点
struct node* createNode(int data) {
struct node *newNode = (struct node*)malloc(sizeof(struct node));
newNode->data = data;
newNode->front = newNode;
newNode->next = newNode;
return newNode;
}
// 向双向循环链表尾部添加节点
void addNode(struct node **pHead, int data) {
struct node *newNode = createNode(data);
if (*pHead == NULL) {
*pHead = newNode;
return;
}
struct node *tail = (*pHead)->front; // 尾节点是头节点的前驱
tail->next = newNode;
newNode->front = tail;
newNode->next = *pHead;
(*pHead)->front = newNode;
}
// 打印双向循环链表
void printList(struct node *pHead, const char *name) {
printf("链表%s:", name);
if (pHead == NULL) {
printf("空\n");
return;
}
struct node *cur = pHead;
do {
printf("%d ", cur->data);
cur = cur->next;
} while (cur != pHead);
printf("\n");
}
// ========== 测试主函数 ==========
int main() {
// 创建链表A:1 -> 2 -> 3 -> 4 -> 1(循环)
struct node *pHeadA = NULL;
addNode(&pHeadA, 1);
addNode(&pHeadA, 2);
addNode(&pHeadA, 3);
addNode(&pHeadA, 4);
// 创建链表B:2 -> 4 -> 5 -> 6 -> 2(循环)
struct node *pHeadB = NULL;
addNode(&pHeadB, 2);
addNode(&pHeadB, 4);
addNode(&pHeadB, 5);
addNode(&pHeadB, 6);
printf("删除前:\n");
printList(pHeadA, "A"); // 输出:1 2 3 4
printList(pHeadB, "B"); // 输出:2 4 5 6
// 删除data相同的节点(2、4)
deleteCommonNodes(&pHeadA, &pHeadB);
printf("\n删除后:\n");
printList(pHeadA, "A"); // 输出:1 3
printList(pHeadB, "B"); // 输出:5 6
return 0;
}
3.4、判断链表是否相交
cpp
#include <stdio.h>
#include <stdlib.h>
// 链表节点结构
typedef struct ListNode {
int val;
struct ListNode *next;
} ListNode;
// 计算链表长度
int getListLength(ListNode *head) {
int len = 0;
while (head != NULL) {
len++;
head = head->next;
}
return len;
}
// 判断两个无环链表是否相交
int isIntersect(ListNode *h1, ListNode *h2) {
if (h1 == NULL || h2 == NULL) {
return 0; // 空链表不相交
}
// 计算长度
int len1 = getListLength(h1);
int len2 = getListLength(h2);
ListNode *longList = h1, *shortList = h2;
// 确定较长和较短链表
if (len2 > len1) {
longList = h2;
shortList = h1;
}
// 较长链表先走长度差步
int diff = abs(len1 - len2);
for (int i = 0; i < diff; i++) {
longList = longList->next;
}
// 同时遍历,检查是否相遇
while (longList != NULL && shortList != NULL) {
if (longList == shortList) {
return 1; // 相交
}
longList = longList->next;
shortList = shortList->next;
}
return 0; // 不相交
}
// ========== 辅助函数:创建链表/连接节点(用于测试) ==========
ListNode* createNode(int val) {
ListNode *node = (ListNode*)malloc(sizeof(ListNode));
node->val = val;
node->next = NULL;
return node;
}
int main() {
// 创建链表1:1->2->3->4
ListNode *h1 = createNode(1);
h1->next = createNode(2);
h1->next->next = createNode(3);
h1->next->next->next = createNode(4);
// 创建链表2:5->6->3->4(与链表1相交于节点3)
ListNode *h2 = createNode(5);
h2->next = createNode(6);
h2->next->next = h1->next->next; // 指向链表1的节点3
// 测试相交
if (isIntersect(h1, h2)) {
printf("链表相交\n");
} else {
printf("链表不相交\n");
}
// 测试不相交(链表3:7->8->9)
ListNode *h3 = createNode(7);
h3->next = createNode(8);
h3->next->next = createNode(9);
if (isIntersect(h1, h3)) {
printf("链表相交\n");
} else {
printf("链表不相交\n");
}
return 0;
}
3.5、输出链表倒数第k个节点。
题目:输入一个单向链表,输出该链表中倒数第k个结点。链表的倒数第0个结点为链表的尾指针。
链表结点定义如下:
struct ListNode
{
int m_nKey;
ListNode* m_pNext;
};
cpp
#include <stdio.h>
#include <stdlib.h>
// 链表节点结构
struct ListNode {
int m_nKey;
struct ListNode* m_pNext;
};
// 查找链表倒数第k个节点
struct ListNode* FindKthToTail(struct ListNode* pHead, unsigned int k) {
// 边界条件:链表为空或k为0(倒数第0个为尾节点)
if (pHead == NULL) return NULL;
if (k == 0) { // 找尾节点
struct ListNode* pTail = pHead;
while (pTail->m_pNext != NULL) {
pTail = pTail->m_pNext;
}
return pTail;
}
// 双指针初始化
struct ListNode* pFast = pHead;
struct ListNode* pSlow = pHead;
// 快指针先走k步
for (unsigned int i = 0; i < k; i++) {
if (pFast == NULL) { // k超过链表长度
return NULL;
}
pFast = pFast->m_pNext;
}
// 快慢指针同步前进,直到快指针到尾部
while (pFast != NULL) {
pFast = pFast->m_pNext;
pSlow = pSlow->m_pNext;
}
return pSlow;
}
// ========== 辅助函数:创建链表/打印节点(用于测试) ==========
struct ListNode* CreateNode(int key) {
struct ListNode* pNode = (struct ListNode*)malloc(sizeof(struct ListNode));
pNode->m_nKey = key;
pNode->m_pNext = NULL;
return pNode;
}
void PrintList(struct ListNode* pHead) {
struct ListNode* pCur = pHead;
while (pCur != NULL) {
printf("%d ", pCur->m_nKey);
pCur = pCur->m_pNext;
}
printf("\n");
}
int main() {
// 创建链表:1 -> 2 -> 3 -> 4 -> 5 -> NULL
struct ListNode* pHead = CreateNode(1);
pHead->m_pNext = CreateNode(2);
pHead->m_pNext->m_pNext = CreateNode(3);
pHead->m_pNext->m_pNext->m_pNext = CreateNode(4);
pHead->m_pNext->m_pNext->m_pNext->m_pNext = CreateNode(5);
printf("链表:");
PrintList(pHead);
// 测试倒数第2个节点(预期4)
struct ListNode* pNode1 = FindKthToTail(pHead, 2);
if (pNode1 != NULL) {
printf("倒数第2个节点:%d\n", pNode1->m_nKey);
} else {
printf("倒数第2个节点不存在\n");
}
// 测试倒数第0个节点(预期5)
struct ListNode* pNode0 = FindKthToTail(pHead, 0);
if (pNode0 != NULL) {
printf("倒数第0个节点:%d\n", pNode0->m_nKey);
} else {
printf("倒数第0个节点不存在\n");
}
// 测试k超过链表长度(预期NULL)
struct ListNode* pNode6 = FindKthToTail(pHead, 6);
if (pNode6 != NULL) {
printf("倒数第6个节点:%d\n", pNode6->m_nKey);
} else {
printf("倒数第6个节点不存在\n");
}
return 0;
}
3.6、单链表就地逆置
cpp
#include <stdio.h>
#include <stdlib.h>
// 链表节点结构
struct ListNode {
int val;
struct ListNode *next;
};
// 单链表就地逆置
struct ListNode* reverseList(struct ListNode *head) {
if (head == NULL || head->next == NULL) {
return head; // 空链表或单个节点,直接返回
}
struct ListNode *prev = NULL; // 前驱节点
struct ListNode *curr = head; // 当前节点
struct ListNode *next = NULL; // 后继节点
while (curr != NULL) {
next = curr->next; // 保存后继节点
curr->next = prev; // 反转当前节点指针
prev = curr; // 前驱节点后移
curr = next; // 当前节点后移
}
return prev; // 新的头节点(原尾节点)
}
// ========== 辅助函数 ==========
struct ListNode* createNode(int val) {
struct ListNode *node = (struct ListNode*)malloc(sizeof(struct ListNode));
node->val = val;
node->next = NULL;
return node;
}
void printList(struct ListNode *head) {
while (head != NULL) {
printf("%d ", head->val);
head = head->next;
}
printf("\n");
}
// 测试单链表逆置
int main() {
// 创建链表:1->2->3->4->5
struct ListNode *head = createNode(1);
head->next = createNode(2);
head->next->next = createNode(3);
head->next->next->next = createNode(4);
head->next->next->next->next = createNode(5);
printf("原链表:");
printList(head);
struct ListNode *newHead = reverseList(head);
printf("逆置后:");
printList(newHead);
return 0;
}
3.7、合并链表
cpp
// 合并两个升序链表
struct ListNode* mergeTwoLists(struct ListNode *l1, struct ListNode *l2) {
// 虚拟头节点(简化边界处理)
struct ListNode dummy;
dummy.next = NULL;
struct ListNode *curr = &dummy;
while (l1 != NULL && l2 != NULL) {
if (l1->val <= l2->val) {
curr->next = l1;
l1 = l1->next;
} else {
curr->next = l2;
l2 = l2->next;
}
curr = curr->next;
}
// 拼接剩余节点
if (l1 != NULL) {
curr->next = l1;
} else {
curr->next = l2;
}
return dummy.next; // 真实头节点
}
// 测试合并链表
int main() {
// 创建链表1:1->3->5
struct ListNode *l1 = createNode(1);
l1->next = createNode(3);
l1->next->next = createNode(5);
// 创建链表2:2->4->6
struct ListNode *l2 = createNode(2);
l2->next = createNode(4);
l2->next->next = createNode(6);
printf("链表1:");
printList(l1);
printf("链表2:");
printList(l2);
struct ListNode *merged = mergeTwoLists(l1, l2);
printf("合并后:");
printList(merged);
return 0;
}
3.8、编写实现链表排序的一种算法。说明为什么你会选择用这样的方法?
cpp
#include <stdio.h>
#include <stdlib.h>
// 链表节点定义
struct ListNode {
int val;
struct ListNode *next;
};
// 合并两个有序链表
struct ListNode* merge(struct ListNode* l1, struct ListNode* l2) {
struct ListNode dummy;
struct ListNode* curr = &dummy;
while (l1 && l2) {
if (l1->val <= l2->val) {
curr->next = l1;
l1 = l1->next;
} else {
curr->next = l2;
l2 = l2->next;
}
curr = curr->next;
}
curr->next = l1 ? l1 : l2; // 拼接剩余节点
return dummy.next;
}
// 找到链表中点(快慢指针)
struct ListNode* findMiddle(struct ListNode* head) {
struct ListNode* slow = head;
struct ListNode* fast = head->next; // 快指针先走一步,避免偶数长度时中点偏右
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
// 归并排序主函数
struct ListNode* sortList(struct ListNode* head) {
// 递归终止条件:空链表或单个节点
if (!head || !head->next) return head;
// 分割链表
struct ListNode* mid = findMiddle(head);
struct ListNode* right = mid->next;
mid->next = NULL; // 断开链表
// 递归排序左右子链表
struct ListNode* leftSorted = sortList(head);
struct ListNode* rightSorted = sortList(right);
// 合并有序链表
return merge(leftSorted, rightSorted);
}
// 辅助函数:创建节点
struct ListNode* createNode(int val) {
struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode));
node->val = val;
node->next = NULL;
return node;
}
// 辅助函数:打印链表
void printList(struct ListNode* head) {
while (head) {
printf("%d ", head->val);
head = head->next;
}
printf("\n");
}
// 测试
int main() {
// 构建链表:4 -> 2 -> 1 -> 3
struct ListNode* head = createNode(4);
head->next = createNode(2);
head->next->next = createNode(1);
head->next->next->next = createNode(3);
printf("原链表:");
printList(head);
struct ListNode* sortedHead = sortList(head);
printf("排序后:");
printList(sortedHead);
return 0;
}
3.9、从尾到头输出链表。
题目:输入一个链表的头结点,从尾到头反过来输出每个结点的值。链表结点定义如下:
struct ListNode
{
int m_nKey;
ListNode* m_pNext;
};
cpp
#include <iostream>
using namespace std;
struct ListNode {
int m_nKey;
ListNode* m_pNext;
ListNode(int val) : m_nKey(val), m_pNext(nullptr) {}
};
// 反转链表
ListNode* reverseList(ListNode* head) {
ListNode* prev = nullptr;
ListNode* curr = head;
while (curr) {
ListNode* next = curr->m_pNext;
curr->m_pNext = prev;
prev = curr;
curr = next;
}
return prev;
}
// 逆序输出(反转后输出,再恢复原链表)
void printListFromTailToHead(ListNode* head) {
if (head == nullptr) return;
ListNode* reversedHead = reverseList(head); // 反转链表
ListNode* temp = reversedHead;
// 输出反转后的链表
while (temp) {
cout << temp->m_nKey << " ";
temp = temp->m_pNext;
}
reverseList(reversedHead); // 恢复原链表(可选)
}
// 测试
int main() {
ListNode* head = new ListNode(1);
head->m_pNext = new ListNode(2);
head->m_pNext->m_pNext = new ListNode(3);
cout << "从尾到头输出:";
printListFromTailToHead(head); // 输出:3 2 1
// 释放内存
ListNode* temp;
while (head) {
temp = head;
head = head->m_pNext;
delete temp;
}
return 0;
}
3.10、在O(1)时间内删除链表结点。
题目:给定链表的头指针和一个结点指针,在O(1)时间删除该结点。链表结点的定义如下:
struct ListNode
{
int m_nKey;
ListNode* m_pNext;
};
函数的声明如下:
void DeleteNode(ListNode* pListHead, ListNode* pToBeDeleted);
cpp
#include <iostream>
using namespace std;
struct ListNode {
int m_nKey;
ListNode* m_pNext;
ListNode(int val) : m_nKey(val), m_pNext(nullptr) {}
};
void DeleteNode(ListNode* pListHead, ListNode* pToBeDeleted) {
// 空指针校验
if (pListHead == nullptr || pToBeDeleted == nullptr) {
return;
}
// 情况1:待删除节点不是尾节点
if (pToBeDeleted->m_pNext != nullptr) {
ListNode* pNext = pToBeDeleted->m_pNext;
// 用下一个节点覆盖当前节点
pToBeDeleted->m_nKey = pNext->m_nKey;
pToBeDeleted->m_pNext = pNext->m_pNext;
// 删除下一个节点
delete pNext;
pNext = nullptr;
}
// 情况2:链表只有一个节点(既是头也是尾)
else if (pListHead == pToBeDeleted) {
delete pToBeDeleted;
pToBeDeleted = nullptr;
pListHead = nullptr; // 头指针置空(需注意:此处pListHead是值传递,外部需同步)
}
// 情况3:待删除节点是尾节点且链表有多个节点(需遍历找前驱)
else {
ListNode* pCurr = pListHead;
while (pCurr->m_pNext != pToBeDeleted) {
pCurr = pCurr->m_pNext;
}
pCurr->m_pNext = nullptr;
delete pToBeDeleted;
pToBeDeleted = nullptr;
}
}
// 辅助函数:打印链表
void PrintList(ListNode* head) {
while (head != nullptr) {
cout << head->m_nKey << " ";
head = head->m_pNext;
}
cout << endl;
}
// 测试
int main() {
// 构建链表:1 -> 2 -> 3 -> 4
ListNode* head = new ListNode(1);
head->m_pNext = new ListNode(2);
head->m_pNext->m_pNext = new ListNode(3);
head->m_pNext->m_pNext->m_pNext = new ListNode(4);
cout << "删除前链表:";
PrintList(head); // 输出:1 2 3 4
// 删除节点3(非尾节点)
DeleteNode(head, head->m_pNext->m_pNext);
cout << "删除节点3后:";
PrintList(head); // 输出:1 2 4
// 删除节点4(尾节点)
DeleteNode(head, head->m_pNext->m_pNext);
cout << "删除节点4后:";
PrintList(head); // 输出:1 2
return 0;
}
3.11、找出链表的第一个公共结点。
题目:两个单向链表,找出它们的第一个公共结点。
链表的结点定义为:
struct ListNode
{
int m_nKey;
ListNode* m_pNext;
};
cpp
#include <iostream>
using namespace std;
struct ListNode {
int m_nKey;
ListNode* m_pNext;
ListNode(int val) : m_nKey(val), m_pNext(nullptr) {}
};
// 计算链表长度
int getListLength(ListNode* head) {
int len = 0;
while (head != nullptr) {
len++;
head = head->m_pNext;
}
return len;
}
// 找到第一个公共结点
ListNode* FindFirstCommonNode(ListNode* pHead1, ListNode* pHead2) {
if (pHead1 == nullptr || pHead2 == nullptr) {
return nullptr;
}
// 计算两链表长度
int len1 = getListLength(pHead1);
int len2 = getListLength(pHead2);
ListNode* pLong = pHead1;
ListNode* pShort = pHead2;
int diff = len1 - len2;
// 确定长链表和短链表
if (len2 > len1) {
pLong = pHead2;
pShort = pHead1;
diff = len2 - len1;
}
// 长链表先走diff步
for (int i = 0; i < diff; i++) {
pLong = pLong->m_pNext;
}
// 同步遍历,找公共结点
while (pLong != nullptr && pShort != nullptr && pLong != pShort) {
pLong = pLong->m_pNext;
pShort = pShort->m_pNext;
}
return pLong; // 找到则返回,否则返回nullptr
}
// 测试
int main() {
// 构建公共结点部分:6 -> 7
ListNode* common = new ListNode(6);
common->m_pNext = new ListNode(7);
// 链表1:1 -> 2 -> 3 -> 6 -> 7
ListNode* head1 = new ListNode(1);
head1->m_pNext = new ListNode(2);
head1->m_pNext->m_pNext = new ListNode(3);
head1->m_pNext->m_pNext->m_pNext = common;
// 链表2:4 -> 5 -> 6 -> 7
ListNode* head2 = new ListNode(4);
head2->m_pNext = new ListNode(5);
head2->m_pNext->m_pNext = common;
ListNode* result = FindFirstCommonNode(head1, head2);
if (result != nullptr) {
cout << "第一个公共结点值:" << result->m_nKey << endl; // 输出6
} else {
cout << "无公共结点" << endl;
}
return 0;
}
3.12、复杂链表的复制
题目:有一个复杂链表,其结点除了有一个m_pNext指针指向下一个结点外,
还有一个m_pSibling指向链表中的任一结点或者NULL。其结点的C++定义如下:
struct ComplexNode
{
int m_nValue;
ComplexNode* m_pNext;
ComplexNode* m_pSibling;
};
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
请完成函数ComplexNode* Clone(ComplexNode* pHead),以复制一个复杂链表。
cpp
struct ComplexNode {
int m_nValue;
ComplexNode* m_pNext;
ComplexNode* m_pSibling;
ComplexNode(int val) : m_nValue(val), m_pNext(nullptr), m_pSibling(nullptr) {}
};
ComplexNode* Clone(ComplexNode* pHead) {
if (pHead == nullptr) return nullptr;
// 第一步:复制结点并插入原结点之后
ComplexNode* pNode = pHead;
while (pNode != nullptr) {
ComplexNode* pClone = new ComplexNode(pNode->m_nValue);
pClone->m_pNext = pNode->m_pNext;
pNode->m_pNext = pClone;
pNode = pClone->m_pNext;
}
// 第二步:设置复制结点的m_pSibling
pNode = pHead;
while (pNode != nullptr) {
ComplexNode* pClone = pNode->m_pNext;
if (pNode->m_pSibling != nullptr) {
pClone->m_pSibling = pNode->m_pSibling->m_pNext;
}
pNode = pClone->m_pNext;
}
// 第三步:拆分链表
pNode = pHead;
ComplexNode* pCloneHead = pHead->m_pNext;
while (pNode != nullptr) {
ComplexNode* pClone = pNode->m_pNext;
pNode->m_pNext = pClone->m_pNext; // 恢复原链表的next指针
pNode = pNode->m_pNext;
if (pNode != nullptr) {
pClone->m_pNext = pNode->m_pNext; // 设置复制链表的next指针
}
}
return pCloneHead;
}
// 辅助函数:打印链表(验证结果)
void PrintComplexList(ComplexNode* pHead) {
ComplexNode* pNode = pHead;
while (pNode != nullptr) {
printf("结点值:%d,", pNode->m_nValue);
if (pNode->m_pSibling != nullptr) {
printf("Sibling值:%d\n", pNode->m_pSibling->m_nValue);
} else {
printf("Sibling:NULL\n");
}
pNode = pNode->m_pNext;
}
}
// 测试用例
int main() {
// 构建原链表:1→2→3→4→5
ComplexNode* n1 = new ComplexNode(1);
ComplexNode* n2 = new ComplexNode(2);
ComplexNode* n3 = new ComplexNode(3);
ComplexNode* n4 = new ComplexNode(4);
ComplexNode* n5 = new ComplexNode(5);
n1->m_pNext = n2;
n2->m_pNext = n3;
n3->m_pNext = n4;
n4->m_pNext = n5;
// 设置Sibling:1→3,2→5,4→2
n1->m_pSibling = n3;
n2->m_pSibling = n5;
n4->m_pSibling = n2;
printf("原链表:\n");
PrintComplexList(n1);
ComplexNode* clonedList = Clone(n1);
printf("\n复制链表:\n");
PrintComplexList(clonedList);
// 释放内存(实际开发中需手动释放,此处省略)
return 0;
}
四:数字
4.1、查找最小的k个元素
题目:输入n个整数,输出其中最小的k个。
例如输入1,2,3,4,5,6,7和8这8个数字,则最小的4个数字为1,2,3和4。
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 划分函数(类似快速排序)
int partition(vector<int>& nums, int left, int right) {
int pivot = nums[right]; // 选最后一个元素为基准
int i = left - 1;
for (int j = left; j < right; ++j) {
if (nums[j] <= pivot) {
i++;
swap(nums[i], nums[j]);
}
}
swap(nums[i+1], nums[right]);
return i+1;
}
// 快速选择找最小的k个元素
vector<int> getLeastNumbers(vector<int>& nums, int k) {
if (k <= 0 || nums.empty()) return {};
if (k >= nums.size()) return nums;
int left = 0, right = nums.size()-1;
while (left <= right) {
int p = partition(nums, left, right);
if (p == k-1) { // 基准位置为第k小,左侧即为最小的k个
return vector<int>(nums.begin(), nums.begin()+k);
} else if (p < k-1) { // 需在右侧继续划分
left = p + 1;
} else { // 需在左侧继续划分
right = p - 1;
}
}
return {};
}
// 测试示例
int main() {
vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8};
int k = 4;
vector<int> res = getLeastNumbers(nums, k);
cout << "最小的" << k << "个元素:";
for (int num : res) cout << num << " "; // 输出:1 2 3 4
cout << endl;
return 0;
}
4.2、题目:求1+2+...+n,
要求不能使用乘除法、for、while、if、else、switch、case等关键字以及条件判断语句(A?B:C)。
cpp
#include <iostream>
using namespace std;
int sumNums(int n) {
int res = n;
// 短路求值:n>0时执行递归,否则终止
(n > 0) && (res += sumNums(n - 1));
return res;
}
int main() {
int n = 10;
cout << "1+2+...+" << n << " = " << sumNums(n) << endl; // 输出55
return 0;
}
4.3、n个数字(0,1,...,n-1)形成一个圆圈,从数字0开始, 每次从这个圆圈中删除第m个数字(第一个为当前数字本身,第二个为当前数字的下一个数字)。 当一个数字删除后,从被删除数字的下一个继续删除第m个数字。 求出在这个圆圈中剩下的最后一个数字。
cpp
#include <iostream>
using namespace std;
int lastRemaining(int n, int m) {
int res = 0; // f(1, m) = 0
for (int i = 2; i <= n; ++i) {
res = (res + m) % i; // 递推计算f(i, m)
}
return res;
}
// 测试示例
int main() {
int n = 5, m = 3;
cout << "最后剩下的数字:" << lastRemaining(n, m) << endl; // 输出3(对应数字3)
n = 10, m = 2;
cout << "最后剩下的数字:" << lastRemaining(n, m) << endl; // 输出4(对应数字4)
return 0;
}
4.4、整数的二进制表示中1的个数
题目:输入一个整数,求该整数的二进制表达中有多少个1。
例如输入10,由于其二进制表示为1010,有两个1,因此输出2。
cpp
// 预处理0-255的1的个数
int table[256];
void initTable() {
for (int i = 0; i < 256; i++) {
table[i] = table[i >> 1] + (i & 1);
}
}
int countOne(int n) {
int count = 0;
unsigned char* p = (unsigned char*)&n;
count = table[p[0]] + table[p[1]] + table[p[2]] + table[p[3]]; // 32位int分4字节
return count;
}
4.5、在从1到n的正数中1出现的次数
题目:输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。
例如输入12,从1到12这些整数中包含1 的数字有1,10,11和12,1一共出现了5次
cpp
#include <iostream>
using namespace std;
int countDigitOne(int n) {
int count = 0;
long long digit = 1; // 避免溢出,从个位开始(1, 10, 100...)
int high = n / 10, curr = n % 10, low = 0;
while (high != 0 || curr != 0) { // 高位和当前位都为0时结束
if (curr == 0) {
count += high * digit;
} else if (curr == 1) {
count += high * digit + low + 1;
} else {
count += (high + 1) * digit;
}
// 更新低位、当前位、高位和位数
low += curr * digit;
curr = high % 10;
high = high / 10;
digit *= 10;
}
return count;
}
// 测试示例
int main() {
cout << countDigitOne(12) << endl; // 输出5
cout << countDigitOne(13) << endl; // 输出6(1,10,11,12,13中1出现6次)
cout << countDigitOne(100) << endl; // 输出21
return 0;
}
4.6、有两个序列a,b,大小都为n,序列元素的值任意整数,无序;
要求:通过交换a,b中的元素,使[序列a元素的和]与[序列b元素的和]之间的差最小。
例如:
var a=[100,99,98,1,2, 3];
var b=[1, 2, 3, 4,5,40];
cpp
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
using namespace std;
int minDiff(vector<int>& a, vector<int>& b) {
int n = a.size();
int sum_a = 0, sum_b = 0;
vector<int> c(n);
for (int i = 0; i < n; ++i) {
sum_a += a[i];
sum_b += b[i];
c[i] = b[i] - a[i]; // 交换a[i]和b[i]的差值变化量
}
int diff = sum_a - sum_b;
int target = -diff / 2; // 目标:sum(选中的c[i])尽可能接近target
int max_sum = 0;
for (int num : c) { // 计算c数组的最大可能和(用于DP数组大小)
max_sum += max(num, 0);
}
int min_sum = 0;
for (int num : c) {
min_sum += min(num, 0);
}
// 处理负数,偏移量使sum非负
int offset = -min_sum;
vector<bool> dp(max_sum - min_sum + 1, false);
dp[offset] = true; // sum=0时可达
for (int num : c) {
// 0-1背包,逆序遍历
if (num >= 0) {
for (int j = max_sum + offset; j >= num + offset; --j) {
if (dp[j - num]) {
dp[j] = true;
}
}
} else {
for (int j = min_sum + offset; j <= num + offset; ++j) {
if (dp[j - num]) {
dp[j] = true;
}
}
}
}
// 找到最接近target的sum
int best = 0;
int min_dist = abs(target);
for (int j = 0; j < dp.size(); ++j) {
if (dp[j]) {
int current = j - offset;
int dist = abs(current - target);
if (dist < min_dist) {
min_dist = dist;
best = current;
}
}
}
return abs(diff + 2 * best);
}
// 测试示例
int main() {
vector<int> a = {100, 99, 98, 1, 2, 3};
vector<int> b = {1, 2, 3, 4, 5, 40};
cout << "最小差值:" << minDiff(a, b) << endl; // 输出2
// 交换后示例(a=[100,1,2,1,2,3], b=[99,98,3,4,5,40],sum_a=109, sum_b=249?实际最优交换后差值为2)
return 0;
}
4.7、如何对n个数进行排序,要求时间复杂度O(n),空间复杂度O(1)
cpp
void countingSort(int arr[], int n, int max_val) {
// 步骤1:统计频率(若max_val较小,可直接用数组;否则需优化)
int count[max_val + 1] = {0};
for (int i = 0; i < n; i++) {
count[arr[i]]++;
}
// 步骤2:原地回填
int idx = 0;
for (int i = 0; i <= max_val; i++) {
while (count[i]--) {
arr[idx++] = i;
}
}
}
4.8、和为n连续正数序列。
题目:输入一个正数n,输出所有和为n连续正数序列。
例如输入15,由于1+2+3+4+5=4+5+6=7+8=15,所以输出3个连续序列1-5、4-6和7-8。
cpp
#include <iostream>
#include <vector>
using namespace std;
vector<vector<int>> findContinuousSequence(int n) {
vector<vector<int>> result;
int left = 1, right = 2;
int sum = left + right;
while (left < right && right < n) {
if (sum == n) {
// 记录当前序列
vector<int> seq;
for (int i = left; i <= right; i++) {
seq.push_back(i);
}
result.push_back(seq);
// 缩小左边界,继续寻找
sum -= left;
left++;
} else if (sum < n) {
// 扩大右边界
right++;
sum += right;
} else {
// 缩小左边界
sum -= left;
left++;
}
}
return result;
}
// 测试示例
int main() {
int n = 15;
vector<vector<int>> res = findContinuousSequence(n);
for (auto& seq : res) {
for (int num : seq) {
cout << num << " ";
}
cout << endl;
}
return 0;
}
4.9、寻找丑数
我们把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,
但14不是,因为它包含因子7。习惯上我们把1当做是第一个丑数。
求按从小到大的顺序的第1500个丑数。
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int nthUglyNumber(int n) {
vector<int> dp(n);
dp[0] = 1; // 第一个丑数
int p2 = 0, p3 = 0, p5 = 0; // 三个指针
for (int i = 1; i < n; i++) {
int next2 = dp[p2] * 2;
int next3 = dp[p3] * 3;
int next5 = dp[p5] * 5;
dp[i] = min({next2, next3, next5}); // 取最小值
// 移动对应指针,避免重复
if (dp[i] == next2) p2++;
if (dp[i] == next3) p3++;
if (dp[i] == next5) p5++;
}
return dp[n-1];
}
int main() {
cout << "第1500个丑数:" << nthUglyNumber(1500) << endl; // 输出859963392
return 0;
}
4.10、输出1到最大的N位数
题目:输入数字n,按顺序输出从1最大的n位10进制数。比如输入3,
则输出1、2、3一直到最大的3位数即999。
cpp
#include <iostream>
#include <string>
using namespace std;
// 字符串模拟数字加1,返回是否溢出(即是否达到最大n位数)
bool increment(string& num) {
int n = num.size();
int carry = 1; // 进位初始为1(加1)
for (int i = n - 1; i >= 0; i--) {
int digit = num[i] - '0' + carry;
if (digit == 10) { // 进位
num[i] = '0';
carry = 1;
} else {
num[i] = digit + '0';
carry = 0;
break;
}
}
return carry == 1; // 若最高位进位,说明已到最大n位数
}
// 输出字符串(跳过前导零)
void printNumber(const string& num) {
bool start = false; // 是否开始输出有效数字
for (char c : num) {
if (c != '0' || start) {
cout << c;
start = true;
}
}
if (start) cout << " "; // 有效数字输出后加空格
}
// 输出1到最大的n位数
void print1ToMaxOfNDigits(int n) {
if (n <= 0) return;
string num(n, '0'); // 初始化n位字符串为全0
while (!increment(num)) { // 未溢出则继续
printNumber(num);
}
cout << endl;
}
// 测试示例
int main() {
print1ToMaxOfNDigits(3); // 输出1 2 ... 999
return 0;
}
4.11、n个骰子的点数。
把n个骰子扔在地上,所有骰子朝上一面的点数之和为S。输入n,打印出S的所有可能的值出现的概率。
cpp
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
void printProbability(int n) {
if (n <= 0) return;
// dp[i][j]表示i个骰子点数和为j的次数,j范围:i~6i
vector<vector<int>> dp(n + 1, vector<int>(6 * n + 1, 0));
// 初始化1个骰子的情况
for (int j = 1; j <= 6; j++) {
dp[1][j] = 1;
}
// 动态规划计算n个骰子的情况
for (int i = 2; i <= n; i++) {
// i个骰子的点数和范围:i ~ 6i
for (int j = i; j <= 6 * i; j++) {
// 第i个骰子的点数k=1~6,且j-k ≥ i-1(i-1个骰子的最小和)
for (int k = 1; k <= 6 && j - k >= i - 1; k++) {
dp[i][j] += dp[i - 1][j - k];
}
}
}
// 计算总次数:6^n
int total = pow(6, n);
// 输出每个点数和的概率
for (int j = n; j <= 6 * n; j++) {
double prob = (double)dp[n][j] / total;
printf("点数和%d: 概率%.4f\n", j, prob);
}
}
// 空间优化版本(用一维数组)
void printProbabilityOpt(int n) {
if (n <= 0) return;
vector<int> dp(6 * n + 1, 0);
// 初始化1个骰子
for (int j = 1; j <= 6; j++) {
dp[j] = 1;
}
// 迭代计算i个骰子(i从2到n)
for (int i = 2; i <= n; i++) {
// 逆序更新,避免覆盖上一轮数据
for (int j = 6 * i; j >= i; j--) {
dp[j] = 0; // 重置当前值
for (int k = 1; k <= 6 && j - k >= i - 1; k++) {
dp[j] += dp[j - k];
}
}
}
int total = pow(6, n);
for (int j = n; j <= 6 * n; j++) {
double prob = (double)dp[j] / total;
printf("点数和%d: 概率%.4f\n", j, prob);
}
}
// 测试示例
int main() {
printProbability(2); // 输出2个骰子的点数和概率
// printProbabilityOpt(3); // 空间优化版本
return 0;
}
4.12、扑克牌的顺子
从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。
2-10为数字本身,A为1,J为11,Q为12,K为13,而大小王可以看成任意数字。
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
bool isContinuous(vector<int>& nums) {
if (nums.size() != 5) return false;
sort(nums.begin(), nums.end()); // 排序
int zero_count = 0; // 大小王数量(0的个数)
int gap = 0; // 非0牌之间的间隔
// 统计大小王数量
for (int num : nums) {
if (num == 0) zero_count++;
}
// 计算非0牌的间隔
int small = zero_count;
int big = small + 1;
while (big < 5) {
if (nums[small] == nums[big]) { // 有重复非0牌,不是顺子
return false;
}
gap += nums[big] - nums[small] - 1; // 计算间隔(如1和3的间隔为1)
small = big;
big++;
}
return gap <= zero_count; // 间隔可被大小王填补
}
// 测试示例
int main() {
vector<int> nums1 = {0, 1, 3, 4, 5}; // 是顺子(0填补1和3的间隔)
cout << (isContinuous(nums1) ? "是顺子" : "不是顺子") << endl;
vector<int> nums2 = {0, 0, 1, 2, 5}; // 是顺子(两个0填补2和5的间隔)
cout << (isContinuous(nums2) ? "是顺子" : "不是顺子") << endl;
vector<int> nums3 = {1, 2, 3, 4, 6}; // 不是顺子(无0填补4和6的间隔)
cout << (isContinuous(nums3) ? "是顺子" : "不是顺子") << endl;
vector<int> nums4 = {0, 1, 2, 2, 5}; // 不是顺子(有重复非0牌)
cout << (isContinuous(nums4) ? "是顺子" : "不是顺子") << endl;
return 0;
}
4.13、数值的整数次方。
题目:实现函数double Power(double base, int exponent),求base的exponent次方。
不需要考虑溢出。
分析:这是一道看起来很简单的问题。可能有不少的人在看到题目后30秒写出如下的代码:
double Power(double base, int exponent)
{
double result = 1.0;
for(int i = 1; i <= exponent; ++i)
result *= base;
return result;
}
cpp
#include <iostream>
using namespace std;
// 快速幂核心函数(处理正指数)
double powerCore(double base, long long exponent) {
if (exponent == 0) return 1.0;
if (exponent == 1) return base;
// 递归计算base^(exponent/2)
double result = powerCore(base, exponent / 2);
result *= result; // 平方
// 若exponent为奇数,额外乘一次base
if (exponent % 2 == 1) {
result *= base;
}
return result;
}
double Power(double base, int exponent) {
// 处理特殊情况
if (base == 0 && exponent < 0) { // 0的负次幂无意义,此处按题目要求忽略溢出
return 0.0;
}
// 用long long避免exponent=-2^31时取反溢出
long long exp = exponent;
if (exp < 0) {
exp = -exp;
base = 1.0 / base; // 负指数转为正指数的倒数
}
return powerCore(base, exp);
}
// 非递归快速幂(可选)
double PowerNonRecursive(double base, int exponent) {
if (exponent == 0) return 1.0;
long long exp = abs((long long)exponent);
double result = 1.0;
while (exp > 0) {
if (exp & 1) { // 若exp为奇数,乘当前base
result *= base;
}
base *= base; // base平方
exp >>= 1; // exp除以2
}
return exponent < 0 ? 1.0 / result : result;
}
// 测试示例
int main() {
cout << Power(2, 3) << endl; // 8
cout << Power(2, -3) << endl; // 0.125
cout << Power(3, 5) << endl; // 243
cout << Power(0, 5) << endl; // 0
cout << Power(5, 0) << endl; // 1
cout << PowerNonRecursive(2, 4) << endl; // 16
return 0;
}
4.14、大整数数相乘的问题。
cpp
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
string multiply(string num1, string num2) {
if (num1 == "0" || num2 == "0") return "0";
int n = num1.size(), m = num2.size();
vector<int> res(n + m, 0); // 结果最多n+m位
// 逆序遍历,逐位相乘
for (int i = n - 1; i >= 0; i--) {
int x = num1[i] - '0';
for (int j = m - 1; j >= 0; j--) {
int y = num2[j] - '0';
res[i + j + 1] += x * y; // 低位先存,i+j+1为当前位位置
}
}
// 处理进位
for (int i = n + m - 1; i > 0; i--) {
res[i - 1] += res[i] / 10;
res[i] %= 10;
}
// 转换为字符串(跳过前导零)
string result;
int start = res[0] == 0 ? 1 : 0;
for (int i = start; i < n + m; i++) {
result += to_string(res[i]);
}
return result;
}
// 测试示例
int main() {
string num1 = "123456789";
string num2 = "987654321";
cout << multiply(num1, num2) << endl; // 输出121932631112635269
return 0;
}
4.15、捣乱数
多人排成一个队列,我们认为从低到高是正确的序列,但是总有部分人不遵守秩序。
如果说,前面的人比后面的人高(两人身高一样认为是合适的),
那么我们就认为这两个人是一对"捣乱分子",比如说,现在存在一个序列:
176, 178, 180, 170, 171
这些捣乱分子对为
<176, 170>, <176, 171>, <178, 170>, <178, 171>, <180, 170>, <180, 171>,
那么,现在给出一个整型序列,请找出这些捣乱分子对的个数(仅给出捣乱分子对的数目即可,不用具体的对)
要求:
输入:
为一个文件(in),文件的每一行为一个序列。序列全为数字,数字间用","分隔。
输出:
为一个文件(out),每行为一个数字,表示捣乱分子的对数。
详细说明自己的解题思路,说明自己实现的一些关键点。
并给出实现的代码 ,并分析时间复杂度。
限制:
输入每行的最大数字个数为100000个,数字最长为6位。程序无内存使用限制。
cpp
#include <iostream>
#include <vector>
#include <fstream>
#include <sstream>
#include <string>
using namespace std;
// 归并过程中统计逆序对
long long mergeAndCount(vector<int>& nums, vector<int>& temp, int left, int mid, int right) {
long long count = 0;
int i = left; // 左子序列指针
int j = mid + 1; // 右子序列指针
int k = left; // 临时数组指针
while (i <= mid && j <= right) {
if (nums[i] <= nums[j]) {
temp[k++] = nums[i++];
} else {
// 左元素>右元素,左子序列i到mid均与j构成逆序对
count += mid - i + 1;
temp[k++] = nums[j++];
}
}
// 处理剩余元素
while (i <= mid) temp[k++] = nums[i++];
while (j <= right) temp[k++] = nums[j++];
// 拷贝回原数组
for (i = left; i <= right; i++) {
nums[i] = temp[i];
}
return count;
}
// 归并排序递归函数
long long mergeSortAndCount(vector<int>& nums, vector<int>& temp, int left, int right) {
long long count = 0;
if (left < right) {
int mid = left + (right - left) / 2;
count += mergeSortAndCount(nums, temp, left, mid); // 左子序列逆序对
count += mergeSortAndCount(nums, temp, mid + 1, right); // 右子序列逆序对
count += mergeAndCount(nums, temp, left, mid, right); // 跨子序列逆序对
}
return count;
}
// 统计逆序对总数
long long countInversions(vector<int>& nums) {
vector<int> temp(nums.size());
return mergeSortAndCount(nums, temp, 0, nums.size() - 1);
}
// 分割字符串为整数数组
vector<int> splitToInt(const string& line) {
vector<int> nums;
stringstream ss(line);
string token;
while (getline(ss, token, ',')) {
nums.push_back(stoi(token));
}
return nums;
}
int main() {
ifstream infile("in.txt");
ofstream outfile("out.txt");
string line;
while (getline(infile, line)) {
vector<int> nums = splitToInt(line);
long long result = countInversions(nums);
outfile << result << endl;
}
infile.close();
outfile.close();
return 0;
}
4.16、求1000!的未尾有几个0(用素数相乘的方法来做,如72=2*2*2*3*3);
cpp
#include <iostream>
using namespace std;
int countTrailingZeros(int n) {
int count = 0;
while (n > 0) {
n /= 5; // 依次计算5、25、125...的倍数个数
count += n; // 累加因数5的个数
}
return count;
}
int main() {
cout << "1000!末尾的0的个数:" << countTrailingZeros(1000) << endl; // 输出249
return 0;
}
4.17、编程实现:把十进制数(long型)分别以二进制和十六进制形式输出,不能使用printf系列库函数。
cpp
#include <iostream>
#include <vector>
using namespace std;
// 十进制转二进制并输出
void printBinary(long num) {
if (num == 0) {
cout << "0";
return;
}
// 处理负数:转换为无符号数(补码形式)
unsigned long n = static_cast<unsigned long>(num);
vector<char> bits;
while (n > 0) {
bits.push_back((n % 2) + '0'); // 取余,转为字符
n /= 2;
}
// 逆序输出
for (auto it = bits.rbegin(); it != bits.rend(); ++it) {
cout << *it;
}
}
// 十进制转十六进制并输出
void printHex(long num) {
if (num == 0) {
cout << "0";
return;
}
// 处理负数:转换为无符号数
unsigned long n = static_cast<unsigned long>(num);
vector<char> hex_chars;
const char* hex_map = "0123456789ABCDEF"; // 十六进制字符映射
while (n > 0) {
int remainder = n % 16;
hex_chars.push_back(hex_map[remainder]); // 取余,映射为字符
n /= 16;
}
// 逆序输出
for (auto it = hex_chars.rbegin(); it != hex_chars.rend(); ++it) {
cout << *it;
}
}
int main() {
long num;
cout << "输入一个十进制数:";
cin >> num;
cout << "二进制形式:";
printBinary(num);
cout << endl;
cout << "十六进制形式:";
printHex(num);
cout << endl;
return 0;
}
五:树
5.1、把二元查找树转变成排序的双向链表
题目:
输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。
要求不能创建任何新的结点,只调整指针的指向。
10
/ \
6 14
/ \ / \
4 8 12 16
转换成双向链表
4=6=8=10=12=14=16。
首先我们定义的二元查找树 节点的数据结构如下:
struct BSTreeNode
{
int m_nValue; // value of node
BSTreeNode *m_pLeft; // left child of node
BSTreeNode *m_pRight; // right child of node
};
cpp
#include <iostream>
using namespace std;
struct BSTreeNode {
int m_nValue;
BSTreeNode *m_pLeft;
BSTreeNode *m_pRight;
BSTreeNode(int val) : m_nValue(val), m_pLeft(nullptr), m_pRight(nullptr) {}
};
// 全局变量记录前驱节点,或用引用传递
BSTreeNode* prev = nullptr;
// 中序遍历转换为双向链表
void Convert(BSTreeNode* root) {
if (root == nullptr) return;
// 递归处理左子树
Convert(root->m_pLeft);
// 调整当前节点与前驱节点的指针
root->m_pLeft = prev;
if (prev != nullptr) {
prev->m_pRight = root;
}
prev = root; // 更新前驱为当前节点
// 递归处理右子树
Convert(root->m_pRight);
}
// 获取链表头节点(中序遍历的第一个节点)
BSTreeNode* GetListHead(BSTreeNode* root) {
BSTreeNode* p = root;
while (p != nullptr && p->m_pLeft != nullptr) {
p = p->m_pLeft;
}
return p;
}
// 打印双向链表
void PrintList(BSTreeNode* head) {
BSTreeNode* p = head;
while (p != nullptr) {
cout << p->m_nValue;
if (p->m_pRight != nullptr) {
cout << "=";
}
p = p->m_pRight;
}
cout << endl;
}
// 测试示例
int main() {
// 构建BST
BSTreeNode* root = new BSTreeNode(10);
root->m_pLeft = new BSTreeNode(6);
root->m_pRight = new BSTreeNode(14);
root->m_pLeft->m_pLeft = new BSTreeNode(4);
root->m_pLeft->m_pRight = new BSTreeNode(8);
root->m_pRight->m_pLeft = new BSTreeNode(12);
root->m_pRight->m_pRight = new BSTreeNode(16);
// 转换为双向链表
Convert(root);
BSTreeNode* head = GetListHead(root);
// 打印结果
PrintList(head); // 输出:4=6=8=10=12=14=16
return 0;
}
5.2、在二元树中找出和为某一值的所有路径
题目:输入一个整数和一棵二元树。
从树的根结点开始往下访问一直到叶结点所经过的所有结点形成一条路径。
打印出和与输入整数相等的所有路径。
例如 输入整数22和如下二元树
10
/ \
5 12
/ \
4 7
则打印出两条路径:10, 12和10, 5, 7。
二元树节点的数据结构定义为:
struct BinaryTreeNode // a node in the binary tree
{
int m_nValue; // value of node
BinaryTreeNode *m_pLeft; // left child of node
BinaryTreeNode *m_pRight; // right child of node
};
cpp
#include <iostream>
#include <vector>
using namespace std;
struct BinaryTreeNode {
int m_nValue;
BinaryTreeNode *m_pLeft;
BinaryTreeNode *m_pRight;
BinaryTreeNode(int val) : m_nValue(val), m_pLeft(nullptr), m_pRight(nullptr) {}
};
// 递归查找路径
void FindPath(BinaryTreeNode* root, int target, vector<int>& path, int currentSum) {
if (root == nullptr) return;
// 将当前节点加入路径,更新路径和
currentSum += root->m_nValue;
path.push_back(root->m_nValue);
// 叶节点且路径和等于目标值,打印路径
bool isLeaf = (root->m_pLeft == nullptr && root->m_pRight == nullptr);
if (currentSum == target && isLeaf) {
for (int val : path) {
cout << val << " ";
}
cout << endl;
}
// 递归访问左子树和右子树
FindPath(root->m_pLeft, target, path, currentSum);
FindPath(root->m_pRight, target, path, currentSum);
// 回溯:移除当前节点,探索其他路径
path.pop_back();
}
// 接口函数:初始化路径和当前和
void FindPath(BinaryTreeNode* root, int target) {
vector<int> path;
FindPath(root, target, path, 0);
}
// 测试示例
int main() {
// 构建二元树
BinaryTreeNode* root = new BinaryTreeNode(10);
root->m_pLeft = new BinaryTreeNode(5);
root->m_pRight = new BinaryTreeNode(12);
root->m_pLeft->m_pLeft = new BinaryTreeNode(4);
root->m_pLeft->m_pRight = new BinaryTreeNode(7);
// 查找和为22的路径
FindPath(root, 22);
return 0;
}
5.3、判断整数序列是不是二元查找树的后序遍历结果
题目:输入一个整数数组,判断该数组是不是某二元查找树的后序遍历的结果。
如果是返回true,否则返回false。
例如输入5、7、6、9、11、10、8,由于这一整数序列是如下树的后序遍历结果:
8
/ \
6 10
/ \ / \
5 7 9 11
因此返回true。
如果输入7、4、6、5,没有哪棵树的后序遍历的结果是这个序列,因此返回false。
cpp
#include <iostream>
#include <vector>
using namespace std;
// 递归验证后序遍历序列
bool verifyPostorder(vector<int>& postorder, int start, int end) {
if (start >= end) return true; // 空树或单节点,返回true
int root = postorder[end]; // 根节点
int i = start;
// 划分左子树(所有元素 < 根节点)
while (i < end && postorder[i] < root) {
i++;
}
// 检查右子树是否有元素 < 根节点(若有则非法)
int j = i;
while (j < end && postorder[j] > root) {
j++;
}
if (j != end) return false; // 右子树存在小于根节点的元素,返回false
// 递归验证左子树和右子树
return verifyPostorder(postorder, start, i - 1) && verifyPostorder(postorder, i, end - 1);
}
// 接口函数
bool VerifySquenceOfBST(vector<int>& postorder) {
if (postorder.empty()) return false; // 空序列返回false(根据题目要求调整)
return verifyPostorder(postorder, 0, postorder.size() - 1);
}
// 测试示例
int main() {
vector<int> seq1 = {5, 7, 6, 9, 11, 10, 8}; // 合法序列
vector<int> seq2 = {7, 4, 6, 5}; // 非法序列
cout << (VerifySquenceOfBST(seq1) ? "true" : "false") << endl; // 输出true
cout << (VerifySquenceOfBST(seq2) ? "true" : "false") << endl; // 输出false
return 0;
}
5.4、求二叉树中节点的最大距离...
如果我们把二叉树看成一个图,父子节点之间的连线看成是双向的,
我们姑且定义"距离"为两节点之间边的个数。
写一个程序,
求一棵二叉树中相距最远的两个节点之间的距离。
cpp
#include <iostream>
#include <algorithm>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
// 递归计算最大深度,并更新最大距离
int dfs(TreeNode* root, int& max_dist) {
if (root == nullptr) return 0;
// 计算左右子树的深度
int left_depth = dfs(root->left, max_dist);
int right_depth = dfs(root->right, max_dist);
// 更新最大距离(经过当前节点的路径长度)
max_dist = max(max_dist, left_depth + right_depth);
// 返回当前节点的最大深度
return max(left_depth, right_depth) + 1;
}
// 求二叉树的最大距离
int maxDistance(TreeNode* root) {
int max_dist = 0;
dfs(root, max_dist);
return max_dist;
}
// 测试示例
int main() {
// 构建二叉树
TreeNode* root = new TreeNode(1);
root->left = new TreeNode(2);
root->right = new TreeNode(3);
root->left->left = new TreeNode(4);
root->left->right = new TreeNode(5);
root->left->left->left = new TreeNode(6);
root->left->right->right = new TreeNode(7);
cout << "二叉树的最大距离:" << maxDistance(root) << endl; // 输出5(6-4-2-5-7)
return 0;
}
5.5、输入一颗二元查找树,将该树转换为它的镜像,
即在转换后的二元查找树中,左子树的结点都大于右子树的结点。
用递归和循环两种方法完成树的镜像转换。
例如输入:
8
/ \
6 10
/\ /\
5 7 9 11
输出:
8
/ \
10 6
/\ /\
11 9 7 5
定义二元查找树的结点为:
struct BSTreeNode // a node in the binary search tree (BST)
{
int m_nValue; // value of node
BSTreeNode *m_pLeft; // left child of node
BSTreeNode *m_pRight; // right child of node
};
cpp
#include <iostream>
using namespace std;
struct BSTreeNode {
int m_nValue;
BSTreeNode *m_pLeft;
BSTreeNode *m_pRight;
BSTreeNode(int val) : m_nValue(val), m_pLeft(nullptr), m_pRight(nullptr) {}
};
// 递归转换镜像
void MirrorRecursive(BSTreeNode* root) {
if (root == nullptr) return;
// 递归交换左右子树
MirrorRecursive(root->m_pLeft);
MirrorRecursive(root->m_pRight);
// 交换当前节点的左右指针
BSTreeNode* temp = root->m_pLeft;
root->m_pLeft = root->m_pRight;
root->m_pRight = temp;
}
#include <queue>
// 循环转换镜像(层序遍历)
void MirrorIterative(BSTreeNode* root) {
if (root == nullptr) return;
queue<BSTreeNode*> q;
q.push(root);
while (!q.empty()) {
BSTreeNode* node = q.front();
q.pop();
// 交换当前节点的左右指针
BSTreeNode* temp = node->m_pLeft;
node->m_pLeft = node->m_pRight;
node->m_pRight = temp;
// 左子树入队
if (node->m_pLeft != nullptr) {
q.push(node->m_pLeft);
}
// 右子树入队
if (node->m_pRight != nullptr) {
q.push(node->m_pRight);
}
}
}
// 中序遍历打印树(验证镜像结果)
void InOrderPrint(BSTreeNode* root) {
if (root == nullptr) return;
InOrderPrint(root->m_pLeft);
cout << root->m_nValue << " ";
InOrderPrint(root->m_pRight);
}
// 测试示例
int main() {
// 构建原树
BSTreeNode* root = new BSTreeNode(8);
root->m_pLeft = new BSTreeNode(6);
root->m_pRight = new BSTreeNode(10);
root->m_pLeft->m_pLeft = new BSTreeNode(5);
root->m_pLeft->m_pRight = new BSTreeNode(7);
root->m_pRight->m_pLeft = new BSTreeNode(9);
root->m_pRight->m_pRight = new BSTreeNode(11);
cout << "原树中序遍历:";
InOrderPrint(root); // 输出:5 6 7 8 9 10 11
cout << endl;
// 递归转换镜像
MirrorRecursive(root);
cout << "镜像树中序遍历(递归):";
InOrderPrint(root); // 输出:11 10 9 8 7 6 5
cout << endl;
// 再次转换回原树(测试循环法)
MirrorIterative(root);
cout << "还原后中序遍历(循环):";
InOrderPrint(root); // 输出:5 6 7 8 9 10 11
cout << endl;
return 0;
}
5.6、输入一颗二元树,从上往下按层打印树的每个结点,同一层中按照从左往右的顺序打印。
例如输入
8
/ \
6 10
/ \ / \
5 7 9 11
输出8 6 10 5 7 9 11。
cpp
#include <iostream>
#include <queue>
using namespace std;
struct BinaryTreeNode {
int m_nValue;
BinaryTreeNode *m_pLeft;
BinaryTreeNode *m_pRight;
BinaryTreeNode(int val) : m_nValue(val), m_pLeft(nullptr), m_pRight(nullptr) {}
};
// 层序遍历打印二叉树
void PrintFromTopToBottom(BinaryTreeNode* root) {
if (root == nullptr) return;
queue<BinaryTreeNode*> q;
q.push(root); // 根节点入队
while (!q.empty()) {
BinaryTreeNode* node = q.front();
q.pop(); // 取出队首节点
cout << node->m_nValue << " "; // 访问当前节点
// 左子节点入队
if (node->m_pLeft != nullptr) {
q.push(node->m_pLeft);
}
// 右子节点入队
if (node->m_pRight != nullptr) {
q.push(node->m_pRight);
}
}
}
// 测试示例
int main() {
// 构建二叉树
BinaryTreeNode* root = new BinaryTreeNode(8);
root->m_pLeft = new BinaryTreeNode(6);
root->m_pRight = new BinaryTreeNode(10);
root->m_pLeft->m_pLeft = new BinaryTreeNode(5);
root->m_pLeft->m_pRight = new BinaryTreeNode(7);
root->m_pRight->m_pLeft = new BinaryTreeNode(9);
root->m_pRight->m_pRight = new BinaryTreeNode(11);
// 层序打印
PrintFromTopToBottom(root); // 输出:8 6 10 5 7 9 11
return 0;
}
5.7、求一个二叉树中任意两个节点间的最大距离,两个节点的距离的定义是 这两个节点间边的个数,
比如某个孩子节点和父节点间的距离是1,和相邻兄弟节点间的距离是2,优化时间空间复杂度。
cpp
#include <iostream>
#include <algorithm>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
// 辅助结构体:存储子树的最大深度和最大直径
struct Result {
int depth; // 子树的最大深度(根节点到叶节点的边数)
int diameter;// 子树的最大直径
Result(int d, int dia) : depth(d), diameter(dia) {}
};
// 后序遍历递归计算
Result dfs(TreeNode* root) {
if (root == nullptr) {
return Result(-1, 0); // 空节点深度为-1(边数),直径为0
}
Result left = dfs(root->left);
Result right = dfs(root->right);
// 当前节点的深度:左右子树深度的最大值 + 1(边数)
int curr_depth = max(left.depth, right.depth) + 1;
// 经过当前节点的路径长度:左深度 + 右深度 + 2(左右子树各一条边)
int curr_path = left.depth + right.depth + 2;
// 当前子树的最大直径:max(左直径, 右直径, 经过当前节点的路径长度)
int curr_diameter = max({left.diameter, right.diameter, curr_path});
return Result(curr_depth, curr_diameter);
}
// 求二叉树的最大距离
int maxDistance(TreeNode* root) {
if (root == nullptr) return 0;
return dfs(root).diameter;
}
// 测试示例
int main() {
// 构建二叉树
TreeNode* root = new TreeNode(1);
root->left = new TreeNode(2);
root->right = new TreeNode(3);
root->left->left = new TreeNode(4);
root->left->right = new TreeNode(5);
root->left->left->left = new TreeNode(6);
root->left->right->right = new TreeNode(7);
cout << "二叉树的最大距离:" << maxDistance(root) << endl; // 输出5(6-4-2-5-7)
return 0;
}
5.8、递归和非递归俩种方法实现二叉树的前序遍历。
cpp
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
// 递归前序遍历
void preorderRecursive(TreeNode* root, vector<int>& result) {
if (root == nullptr) return;
result.push_back(root->val); // 访问根节点
preorderRecursive(root->left, result); // 递归左子树
preorderRecursive(root->right, result); // 递归右子树
}
// 非递归前序遍历(栈实现)
void preorderIterative(TreeNode* root, vector<int>& result) {
if (root == nullptr) return;
stack<TreeNode*> stk;
stk.push(root);
while (!stk.empty()) {
TreeNode* node = stk.top();
stk.pop();
result.push_back(node->val); // 访问当前节点
// 先右后左入栈,保证左子树先处理
if (node->right != nullptr) {
stk.push(node->right);
}
if (node->left != nullptr) {
stk.push(node->left);
}
}
}
int main() {
// 构建二叉树
TreeNode* root = new TreeNode(1);
root->right = new TreeNode(2);
root->right->left = new TreeNode(3);
vector<int> res_recursive, res_iterative;
// 递归遍历
preorderRecursive(root, res_recursive);
cout << "递归前序遍历:";
for (int val : res_recursive) {
cout << val << " "; // 输出:1 2 3
}
cout << endl;
// 非递归遍历
preorderIterative(root, res_iterative);
cout << "非递归前序遍历:";
for (int val : res_iterative) {
cout << val << " "; // 输出:1 2 3
}
cout << endl;
return 0;
}
5.9、输入一棵二元树的根结点,求该树的深度。
从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
例如:输入二元树:
10
/ \
6 14
/ / \
4 12 16
输出该树的深度3。
二元树的结点定义如下:
struct SBinaryTreeNode // a node of the binary tree
{
int m_nValue; // value of node
SBinaryTreeNode *m_pLeft; // left child of node
SBinaryTreeNode *m_pRight; // right child of node
};
分析:这道题本质上还是考查二元树的遍历。
cpp
#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
struct SBinaryTreeNode {
int m_nValue;
SBinaryTreeNode *m_pLeft;
SBinaryTreeNode *m_pRight;
SBinaryTreeNode(int val) : m_nValue(val), m_pLeft(nullptr), m_pRight(nullptr) {}
};
// 递归求树的深度
int TreeDepthRecursive(SBinaryTreeNode* root) {
if (root == nullptr) return 0; // 空树深度为0
int leftDepth = TreeDepthRecursive(root->m_pLeft); // 左子树深度
int rightDepth = TreeDepthRecursive(root->m_pRight); // 右子树深度
return max(leftDepth, rightDepth) + 1; // 当前树深度 = 子树最大深度 + 1(当前节点)
}
int main() {
// 构建题目示例中的二元树
SBinaryTreeNode* root = new SBinaryTreeNode(10);
root->m_pLeft = new SBinaryTreeNode(6);
root->m_pRight = new SBinaryTreeNode(14);
root->m_pLeft->m_pLeft = new SBinaryTreeNode(4);
root->m_pRight->m_pLeft = new SBinaryTreeNode(12);
root->m_pRight->m_pRight = new SBinaryTreeNode(16);
cout << "递归法求深度:" << TreeDepthRecursive(root) << endl; // 输出3
cout << "迭代法求深度:" << TreeDepthIterative(root) << endl; // 输出3
return 0;
}
5.10、二叉树两个结点的最低共同父结点
题目:二叉树的结点定义如下:
struct TreeNode
{
int m_nvalue;
TreeNode* m_pLeft;
TreeNode* m_pRight;
};
输入二叉树中的两个结点,输出这两个结点在数中最低的共同父结点。
分析:求数中两个结点的最低共同结点是面试中经常出现的一个问题。这个问题至少有两个变种。
cpp
#include <iostream>
#include <vector>
using namespace std;
struct TreeNode {
int m_nvalue;
TreeNode* m_pLeft;
TreeNode* m_pRight;
TreeNode(int val) : m_nvalue(val), m_pLeft(nullptr), m_pRight(nullptr) {}
};
// BST的最低共同父节点
TreeNode* LCA_BST(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == nullptr) return nullptr;
// p和q都小于根节点,递归左子树
if (p->m_nvalue < root->m_nvalue && q->m_nvalue < root->m_nvalue) {
return LCA_BST(root->m_pLeft, p, q);
}
// p和q都大于根节点,递归右子树
if (p->m_nvalue > root->m_nvalue && q->m_nvalue > root->m_nvalue) {
return LCA_BST(root->m_pRight, p, q);
}
// 否则当前节点为LCA
return root;
}
// 普通二叉树的最低共同父节点(递归法)
TreeNode* LCA_Common_Recursive(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == nullptr || root == p || root == q) {
return root; // 找到p/q或空节点,返回当前节点
}
// 递归查找左右子树
TreeNode* left = LCA_Common_Recursive(root->m_pLeft, p, q);
TreeNode* right = LCA_Common_Recursive(root->m_pRight, p, q);
// 左右子树各找到一个节点,当前节点为LCA
if (left != nullptr && right != nullptr) {
return root;
}
// 仅左/右子树找到节点,返回对应子树的结果
return left != nullptr ? left : right;
}
int main() {
// 构建二叉树
TreeNode* root = new TreeNode(1);
root->m_pLeft = new TreeNode(2);
root->m_pRight = new TreeNode(3);
root->m_pLeft->m_pLeft = new TreeNode(4);
root->m_pLeft->m_pRight = new TreeNode(5);
root->m_pRight->m_pLeft = new TreeNode(6);
root->m_pRight->m_pRight = new TreeNode(7);
TreeNode* p = root->m_pLeft->m_pLeft; // 4
TreeNode* q = root->m_pLeft->m_pRight; // 5
// BST测试(假设该树为BST)
TreeNode* lca_bst = LCA_BST(root, p, q);
cout << "BST的LCA:" << lca_bst->m_nvalue << endl; // 输出2
// 普通二叉树路径法测试
TreeNode* lca_path = LCA_Common_Path(root, p, q);
cout << "普通二叉树(路径法)LCA:" << lca_path->m_nvalue << endl; // 输出2
// 普通二叉树递归法测试
TreeNode* lca_recursive = LCA_Common_Recursive(root, p, q);
cout << "普通二叉树(递归法)LCA:" << lca_recursive->m_nvalue << endl; // 输出2
return 0;
}
5.11、怎样编写一个程序,把一个有序整数数组放到二叉树中?
cpp
#include <iostream>
#include <vector>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
// 递归构建平衡BST
TreeNode* sortedArrayToBST(vector<int>& nums, int left, int right) {
if (left > right) return nullptr; // 递归终止条件:区间无效
int mid = left + (right - left) / 2; // 取中间位置(避免溢出)
TreeNode* root = new TreeNode(nums[mid]); // 中间元素为根节点
// 递归构建左子树(左半区间)
root->left = sortedArrayToBST(nums, left, mid - 1);
// 递归构建右子树(右半区间)
root->right = sortedArrayToBST(nums, mid + 1, right);
return root;
}
// 对外接口函数
TreeNode* sortedArrayToBST(vector<int>& nums) {
return sortedArrayToBST(nums, 0, nums.size() - 1);
}
// 中序遍历验证BST(输出应为升序)
void inOrderPrint(TreeNode* root) {
if (root == nullptr) return;
inOrderPrint(root->left);
cout << root->val << " ";
inOrderPrint(root->right);
}
// 测试示例
int main() {
vector<int> nums = {-10, -3, 0, 5, 9}; // 有序数组
TreeNode* root = sortedArrayToBST(nums);
cout << "BST的中序遍历结果:";
inOrderPrint(root); // 输出:-10 -3 0 5 9
cout << endl;
return 0;
}
六:其他
6.1、求一个矩阵中最大的二维矩阵(元素和最大).如:
1 2 0 3 4
2 3 4 5 1
1 1 5 3 0
中最大的是:
4 5
5 3
要求:(1)写出算法;(2)分析时间复杂度;(3)用C写出关键代码
cpp
#include <stdio.h>
#include <limits.h>
// Kadane算法:求一维数组的最大子数组和
int kadane(int* arr, int len) {
int max_current = arr[0];
int max_global = arr[0];
for (int i = 1; i < len; i++) {
max_current = (arr[i] > max_current + arr[i]) ? arr[i] : (max_current + arr[i]);
if (max_current > max_global) {
max_global = max_current;
}
}
return max_global;
}
// 求矩阵中最大子矩阵和
int maxSubmatrixSum(int** matrix, int rows, int cols) {
int max_sum = INT_MIN;
int* col_sum = (int*)malloc(cols * sizeof(int)); // 存储列求和结果
// 固定上边界i
for (int i = 0; i < rows; i++) {
// 初始化列求和数组为0
for (int k = 0; k < cols; k++) {
col_sum[k] = 0;
}
// 扩展下边界j
for (int j = i; j < rows; j++) {
// 累加i到j行的每列元素
for (int k = 0; k < cols; k++) {
col_sum[k] += matrix[j][k];
}
// 用Kadane算法求当前列求和数组的最大子数组和
int current_max = kadane(col_sum, cols);
if (current_max > max_sum) {
max_sum = current_max;
}
}
}
free(col_sum);
return max_sum;
}
// 测试示例
int main() {
int matrix[3][5] = {
{1, 2, 0, 3, 4},
{2, 3, 4, 5, 1},
{1, 1, 5, 3, 0}
};
int rows = 3, cols = 5;
int** mat = (int**)malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
mat[i] = matrix[i];
}
int result = maxSubmatrixSum(mat, rows, cols);
printf("最大子矩阵和:%d\n", result); // 输出17(对应子矩阵4 5;5 3,和为4+5+5+3=17)
free(mat);
return 0;
}
6.2、设计一个栈结构,满足一下条件:min,push,pop操作的时间复杂度为O(1)。
cpp
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#define MAX_SIZE 100 // 栈的最大容量
typedef struct {
int data[MAX_SIZE]; // 数据栈
int min_stack[MAX_SIZE]; // 最小值栈
int top; // 栈顶指针(数据栈和最小值栈共用)
} MinStack;
// 初始化栈
void MinStackInit(MinStack* stack) {
stack->top = -1;
}
// 判断栈是否为空
int MinStackIsEmpty(MinStack* stack) {
return stack->top == -1;
}
// 判断栈是否已满
int MinStackIsFull(MinStack* stack) {
return stack->top == MAX_SIZE - 1;
}
// push操作
void MinStackPush(MinStack* stack, int val) {
if (MinStackIsFull(stack)) {
printf("栈溢出!\n");
return;
}
stack->top++;
stack->data[stack->top] = val;
// 更新最小值栈:若新元素更小或栈空,压入新元素;否则压入当前最小值
if (MinStackIsEmpty(stack) || val <= stack->min_stack[stack->top - 1]) {
stack->min_stack[stack->top] = val;
} else {
stack->min_stack[stack->top] = stack->min_stack[stack->top - 1];
}
}
// pop操作
int MinStackPop(MinStack* stack) {
if (MinStackIsEmpty(stack)) {
printf("栈为空!\n");
return INT_MIN;
}
int val = stack->data[stack->top];
stack->top--;
return val;
}
// min操作
int MinStackMin(MinStack* stack) {
if (MinStackIsEmpty(stack)) {
printf("栈为空!\n");
return INT_MIN;
}
return stack->min_stack[stack->top];
}
// 测试示例
int main() {
MinStack stack;
MinStackInit(&stack);
MinStackPush(&stack, 3);
printf("当前最小值:%d\n", MinStackMin(&stack)); // 输出3
MinStackPush(&stack, 2);
printf("当前最小值:%d\n", MinStackMin(&stack)); // 输出2
MinStackPush(&stack, 5);
printf("当前最小值:%d\n", MinStackMin(&stack)); // 输出2
MinStackPop(&stack);
printf("当前最小值:%d\n", MinStackMin(&stack)); // 输出2
MinStackPop(&stack);
printf("当前最小值:%d\n", MinStackMin(&stack)); // 输出3
return 0;
}
6.3、用递归颠倒一个栈。例如输入栈{1, 2, 3, 4, 5},1在栈顶。颠倒之后的栈为{5, 4, 3, 2, 1},5处在栈顶。
cpp
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_SIZE 100
typedef struct {
int data[MAX_SIZE];
int top;
} Stack;
// 初始化栈
void initStack(Stack* stack) {
stack->top = -1;
}
// 判断栈是否为空
bool isEmpty(Stack* stack) {
return stack->top == -1;
}
// 判断栈是否已满
bool isFull(Stack* stack) {
return stack->top == MAX_SIZE - 1;
}
// 压栈
void push(Stack* stack, int val) {
if (isFull(stack)) {
printf("栈溢出!\n");
return;
}
stack->data[++stack->top] = val;
}
// 弹栈
int pop(Stack* stack) {
if (isEmpty(stack)) {
printf("栈为空!\n");
return -1;
}
return stack->data[stack->top--];
}
// 取栈顶元素
int peek(Stack* stack) {
if (isEmpty(stack)) {
printf("栈为空!\n");
return -1;
}
return stack->data[stack->top];
}
// 将元素插入栈底(递归)
void insertAtBottom(Stack* stack, int val) {
if (isEmpty(stack)) {
push(stack, val); // 栈空时直接压入
} else {
int temp = pop(stack); // 弹出栈顶元素
insertAtBottom(stack, val); // 递归插入当前元素到栈底
push(stack, temp); // 将弹出元素压回
}
}
// 递归颠倒栈
void reverseStack(Stack* stack) {
if (!isEmpty(stack)) {
int temp = pop(stack); // 弹出栈顶元素
reverseStack(stack); // 递归颠倒剩余栈
insertAtBottom(stack, temp); // 将弹出元素插入栈底
}
}
// 打印栈(从栈顶到栈底)
void printStack(Stack* stack) {
if (isEmpty(stack)) {
printf("栈为空!\n");
return;
}
for (int i = stack->top; i >= 0; i--) {
printf("%d ", stack->data[i]);
}
printf("\n");
}
// 测试示例
int main() {
Stack stack;
initStack(&stack);
// 压入元素:1(栈顶)、2、3、4、5(栈底)
push(&stack, 5);
push(&stack, 4);
push(&stack, 3);
push(&stack, 2);
push(&stack, 1);
printf("原栈(栈顶→栈底):");
printStack(&stack); // 输出:1 2 3 4 5
reverseStack(&stack);
printf("颠倒后栈(栈顶→栈底):");
printStack(&stack); // 输出:5 4 3 2 1
return 0;
}
6.4、输入N, 打印 N*N 矩阵
比如 N = 3,打印:
1 2 3
8 9 4
7 6 5
N = 4,打印:
1 2 3 4
12 13 14 5
11 16 15 6
10 9 8 7
cpp
#include <stdio.h>
#include <stdlib.h>
// 生成并打印N*N螺旋矩阵
void printSpiralMatrix(int N) {
// 动态分配N*N矩阵
int** matrix = (int**)malloc(N * sizeof(int*));
for (int i = 0; i < N; i++) {
matrix[i] = (int*)malloc(N * sizeof(int));
}
int top = 0, bottom = N - 1; // 上下边界
int left = 0, right = N - 1; // 左右边界
int num = 1; // 当前要填充的数字
while (top <= bottom && left <= right) {
// 1. 从左到右填充上边界
for (int i = left; i <= right; i++) {
matrix[top][i] = num++;
}
top++; // 上边界下移
// 2. 从上到下填充右边界
for (int i = top; i <= bottom; i++) {
matrix[i][right] = num++;
}
right--; // 右边界左移
// 3. 从右到左填充下边界(需保证上边界≤下边界)
if (top <= bottom) {
for (int i = right; i >= left; i--) {
matrix[bottom][i] = num++;
}
bottom--; // 下边界上移
}
// 4. 从下到上填充左边界(需保证左边界≤右边界)
if (left <= right) {
for (int i = bottom; i >= top; i--) {
matrix[i][left] = num++;
}
left++; // 左边界右移
}
}
// 打印矩阵
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
// 释放内存
for (int i = 0; i < N; i++) {
free(matrix[i]);
}
free(matrix);
}
// 测试示例
int main() {
int N;
printf("请输入N:");
scanf("%d", &N);
printSpiralMatrix(N);
return 0;
}
6.5、非递归实现斐波那契数列
cpp
#include <stdio.h>
// 计算第n项斐波那契数(非递归)
int fibonacci(int n) {
if (n == 0) return 0;
if (n == 1) return 1;
int a = 0, b = 1; // a=F(n-2), b=F(n-1)
int result = 0;
for (int i = 2; i <= n; i++) {
result = a + b; // 计算当前项F(i)
a = b; // 更新a为F(i-1)
b = result; // 更新b为F(i)
}
return result;
}
// 打印前n项斐波那契数列
void printFibonacci(int n) {
if (n <= 0) {
printf("请输入正整数!\n");
return;
}
printf("斐波那契数列前%d项:", n);
int a = 0, b = 1;
if (n >= 1) printf("%d ", a);
if (n >= 2) printf("%d ", b);
for (int i = 3; i <= n; i++) {
int c = a + b;
printf("%d ", c);
a = b;
b = c;
}
printf("\n");
}
// 测试示例
int main() {
int n;
printf("请输入要计算的斐波那契数列项数:");
scanf("%d", &n);
printf("第%d项斐波那契数:%d\n", n, fibonacci(n));
printFibonacci(n);
return 0;
}
6.6、设编号为1,2,... n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
cpp
#include <stdio.h>
#include <stdlib.h>
// 模拟约瑟夫环,返回出列顺序数组
int* josephus(int n, int k, int m, int* out_len) {
*out_len = n;
int* result = (int*)malloc(n * sizeof(int)); // 存储出列顺序
int* status = (int*)calloc(n, sizeof(int)); // 0表示未出列,1表示出列
int count = 0; // 已出列人数
int current = k - 1; // 当前起始位置(从0开始索引)
int step = 0; // 报数计数器
while (count < n) {
if (status[current] == 0) { // 当前人未出列
step++;
if (step == m) { // 数到m,出列
status[current] = 1;
result[count++] = current + 1; // 转换为1-based编号
step = 0; // 重置报数
}
}
current = (current + 1) % n; // 移动到下一位(环形)
}
free(status);
return result;
}
// 测试示例
int main() {
int n, k, m;
printf("请输入人数n、起始报数位置k、报数m:");
scanf("%d %d %d", &n, &k, &m);
int out_len;
int* out_order = josephus(n, k, m, &out_len);
printf("出列顺序:");
for (int i = 0; i < out_len; i++) {
printf("%d ", out_order[i]);
}
printf("\n");
free(out_order);
return 0;
}