详解剑指offer中的四道题(杨氏矩阵 替换空格 数组中只出现一次的两个数字 实现atoi)

文章目录

  • 前言
  • 1.杨氏矩阵
    • 1.题目要求
    • 2.题目分析
    • 3.代码示例
  • 2.替换空格
    • 1.题目要求
    • 2.题目分析
    • 3.代码示例
  • 3.数组中只出现一次的两个数字
    • 1.题目要求
    • 2.题目分析
    • 3.代码示例
  • 4.实现atoi
    • 1.题目要求
    • 2.题目分析
    • 3.代码示例
  • 5.总结


前言

编程是需要不断实践练习的,同时对于做过的题也要及时的复盘总结。本文简单对剑指offer中的四道题进行简单的讲解。


1.杨氏矩阵

直接点击题目链接

1.题目要求

有一个数字矩阵,矩阵的每行从左到右是递增的,矩阵从上到下是递增的,请编写程序在这样的矩阵中查找某个数字是否存在。要求:时间复杂度小于O(N);

2.题目分析

这道题解题的关键点在于这个矩阵的特性是一个自增的矩阵。也就是说这个矩阵每一行最后一个数一定是这一行最大的数。只需要用要查找的数字和每一行最后一个数字比较即可。从第一行开始如果这个数字比第一行末尾的数字还要大,就接着比较下一行末尾的数字。如果某行的末尾数字比这个要查找是数字大,那么说明这个数字的大小范围可能在这一行。就从这一行开始从后往前挨个比较,直到找到这个要查找的数,如果没找到就说明这个矩阵中不存在这个数字。

3.代码示例

bool Find(int target, int** array, int arrayRowLen, 
int* arrayColLen ) {
    int row=0;
    int col=*arrayColLen-1;
    while(row<arrayRowLen&&col>=0)
    {
        if(array[row][col]<target)
        {
            row++;
        }
        else if(array[row][col]>target)
        {
            col--;
        }
        else
        {
            return true;
        }
    }

 return false;
    // write code here
}

这道题利用了矩阵数字是递增排布的特性,将整个矩阵拆解成一个一维数组,直接一行比较,每次比较过后都解决了一行数据,在一行中排查目标数字。因为这个矩阵是递增的,按行或者按列比较都是可行的,本质都是同一种思路。

2.替换空格

直接点击题目链接

1.题目要求

请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

2.题目分析

这道题将字符串中的每个空格都替换成%20实际上是将空格替换成3个字符。%20在字符串中的本质就是3个字符。我们先将空格数记录下来,然后通过空格数就知道了新字符串的长度。因为这道题是函数返回值类型是void那就说明这个是原字符串进行修改。也侧面反映出原字符数组的空间长度是足够的。我们从后往前遍历字符数组,某位置的字符不是空格,就把字符赋值给新的位置,然后指向新位置的数组下标在往后移动。如果是空格,就从这新位置开始依次赋值 0 2 %,因为这是倒着放的。

3.代码示例

class Solution {
public:
	void replaceSpace(char *str,int length) {
        int count=0;
        for(int i=0;i<length;i++)
        {
            if(str[i]==' ')
            {
                count++;
            }
        }
       int index=2*count+length;
       for(int i=length;i>=0;i--)
       {
         if(str[i]!=' ')
         {
            str[index]=str[i];
            index--;
        }
        else
        {
            str[index]='0';
            str[index-1]='2';
            str[index-2]='%';
            index=index-3;
        }
    }
        
	}
};

这道题的为啥不从前往后遍历赋值呢?因为替换空格势必会造成原字符数组中字符的移动,当从前往后移遍历赋值时,如果遇到第一个空格,从空格处往后一个的位置开始,整个字符串整体往后挪动两个单位,然后开始赋值 % 2 0,遇到下一个字符重复上述操作。这样的移动太过于麻烦,同时如果位置移动不准确,就会造成整个字符数组字符摆放错乱。如果是从后往前就不会造成这样的问题了,但是要记得移更新新数组下标。而且是倒着放的,所以%20也要倒着赋值。还有一个关键问题,字符串长度是不包含\0的,数组下标又是从0开的,所以在赋值遍历的时,为了将\0也赋值给新字符串,i我设置的起始大小是数组长度length,index的大小也是length+count*2,如果不移动\0,i设置成length-1,index设置成2*count+length-1即可,也是可以通过测试用例的。

