编程艺术 - 第二章 、俩个字符串是否包含问题以及扩展

简介: 编程艺术 - 第二章 、俩个字符串是否包含问题以及扩展

1、题目

假设这有一个各种字母组成的字符串,假设这还有另外一个字符串,而且这个字符串里的字

母数相对少一些。从算法是讲,什么方法能最快的查出所有小字符串里的字母在大字符串里

都有?

比如,如果是下面两个字符串:

String 1: ABCDEFGHLMNOPQRS

String 2: DCGSRQPOM

答案是 true,所有在 string2 里的字母 string1 也都有。

如果是下面两个字符串:

String 1: ABCDEFGHLMNOPQRS

String 2: DCGSRQPOZ

答案是 false,因为第二个字符串里的 Z 字母不在第一个字符串里


2、分析

2.1、暴力法

最简单的思路就是,让子串中的每一个字符都去与目标串的字符去匹配,匹配成功就继续匹配,若匹配失败直接false。时间复杂度为O(n^2),空间复杂度为O(1);

C

#include<stdio.h>
#include<string.h>
#include<stdbool.h>
//暴力求解
bool CompareSting(char* dest, int dlen, char* sub, int slen)
{
  int i = 0;//目标串指针
  int j = 0;//子串指针
  for (j = 0; j < slen; j++)//O(n^2)
  {
    int flag = 0;
    for (i = 0; i < dlen; i++)
    {
      if (sub[j] == dest[i])
      {
        flag = 1;
        break;
      }
    }
    if (flag == 0)
    {
      return false;
    }
  }
  return true;
}
int main()
{
  char dest[] = "ABCDEFGHLMNOPQRS";
  char sub[] = "DCGSRQPOM";
  int dlen = sizeof(dest) / sizeof(dest[0]) - 1;
  int slen = sizeof(sub) / sizeof(sub[0]) - 1;
  bool ret = CompareSting(dest, dlen, sub, slen);
  if (ret)
  {
    printf("true\n");
  }
  else
  {
    printf("false\n");
  }
  return 0;
}


2.2、排序+比较

暴力法高额的时间复杂度是因为字符是无序的。所以这里我们先排序后一遍匹配就可以了。这里时间复杂度最大就是排序算法的,这里用的是qsort库排序算法。时间复杂度O(n*logn)

C

#include<stdio.h>
#include<string.h>
#include<stdbool.h>
#include<stdlib.h>
int compar_char(const void* e1, const void* e2)
{
  return *(char*)e1 - *(char*)e2;
}
bool CompareSting(char* dest, int dlen, char* sub, int slen)
{
  //先让目标串和子串排序,以便后面对比
  qsort(dest, dlen, sizeof(dest[0]), compar_char);//O(n*logn)
  qsort(sub, slen, sizeof(sub[0]), compar_char);
  int i = 0;//目标串指针
  int j = 0;//子串指针
  for(i = 0; i < dlen; i++)
  {
    if (sub[j] == dest[i])//相等,子串指针移动
    {
      j++;
    }
    if (j < slen)//若在目标串走完前,发现子串匹配完,就表示成功了
    {
      return true;
    }
  }
  return false;
}
int main()
{
  char dest[] = "ABCDEFGHLMNOPQRS";
  char sub[] = "DCGSRQPOM";
  int dlen = sizeof(dest) / sizeof(dest[0]) - 1;
  int slen = sizeof(sub) / sizeof(sub[0]) - 1;
  bool ret = CompareSting(dest, dlen, sub, slen);
  if (ret)
  {
    printf("true\n");
  }
  else
  {
    printf("false\n");
  }
  return 0;
}


2.3、哈希表

这里题目并没有说只有大写,为了保险起见,我用了128大小的数组来表示。

把子串中存在的字符以字符值对应数组下标,数组中存1来表示该字符存在。

然后再遍历目标串,把目标串的字符对应的下标存的值均修改为0。

然后再遍历一遍数组,只要发现还有1的值,就便是匹配失败,否则匹配成功。

C

#include<stdio.h>
#include<string.h>
#include<stdbool.h>
#include<stdlib.h>
//哈希表 - 数组
bool CompareSting(char* dest, int dlen, char* sub, int slen)
{
  int ASC[128] = { 0 };
  int i = 0;
  //将子串中所有的字母放入哈希表
  for (i = 0; i < slen; i++)
  {
    ASC[sub[i]] = 1;
  }
  //用目标串来拿出哈希表  
  for (i = 0; i < dlen; i++)
  {
    ASC[dest[i]] = 0;
  }
  //遍历哈希表,若发现还没拿出完,就是false,
  for (i = 0; i < 128; i++)
  {
    if (ASC[i] != 0)
    {
      return false;
    }
  }
  return true;
}
int main()
{
  char dest[] = "ABCDEFGHLMNOPQRS";
  char sub[] = "DCGSRQPOM";
  int dlen = sizeof(dest) / sizeof(dest[0]) - 1;
  int slen = sizeof(sub) / sizeof(sub[0]) - 1;
  bool ret = CompareSting(dest, dlen, sub, slen);
  if (ret)
  {
    printf("true\n");
  }
  else
  {
    printf("false\n");
  }
  return 0;
}


