请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
补充C++/C知识
- 关于指针:
- 人们常说指针就是地址,其实并不懂到底什么意思。其实指针是一个变量,普通的变量,类似于int,float这种变量,只不过它的变量类型是int *,float *等等,这个变量本身就是一个内存地址,里面存储的是地址,这个地址指向另外的实际的数值。所以这里涉及到2个地址。如 char * s = "abced";那么&s就是变量本身的地址,s就代表里面的存储值,存储的地址,*s获取的就是指针的内容,这个例子中*s就是'a';
- 指针就是变量,存储的地址;数组是一段连续的内存空间,一旦初始化就是一个常量。所以可以后者赋值给前者,但是不可以前者赋值给后者。
- 记住:数组名就是这个数组内存的首地址。这是C的流程。不管是字符数组还是一般的数值数组,数组名字就是这个数组的地址/第一个元素的地址/数组元素首地址,所以可以把数组名字赋值给指针,指针变量里面存储的就是首地址。
- 而有一个特殊的东西是字符串常量,这个常量在我们看来是字符串,当然可以放在字符数组里面,但是一般就用“”来表示字符串常量。但是这个东西在电脑看来就是一个地址,是第一个字母的地址,这就是为什么可以将字符串常量赋值给指针。
- 综上:可以将一维数组;字符串常量赋值给一级指针;反之不成立。
- 指针变量就是地址,所以指针的加减大就是地址的变化,而变化之后取里面的内容是通过操作符*来取得,注意这个*和申明指针变量的那个*不是一个概念。所以常见的操作就是++/--。或者加一个固定大小的值表示这个指针能控制的范围,从而等价于数组的扩容,这点非常重要。
- 之后就是访问,访问不论数组还是指针,都可以使用下面进行访问,但是一般在不适用库函数的情况下,一般需要记录长度。如果使用指针访问,是用过* p来获取地址p里面的内容的,之后地址变化是通过p++/++p来变换的。那么这里的++是加的一个sizeof(p指针类型)来加的,具体可以看一下这个链接。关于二级指针先不考虑。
- 所以可以发现只要指针的地址范围能找到,那么就可以利用指针随时的变换到任意的地址范围。
- https://blog.csdn.net/daiyutage/article/details/8604720
对于这个题目,要明确一个问题:替换字符串,是在原来的字符串上做替换,还是新开辟一个字符串做替换!
- 如果新开辟一个数组list那么遇到空格替换为“%20”就行了,太简单了。代码如下:
-
def replaceSpace(self, s): # write code here t = '' for i in s: if i == ' ': t = t + '%20' else: t = t + i return t
-
不使用新的数组,规定只能本数组s上处理,那么可以直接使用函数 str.replace(),代码如下:
-
def replaceSpace(self, s): # write code here return s.replace(' ', '%20')
就可以了。那么这个题目考啥呢。
-
其实这个题目是考察在C里面的代码的,在C里面没有replace函数,且使用一个数组,所以这个题目就变了变成了只使用一个数组实现replace函数。C++代码写的好的一个:
-
void replaceSpace(char *str,int length) { if(str==NULL) return ; int CountOfBlanks=0; int Originallength=0; for(int i=0;str[i]!='\0';i++) { Originallength++; if(str[i]==' ') ++CountOfBlanks; } int len =Originallength+2*CountOfBlanks; if(len+1>length) return ; char*pStr1=str+Originallength;//复制结束符‘\0’ char*pStr2=str+len; while(pStr1<pStr2) { if(*pStr1==' ') { *pStr2--='0'; *pStr2--='2'; *pStr2--='%'; } else { *pStr2--=*pStr1; } --pStr1; } }
将上面的指针先使用好,这个代码就能弄个明白了。
-
所以如果使用一个数组,且不能使用库函数,那么代码该如何写?这才是奔头考察的重点。所以核心就是只用一个数组存储。那么思路就是先计算出扩展后需要的数组大小,为 原始大小 + 空格数 * 2,一个空格替换为三个字符,所以增量为 空格数的2倍。先扩容。之后从后往前移动,那么每个字符最多移动一次,如果从前扫描空格,遇到空格就往后移动,那么移动次数就太大了,所以在扩容后从后往前扫描。
-
同时,这个只有C/C++能实现,因为有指针,可以按照地址扩容,不用重新来一个数组,这就是将数组赋值给指针的好处;但是python的数组是显示的不能扩容,这就是为什么要实现底层只能用C/C++。时间复杂度O(2n)。
-
补充一些小点:将数组转化为str不能直接str(list),只能''.join(list),要注意这个,前者str(list)是将list里面的每个元素转化为str,达不到整合起一个str的目的,后者能达到。其次,使用range左闭右开,步长-1可以实现倒叙,这几点笑得技巧要注意。
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
- 解体思路:核心的思维就是利用二个栈实现一个队列,在STL里面我们是使用队列作为底层,将其一端封闭,封装为一个队列,所以将队列封装为栈是相对简单的,但是将二个栈封装为一个队列是相对比较繁琐的。所以本题核心的思路就是:要抓住队列和栈的本质区别和属性。
- 本质区别:队列是一头进,另外的一头出,即FIFO;而栈是先进后出,后进先出,这就是本质区别,在STL里面,队列的压入和弹出就是使用的push和pop操作,所以其实本质上就是考察的C++里面的STL源码解析。
- 所以队列的push操作的实现用stack_1不断的压入;要想模拟弹出最开始压入的,我们只能在碎裂采用pop时,先把stack_1里面的元如全部pop与statck_1相反的压入stack_2中,这样才能把最开始的暴露出来,才能通过栈的pop拿到数据,之后在将2里面的数据在pop倒叙到1里面,等待push。也就是说stack_2仅仅起到一个临时缓存、获取第一个数字的倒叙缓冲区。
-
public: void push(int node) { stack1.push(node); } int c; int pop() { while(!stack1.empty()){ c = stack1.top(); stack2.push(c); stack1.pop(); } int d = stack2.top(); stack2.pop(); while(!stack2.empty()){ c = stack2.top(); stack1.push(c); stack2.pop(); } return d; // 注意pop时候需要返回,不然无法测试; } private: stack<int> stack1; //知道stack的几个函数,top()有返回值,取top无返回值,empty()判断是否为空。 stack<int> stack2; }; // 注意这里的pop无返回值,而在python中的list的pop有返回值且可以删除元素。
这是C/C++代码:这种思路简单容易实现,但是里面有一个问题就是每次pop之后,都需要把数据都在放回到栈1里面,如果下次在pop则还需要再从1里面把数据拿到2里面,这样的操作显然是不合理的。
-
所以另外一种改进就是:当2里面不是空直接往出pop数据就行了,以后pop再从2中拿数据就行了,而不用放回去1里面再从1里面拿出来。只有当2为空,那么此时就从1里面把当前1里面的数据全部拿到2里面,等到2pop操作(注意此时也pop一下,别只把数据拿过来而不进行pop操作)。上面的操作都不满足,说明1也是空的,pop只能返回NULL/None,等待push即可。针对此更改的C++代码如下:
-
同时记住一句话:栈、队列不是可迭代的,所以获取它们中间的元素只能不断的push,pop才可以获取当前元素,这就决定了迭代获取栈元素队列元素的方法必定是pop操作。
-
改进的C++代码:
class Solution { public: void push(int node) { stack1.push(node); } int c; int pop() { if(stack2.empty()){ if(stack1.empty()){ return NULL; } while(!stack1.empty()){ c = stack1.top(); stack2.push(c); stack1.pop(); } } int d = stack2.top(); stack2.pop(); return d; } private: stack<int> stack1; stack<int> stack2; };
-
python代码:
# -*- coding:utf-8 -*- class Solution: #注意这里的栈可以不给你,因为人家测试的时候就是使用下面给定的push和pop操作进行测试 #只要和人家的测试序列等同,就认为是对的。所以题目中涉及到的两个栈可以自己定义,在python #里面我们的栈使用 list实现的,所以自定义。 def __init__(self): self.inputStack = [] self.outputStack = [] def push(self, node): # write code here self.inputStack.append(node) def pop(self): # return xx 注意这里人家给你写了要你返回值,切记这点,读清楚题目 if len(self.outputStack) == 0: if len(self.inputStack) == 0: return None while len(self.inputStack) != 0: self.outputStack.append(self.inputStack.pop()) return self.outputStack.pop()
所以核心就是掌握STL里面如何 底层用队列实现栈,以及如何用双栈实现队列,因为用双栈实现队列,所以我们可以看到队列的空间的操作,而不会出现队列一旦pop前面一直为空的那种我们想象的空间空余。
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
- 首先说明这个题目一般的面试必考:使用python的第一种方法如下:
-
class Solution: # array 二维列表 def Find(self, target, array): for row in array: if target in row: return True
二维数组直接查找,时间复杂度是二次循环,in的本质就是循环,所以时间复杂度是O(mn),本质上是没有利用它的有序性。
-
那么如何利用有序性呢?采用往左下角逼的方法。
-
target与每一行最后一个数比较,如果比这个数大,这一行就没必要在比了,直接i + 1;如果比这一行最末尾的数小,那么这一列就没必要再比了,直接 j - 1,这就从最大的 7形状变成了一下 小的 7形状,如此不断处理,直到结束。时间复杂度就是 O(m+n)。
-
那么这样操作的话就有一个问题,就是他不是一个方阵,而是一个长方形,这样的话,必定扫描到最后只剩最后一行或者最左一列,此时是否需要单独判断。当然可以单独判断,答案是不用大怒判断,自己考虑下面代码当while退出的时候为什么可以直接return false,思考原因是什么。(画画图就明白了)。
-
def Find(target, array): if len(array) == 1: return target in array[0] i = 0 j = len(array[0]) - 1 while i < len(array[0]) and j >= 0: d = array[i][j] if target == array[i][j]: return True if target > array[i][j]: i += 1 elif target < array[i][j]: j -= 1 return False
- 这就是上面的解法,可以看到查找是 m+n, 但是如果再横向和纵向采用二分查找,那么时间复杂度就是 log m + log n = log mn。可以自己尝试尝试。
所以对于算法:
考虑本算法的一般解法,又称暴力法;
之后考虑本题目固有属性;
之后利用此属性进行算法优化;
注意考虑边界条件。