3.数组中只出现一次的两个数字

直接点击题目链接

1.题目要求

一个整型数组里除了两个数字只出现一次,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

2.题目分析

这道题有两种做法。先简单介绍第一种,在之前的博客中介绍过元素去重的问题,这道题本质还是和元素去重一样的。这道题给出的数据都是正整数,我们还是可以将原数组中的数据转化成另一个数组的下标,通过遍历这个新数组中的元素来判断该数组下标是不是要查找的数据。因为数组下标是从小到大开始遍历,所以在找到目标数据后不用排序,直接输出即可。但是要额外开空间。

第二种方法就是利用位运算。在之前的博客中也介绍过一道题,就是在一组整型数据中只有一个数出现的次数是奇数次,其他的数都是出现了偶数次,请找到这个出现奇数次的数字。当时介绍一种简单方法利用异或的性质,来查找这个数字。但是这道题是两个数字,怎么异或呢?如果我们能将这两个数字分开来异或就解决了这个问题。那么怎么分呢?以1 1 2 2 3 4 为例。如果把使有的数字都异或在一起,结果就是3^4,这个结果的二进制序列某一位一定是1。因为这个一起异或的结果一定不会为0。找到这个1在32位二进制序列的位置,以这个位置划分两组数据。首先异或的规则是相同为0,不同为1。由此可知,所求的两个数字的二进制序列在该位置上的位是不同的。根据这个特点就可以划分成两组数据来分别异或。

3.代码示例

第一种方法

int* FindNumsAppearOnce(int* array, int arrayLen,
 int* returnSize ) 
 {
    *returnSize=2;
      int* nums=(int*)malloc(4*2);
     int arr[10000001]={0};
     for(int i=0;i<arrayLen;i++)
     {
         arr[array[i]]++;
     }
     int k=0;
     for(int j=0;j<10000001;j++)
     {
         if(arr[j]==1)
         { 
             nums[k]=j;
             k++;
         }
     }
    return nums;// write code here
}

在使用这种用方法时需要注意一点就是新数组的下标最大值至少要等于目标数组中元素数据的最大值。不然就会把一些目标数组中的数据元素给遗漏。

第二种方法

int* FindNumsAppearOnce(int* array, int arrayLen,
 int* returnSize ) 
 {
    *returnSize=2;
    int ret=0;
      for(int i=0;i<arrayLen;i++)
      {
          ret^=array[i];
      }
      int j=0;
      for( j=0;j<32;j++)
      {
          if((ret>>j)&1)
          {
             break;
          }
      }
      int num1=0;
      int num2=0;
      int* nums=(int*)malloc(4*2);
     for(int k=0;k<arrayLen;k++)
     {
       if((array[k]>>j)&1)
       {
           num1^=array[k];
       }
       else
       {
           num2^=array[k];
       }
     }
     if(num1<num2)
     {
       nums[0]=num1;
       nums[1]=num2;
     }
     else
     {
         nums[0]=num2;
         nums[1]=num1;
     }
    return nums;// write code here
}

第二种方法需要注意的是,在分别异或之后,根据题目要求需要简单的排个序。j是创建在循环之外的,因为需要记录这个位置。也可以创建个临时变量来保存记录位置。

4.实现atoi

直接点击题目链接

1.题目要求