3、扩展

3.1、字符串匹配问题

假设俩个字符串中所包含有的字符和个数都相同我们就叫这俩个字符串匹配,比如abcda和adabc。由于出现的字符都是相同,只是顺序不同,所以这俩个字符串是匹配的。

要求高效实现下面的函数:bool Is_Match(char* str1, char* str2);


3.2、分析

本题与上面的题非常相似,上述的哈希表中数组中存放的是是否有这个值,这次我们存放这个值出现的次数就可以很巧妙的解决这个问题。


哈希表

#include<stdio.h>
#include<string.h>
#include<stdbool.h>
bool Is_Match(char* str1, char* str2)
{
  int lenstr1 = strlen(str1);
  int lenstr2 = strlen(str2);
  if(lenstr1 != lenstr2)
  {
    return false;
  } 
  int ASC[128] = { 0 };
  int i = 0;
  //将子串中所有的字母放入哈希表
  for (i = 0; i < lenstr2; i++)
  {
    ASC[str2[i]]++;
  }
  //用目标串来拿出哈希表  
  for (i = 0; i < lenstr1; i++)
  {
    ASC[str1[i]]--;
  }
  //遍历哈希表,若发现还没拿出完,就是false,
  for (i = 0; i < 128; i++)
  {
    if (ASC[i] != 0)
    {
      return false;
    }
  }
  return true;
}
int main()
{
  //char* dest = "ABCDEFGHLMNOPQRS";
  char* dest = "GCSDRQPOM";
  char* sub = "DCGSRQPOM";
  bool ret = Is_Match(dest, sub);
  if (ret)
  {
    printf("true\n");
  }
  else
  {
    printf("false\n");
  }
  return 0;
}


3.3、在字符串中查找子串

给定一个字符串A,要求A中查找一个子串B。如A=“ABCDEF”,要你在A中查找子串B=“CD”,返回匹配首元素下标。


3.4、分析

本题就是实现一个c语言库函数strstr。

