剑指offer-数组相关(空格替换、二维有序数组查询、两个栈模拟队列)

请实现一个函数,将一个字符串中的每个空格替换成“%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。可以自己尝试尝试。

所以对于算法: 

           考虑本算法的一般解法,又称暴力法; 

           之后考虑本题目固有属性;   

            之后利用此属性进行算法优化;   

             注意考虑边界条件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值