写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。在任何情况下,若函数不能进行有效的转换时,请返回 0。说明:假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为[−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。

这个简单来说就是把数字字符串转成对应的整型,比如'' -123''转成-123.

2.题目分析

虽然总得来说这个题目要求很单一就是把数字字符串转成对应整型,但是考虑很多细节。首先先考虑非法的情况,当传入的字符串是空指针或者是空字符串时需要返回0,但是这就有一个问题了。当我传入的字符串是’‘00’‘,这个时候返回值也是0,而且这是属于合法的转化。那么怎么区分这个0是合法转化得来的还是非法转化得来的,除了这样的情况外还有类似于’’ -123abc’‘转成-123,或者传入的字符串转化出的数据超出了整型的范围,这些也是非法的转化结果。为了区分合法和非法的结果,就先定义一个枚举类型的全局变量。枚举的取值有合法和非法两者可能。因为非法的情况是肯定远多于合法的,所以先将这个枚举类型的变量先置为非法。然后挨个判断,如果传入字符串是空指针或者是空字符串,就返回0,然后定义一个longlong类型变量用于保存转化结果。因为用整型保存结果如果数据溢出就会自动截断数据,整型数据永远不会超出整型范围。这就无法判断转化结果是否溢出。同时,还要定义一个flag原来处理转化结果的符号问题,如果传入的字符串是‘’-123‘’,就需要将结果处理为负数。接着就是判断空字符,处理完空字符后,然后就是判断字符串是不是‘’+‘’或者是‘’-‘’,flag的初始值要置为-1,如果空字符串之后的第一个字符是‘-’,就需要将flag置为-1。然后就是判断字符串是否为数字字符串了。如果数字字符就进行转化,在转化过程中判断保存的结果是否溢出,如果溢出了就直接返回。在判断到’\0’时还没返回,说明已经是转化完了,且是一次合法的转化。这时候就直接将枚举变量置为合法即可。同时返回保存的结果。在返回结果时需要将强制类型转化,因为函数返回值是整型但是保存的结果是long long类型。

3.代码示例

enum Status
{
	VALID,
	INVALID
}status = INVALID; //默认非法

int strToInt(char* str){
    int flag = 1;
	if (*str == '\0'||str==NULL)
	{
		return 0;
	}//非法0
	while (isspace(*str))//跳过空白字符
	{
		str++;
	}
	//判断符号
	if (*str == '+')
	{
		flag = 1;
		str++;
	}
	else if(*str == '-')
	{
		flag = -1;
		str++;
	}

	long long ret = 0;//保存结果
		while (isdigit(*str))
		{
			ret = ret * 10 + flag*(*str - '0');
			if (ret > INT_MAX)//溢出
			{
				return INT_MAX;
			}
            else if(ret<INT_MIN)//溢出
            {
                return INT_MIN;
            }
            str++;
		}
	if (*str == '\0')
	{
		status = VALID;
	}
	return (int)ret;
}

这道题看起来简单但是实际上要处理的东西很多,需要尽可能周全的考虑。isspace是用来判断空字符串的,isdigit是用来判断数字字符的。也可以不用这些库函数直接判断也行。当数字字符减去字符0这个结果就是整型数字了。当跳出字符判断时,需要判断是因为'\0'跳出循环还是因为遇到了其他的字符。同时这个flag初始化不能置为0,如果传入字符串是123,这样就会造成结果错误。所以flag要初始化为1。对每次的转化都要进行溢出判断。这个枚举变量起始不设置不对转化的值进行合法性的判断也能通过所有的测试用例,但是这样不够严谨,我还是建议对转化结果进行合法性的判断。还需要注意要将保存的结果进行强制类型转化。因为函数的返回值的整型。
还有符号判断时要写成if 和else if语句,因为如果字符串是-+123,返回值肯定是0。如果都写成if if,那么就会同时判断返回的结果是 -123,符号只用判断一次即可。else if语句就可以满足这个要求。

5.总结

对剑指offer中的四道题进行简单总结复盘。编程是需要不断实践练习的,熟能生巧。做完题后还要及时的总结加深印象。以上内容如有错误欢迎指正!

你可能感兴趣的:(c语言,学习)