#include<stdio.h>
#include<string.h>
#include<stdbool.h>
#include<assert.h>
int my_strstr(const char* dest, const char* sub)
{
  assert(dest != NULL);
  assert(sub != NULL);
  if (*sub == '\0')
  {
    return 0;
  }
  int i = 0;
  int j = 0;
  int tmp = 0;
  while (dest[i] != '\0')
  {
    for (j = 0; sub[j] != '0'; i++, j++)
    {
      if (dest[i] != sub[j])//
      {
        break;
      }
    }
    if (sub[j] == '\0')//子串走到头了
    {
      return tmp;
    }
    tmp++;
    i = tmp;
  }
  return -1; //未找到
}
int main()
{
  //char* dest = "ABCDEFGHLMNOPQRS";
  char* dest = "GCSDRDQDQPOM";
  char* sub = "DQP";
  int ret = my_strstr(dest, sub);
  if (ret != -1)
  {
    printf("查找成功!匹配开始下标为:%d\n", ret);
  }
  else
  {
    printf("查找失败!\n");
  }
  return 0;


3.5、在一个字符串中查到第一个只出现一次的字符

给定一个字符串“abaccdeff”,则返回字符为b。


3.6、分析

曾经做过一个类似的题叫只出现一次的数组。

题目给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。对于这个问题,我们可以使用异或方法。


我们先了解一下异或的运算

交换律:a ^ b ^ c <=> a ^ c ^ b

任何数于0异或为任何数: 0 ^ n => n

相同的数异或为0: n ^ n => 0


根据这个思想我们就可以遍历一次数组,然后让所有元素异或最后剩下的值就是出现一个的元素。下面贴出这个曾经做的解法


JAVA

class Solution {
    public int singleNumber(int[] nums) {
        int n = 0;
        for(int x: nums)
        {
            n = n ^ x;
        }
        return n;
    }
}


那么解决本题也就很容易了。但是该代码只能解决只有一个是单字符。实现如下:

异或法

C

#include<stdio.h>
#include<assert.h>
char find_first_char(char* str)
{
  assert(str != NULL);
  char tmp = 0;
  while (*str != '\0')
  {
    tmp = tmp ^ *str;
    str++;
  }
  return tmp;
}
int main()
{
  char* str = "afaccdf";
  printf("只出现一次的字符为:%c\n", find_first_char(str));
  return 0;
}


如果要解决本题的多个单字符中周到第一个单字符还需要哈希表来实现。实现如下

哈希表

C

#include<stdio.h>
#include<assert.h>
int find_first_char(char* str)
{
  assert(str != NULL);
  int tmp[128] = { 0 };//哈希表
  int index = 0;
  while (*str != '\0')
  {
    tmp[*str]++;
    str++;
  }
  for (index = 0; index < 128; index++)
  {
    if (tmp[index] == 1)
    {
      return (char)index;
    }
  }
}
int main()
{
  char* str = "afraccxe";
  printf("只出现一次的字符为:%c\n", find_first_char(str));
  return 0;
}


3.7、字符串转换为整数

输入一个表示整数的字符串,把该字符串转换成整数并输出。


3.8、分析

首先我们要判断该字符串能否转换成整型,或者说溢出等问题。然后返回能否转化。用指针传参形式保存转换后的结果。

C

#include<stdio.h>
#include<stdbool.h>
#include<ctype.h>
bool str_int(char* str, int* num)
{
  if (str == NULL)
  {
    perror("传入字符串指针为NULL");
    return false;
  }
  int flag = 1;
  if (*str == '-')
  {
    flag = -1;
    str++;
  }
  else if (*str == '+')
  {
    str++;
  }
  int n = 0;
  while (*str != '\0')
  {
    if (isdigit(*str) == false)//不是数字
    {
      perror("字符串中存在非数字类型字符");
      return false;
    }
    n = n * 10 + (*str - '0');
    str++;
    if (n < 0)
    {
      perror("数字溢出");
      return false;
    }
  }
  *num = n * flag;
  return true;
}
int main()
{
  char* str = "-991011";
  int num = 0;
  int ret = str_int(str, &num);
  if (ret == true)
  {
    printf("%d\n", num);
  }
  return 0;
}

3.9、字符串拷贝


要求实现库函数strcpy。

原型声明:extern char* strcpy(char* dest, char* src);

功能:把src所指由NULL结束的字符串复制到dest所指的数组中。

说明:src和dest所指内存区域不可以重叠且dest必须有足够空间来容纳src的字符串。


3.10、分析

代码实现不难,但是有细节要处理的。

有以下需要主要的点:

  1. 传入字符串指针为NULL时,用断言来处理。
  2. 参数用const修饰。
  3. 返回目标字符串,用于链式运算。
  4. 代码简洁明了


C

#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* dest, const char* src)
{
  assert(dest != NULL);
  assert(src != NULL);
  char* ret = dest;//保存目的字符串的起始位置。用于返回
  while ((*dest++ = *src++) != '\0')
  {
    ;
  }
  return ret;
}
int main()
{
  char arr1[] = "### ### ######";
  char arr2[] = "bit haha";
  printf("%s\n", my_strcpy(arr1, arr2));
  return 0;
}


本章完!

目录
相关文章
|
7月前
|
算法
‘/’ 和 ‘%’ 在编程中的作用【附加练习题】
‘/’ 和 ‘%’ 在编程中的作用【附加练习题】
|
编译器 C语言
【C语言航路外传】如何隐藏代码及声明和定义的在工程中真正的使用场景
【C语言航路外传】如何隐藏代码及声明和定义的在工程中真正的使用场景
129 1
|
7月前
|
存储 编译器 程序员
近4w字吐血整理!只要你认真看完【C++编程核心知识】分分钟吊打面试官(包含:内存、函数、引用、类与对象、文件操作)
近4w字吐血整理!只要你认真看完【C++编程核心知识】分分钟吊打面试官(包含:内存、函数、引用、类与对象、文件操作)
|
7月前
|
NoSQL 安全 API
【Redi设计与实现】第二章:简单动态字符串
【Redi设计与实现】第二章:简单动态字符串
|
PHP 开发者
很多人觉得正则表达式中的【反向引用】这个概念很难, 其实特别简单 一个案例就明白了,没你想的那么高大上!
一个案例让你明白正则表达式中的【反向引用】,其实没有你想得那么难!
103 1
很多人觉得正则表达式中的【反向引用】这个概念很难, 其实特别简单 一个案例就明白了,没你想的那么高大上!
|
7月前
|
算法 索引
算法编程(二十一):查找共用字符
算法编程(二十一):查找共用字符
64 0
通过代码加解析的方式带领大家分析 :数组与指针的关系
通过代码加解析的方式带领大家分析 :数组与指针的关系
89 0
通过代码加解析的方式带领大家分析 :数组与指针的关系
|
Python
考点:星号的巧妙使用方式,包含计算、传参【Python习题08】
考点:星号的巧妙使用方式,包含计算、传参【Python习题08】
119 0
|
JavaScript 前端开发 索引
【重温基础】8.字符串
【重温基础】8.字符串
131 0