C语言题目的多种解法分享 2之字符串左旋和补充题

简介: C语言题目的多种解法分享 2之字符串左旋和补充题

前言

有的时候,这个系列专栏中的解法之间并无优劣,只是给大家提供不同的解题思路

我决定将代码实现的过程写成注释,方便大家直接找到对应的函数,只有需要补充说明的知识才会单拿出来强调

这个系列的文章会更的比较慢,因为多种解法的需要慢慢收集、整理

字符串左旋

实现一个函数,可以左旋字符串中的k个字符。

例如:

ABCD左旋一个字符得到BCDA

ABCD左旋两个字符得到CDAB

暴力求解法1:数组传参

#include<stdio.h>
#include<string.h>
 void left_move(char arr[], int k)
{
  int i = 0;
  int len = strlen(arr);
  for (i = 0; i < k; i++)
  {
    char tmp = arr[0];
    int j = 0;
    for (j = 0; j < len - 1; j++)
    {
      arr[j] = arr[j + 1];
    }
    arr[len - 1] = tmp;
  }
}
int main()
{
  int k = 0;
  scanf("%d", &k);
  char arr[] = "abcdef";
  printf("%s\n", arr);
  left_move(arr,k);
  printf("%s\n", arr);
  return 0;
}

在此简单的解释一下:先将前面的元素提取出来存在别的地方,然后后面的元素向前移动一位,再将前面的元素放在后面

通过循环即可实现

暴力求解法2:指针传参

#include<stdio.h>
#include<string.h>
#include<assert.h>
//函数主体(应用场景)
void left_move(char *arr, int k)
{
  assert(arr);
  //为了良好的代码风格(前面在《实用调试技巧》中有提到过),我们可以在函数体开头使用断言,防止arr是空指针
  int i = 0;
  for (i = 0; i < k; i++)//旋转一个字符
  {
    int len = strlen(arr);//求数组长度
    char* tmp = arr;//存储第一个元素
    int j = 0;
    for (j = 0; j < len - 1; j++)//把后面的元素都向前移动一位
    {
      *(arr + j) = *(arr + j + 1);
      //因为此处会访问到j+1,所以如果j<len的话,会出现数组越界的情况,所以在for循环的判断那里要注意一下
    }
    *(arr + len - 1) = tmp;
    //将第一个元素存储到最后
  }
}
int main()
{
  char arr[] = "abcdef";
  int k = 0;
  scanf("%d", &k);
  left_move(arr, k);//左旋函数
  printf("%s\n", arr);//输出结果
  return 0;
}

提示:使用assert

使用assert可以改善我们的代码风格,具体原因在我的另一篇文章中有详细说明,感兴趣的朋友可以去看一看

三步翻转法

思路:

先逆序前k个元素,再逆序后面的元素,最后再逆序整体

也就是同一个逆序函数要调用三次,我们为了整洁,就单独封装出一个reverse函数

需求:

想要实现逆序,就需要知道两端的地址

reverse函数

void reverse(char* left, char* right)
{
  assert(left);//建议存在指针传参的时候,就使用断言,反正也不吃亏
  assert(right);
  while (left<right)
  {
    char tmp = *left;
    *left = *right;
    *right = tmp;
    left++;
    right--;
  }
}

left_move函数

void left_move(char* arr, int k)
{
  assert(arr);
  int len = strlen(arr);
  assert(len);//判断k是否超出数组大小
  reverse(arr, arr + k - 1);//逆序左边k个元素
  reverse(arr + k, arr + len - 1);//逆序右边的元素
  reverse(arr, arr + len - 1);//逆序整体
}

代码

#include<stdio.h>
#include<string.h>
#include<assert.h>
void reverse(char* left, char* right)
{
  assert(left);
  assert(right);
  while (left<right)
  {
    char tmp = *left;
    *left = *right;
    *right = tmp;
    left++;
    right--;
  }
}
void left_move(char* arr, int k)
{
  assert(arr);
  int len = strlen(arr);
  assert(len);
  reverse(arr, arr + k - 1);//逆序左边k个元素
  reverse(arr + k, arr + len - 1);//逆序右边的元素
  reverse(arr, arr + len - 1);//逆序整体
}
int main()
{
  char arr[] = "abcdef";
  int k = 0;
  scanf("%d", &k);
  left_move(arr, k);//左旋函数
  printf("%s\n", arr);//输出结果
  return 0;
}

拓展

做一道类似的题,帮助大家巩固

写一个函数,判断一个字符串是否为另外一个字符串旋转之后的字符串。

例如:给定s1 =AABCD和s2 = BCDAA,返回1

给定s1=abcd和s2=ACBD,返回0.

AABCD左旋一个字符得到ABCDA

AABCD左旋两个字符得到BCDAA

AABCD右旋一个字符得到DAABC

解法一:穷举,列出所有可能

void reverse(char* left, char* right)
{
  assert(left);
  assert(right);
  while (left<right)
  {
    char tmp = *left;
    *left = *right;
    *right = tmp;
    left++;
    right--;
  }
}
int left_move(char* arr, int k)
{
  assert(arr);
  int len = strlen(arr);
  assert(len);
  reverse(arr, arr + k - 1);//逆序左边k个元素
  reverse(arr + k, arr + len - 1);//逆序右边的元素
  reverse(arr, arr + len - 1);//逆序整体
}
int is_left_move(char* s1, char* s2)
{
  int len = strlen(s1);//求字符串长度,来判断有多少种可能性
  int i = 0;
  for (i = 0; i < len; i++)
  {
    left_move(s1, 1);//左旋的每种可能
    int ret =strcmp(s1, s2);//比较二者是否相等
    if (ret == 0)
    {
      return 1;
    }
  }
    return 0;
}
int main()
{
  char arr1[] = "abcdef";//此处不能使用指针,因为使用指针变量的话,它就是常量,是不能改变的
  char arr2[] = "cdefab";
  int ret = is_left_move(arr1, arr2);//接收返回值来判断是否是
  if (ret == 1)
  {
    printf("yes\n");//是
  }
  else
  {
    printf("no\n");//不是
  }
  return 0;
}

解法二:双倍数组

创建两个数组,如果要对比的字符串,是这个大的数组的子集,那就符合要求,不然就不符合要求

is_left_move函数的实现

int is_left_move(char* s1, char* s2)
{
  int len2 = strlen(s2);
  //分为两步:
  //1.在str1字符串中再放一个str1字符串,要确保str1足够大,并且str1一定要确定数组大小,不然在使用srtncat函数的时候会报错
  // strnact函数:字符串操作函数
  int len1 = strlen(s1);
  if (len1 != len2)
  {
    return 0;
  }
  strncat(s1, s2, len1);
  //2.判断str2指向的字符串是否是str1指向的字符串的子串(类似子集)
  //strstr:找子串的函数
  char* ret = strstr(s1, s2);
  if (ret == NULL)
  {
    return 0;
  }
  else
  {
    return 1;
  }
  return 0;
}

单独说明:

此处单独说明三个比较重要的函数:

stract与strnact

strcat:字符串操作函数,需要包含头文件<string.h>

语法格式:

stract(s1, s2);//把后者追加到前者的后面

但是,要注意自己追加自己是不能使用这个函数的,程序会直接报错

简单的解释一下:

我们创建两个数组,分别存储abc\0和def\0

想要追加的话,首先,要找到第一个字符串中的\0,然后用’d‘取代\0,之后存入ef\0,遇到\0说明追加结束,

而如果想要自己追加自己,我们还是要先找到\0,然后再放入abc,但当我们想放入\0时,却发现原来字符串中的\0已经被我替换成’a‘了,这样追加永远无法结束,程序崩溃~

那么我们可以使用strnact函数

语法格式

strnact(s1, s2, len)

此处简单的说明一下吧,strnact比stract多了一个参数

下图是二者的函数

strnact函数多了一个count参数

前者是直接追加,遇到\0,就停止追加

后者则是追加count位字符之后停止追加

strstr

字符串操作函数,找后者是不是前者的子串

语法格式:

strstr(s1, s2)

返回值的说明:

如果是子串,就返回s2的首元素地址

如果不是子串,就返回空指针NULL

小疑问

代码此时看起来已经可以运行了,但是小明这时候提出了一个疑问:

如果s2是cdef,输出结果是什么

输出结果是yes

这就是问题所在,所以我们在使用那些库函数之前,先要进行一个判断:s1和s2的长度是否相等,如果相等再去判断,如果不等,直接返回0结束判断。

最终代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<assert.h>
int is_left_move(char* s1, char* s2)
{
  int len1 = strlen(s1);
  int len2 = strlen(s2);
  if (len1 != len2)
  {
    return 0;
  }
  strncat(s1, s2, len1);
  char* ret = strstr(s1, s2);
  if (ret == NULL)
  {
    return 0;
  }
  else
  {
    return 1;
  }
}
int main()
{
  char arr1[50] = "abcdef";//此处不能使用指针,因为使用指针变量的话,它就是常量,是不能改变的
  char arr2[] = "cdefab";
  int ret = is_left_move(arr1, arr2);//接收返回值来判断是否是
  if (ret == 1)
  {
    printf("yes\n");//是
  }
  else
  {
    printf("no\n");//不是
  }
  return 0;
}

结语

本来是想整理五道题再发的,但现在刚写两道就四千多字了,那我想还是发出去吧,毕竟字符串左旋也比较经典,就单独列出一篇来。

希望对各位有帮助,我们下次见

相关文章
|
1月前
|
程序员 C语言
【C语言】LeetCode(力扣)上经典题目
【C语言】LeetCode(力扣)上经典题目
|
1月前
|
C语言 C++
【C语言】解决不同场景字符串问题:巧妙运用字符串函数
【C语言】解决不同场景字符串问题:巧妙运用字符串函数
|
2月前
|
存储 C语言
【C语言基础考研向】10 字符数组初始化及传递和scanf 读取字符串
本文介绍了C语言中字符数组的初始化方法及其在函数间传递的注意事项。字符数组初始化有两种方式:逐个字符赋值或整体初始化字符串。实际工作中常用后者,如`char c[10]=&quot;hello&quot;`。示例代码展示了如何初始化及传递字符数组,并解释了为何未正确添加结束符`\0`会导致乱码。此外,还讨论了`scanf`函数读取字符串时忽略空格和回车的特点。
|
2月前
|
存储 Serverless C语言
【C语言基础考研向】11 gets函数与puts函数及str系列字符串操作函数
本文介绍了C语言中的`gets`和`puts`函数,`gets`用于从标准输入读取字符串直至换行符,并自动添加字符串结束标志`\0`。`puts`则用于向标准输出打印字符串并自动换行。此外,文章还详细讲解了`str`系列字符串操作函数,包括统计字符串长度的`strlen`、复制字符串的`strcpy`、比较字符串的`strcmp`以及拼接字符串的`strcat`。通过示例代码展示了这些函数的具体应用及注意事项。
121 7
|
2月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
|
2月前
|
C语言
C语言 字符串操作函数
本文档详细介绍了多个常用的字符串操作函数,包括 `strlen`、`strcpy`、`strncpy`、`strcat`、`strncat`、`strcmp`、`strncpy`、`sprintf`、`itoa`、`strchr`、`strspn`、`strcspn`、`strstr` 和 `strtok`。每个函数均提供了语法说明、参数解释、返回值描述及示例代码。此外,还给出了部分函数的自实现版本,帮助读者深入理解其工作原理。通过这些函数,可以轻松地进行字符串长度计算、复制、连接、比较等操作。
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
33 3
|
6天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
21 6
|
25天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
34 10
|
19天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。