Coding_Algorithms(剑指Offer系列)

写在前面:

2016.12.07开始刷题模式,掌握基础算法的同时学习巩固C++和Python编程基础,每道算法题我会尽量用两种语言去实现。目前是从剑指offer(牛客网)开始,欢迎同道中人共同学习,批评指正。本地测试代码和笔记会更新在我的github上Coding_Algorithms

暂告一段落,忙其它的事,学习不止。

ppi and ipp (自己琢磨的问题,还没搞明白,置顶)

i++和++i的问题:

i++和++i的问题,解释下下面几段代码的输出

part1:i++
 int main(void)
    {
    int i = 0;
    cout << "Test execute order: " << endl
        << "cout: "
        << i << ", " << i++ << ", " << i++ + i++ << endl;

    i = 0;
    printf("printf(): %d, %d,%d\n", i,i++, i++ + i++);//3,2,0

    //output:
    /*Test execute order:
    cout: 3, 2, 0
    printf(): 3, 2,0 */
    getchar();
    }
part2:++i
    int main(void)
    {
    int i = 0;
    cout << "Test execute order: " << endl
        << "cout: "
        << i << ", " << ++i <<", " << ++i + ++i << endl;

    i = 0;
    printf("printf(): %d, %d,%d\n", i, ++i, ++i + ++i);//3,3,6

    //output:
    /*Test execute order:
    cout: 3, 3, 6
    printf(): 3, 3,6*/
    getchar();
    }
part3:纯printf() 难难难
    int main(void)
    {
        int i = 0;
        printf("%d %d %d %d %d\n", i, i++, ++i, ++i + ++i, ++i + ++i + i++);//7 6 7 14 6

        getchar();
    }
笔记:

新的解释:

对变量i的寄存器本身操作的时候(++i)会优先执行,然后拷贝本身寄存器值的时候会把上一个表达式的结果求出来。vs和g++区别就是vs不会用其他寄存器保存,而g++会保存。vs的第四项为14,就是++i + ++i执行,第三项++i,第二项i++,碰到第二项才本身寄存器拷贝,把结果求出来。g++执行第四项为什么会有中间寄存器保存?而g++第三项却没有,只要碰到除++i之外的运算符 都会保存到中间寄存器,这么解释?而VS只有碰到i++才会保存到中间寄存器?

下图是VS编译环境,第五项是三个++i,结果显然是i经过所有自增后的和。

这个问题还没怎么搞明白,特别是part3,在VS编译环境下输出是7 6 7 14 6,而在g++下编译结果是 7 6 7 10 6。下午跟同学看了汇编代码还是没怎么整明白,路过大神求指导!下图是g++反编译出来的。

来自好基友jack.j的手解汇编:POFEI_IS_SHIT的博客

之前只知道i++是先运算在赋值,++i是先赋值再运算,昨天学妹问了个问题把我给难住了(得好好补基础啊!)…如上面代码中所示,当i++与++i在同一表达式里进行运算的时候,各种情况各种输出结果。

下面几点是根据网友别总结的,没怎么看懂啊!

编译器决定对函数实参的表达式求解顺序,从右到左(看printf()的输出)。

只要出现i++,就会先对i作相关运算,然后再增1.

因为C语言函数参数入栈顺序是从右到左的,所以计算顺序是从右到左开始计算的;对于a++的结果,是有ebp寻址函数栈空间来记录中间结果的,在最后给printf压栈的时候,再从栈中把中间结果取出来;而对于++a的结果,则直接压寄存器变量,寄存器经过了所有的自增操作。

Merge2016.12.27

题目十六:

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

解答(C++):递归
    class Solution {
    public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        ListNode* pResult = NULL;//合并后链表的头指针
        if (pHead1 == NULL)
            return pHead2;//list1为空,直接返回list2
        if (pHead2 == NULL)
            return pHead1;//list2为空,直接返回list1

        if (pHead1->val <= pHead2->val) {
            pResult = pHead1;
            pResult->next = Merge(pHead1->next, pHead2);//递归,两个链表中值小的头结点作为合并结点的下一结点
        }
        else {
            pResult = pHead2;
            pResult->next = Merge(pHead1, pHead2->next);
        }
        return pResult;
    }
    };
解答(C++):非递归
    class Solution {
    public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        ListNode* pResult = NULL;
        ListNode* current = NULL;

        if (pHead1 == NULL)
            return pHead2;
        if (pHead2 == NULL)
            return pHead1;
        while (pHead1 != NULL && pHead2 != NULL) {
            if (pHead1->val <= pHead2->val) {
                if (pResult == NULL) {
                    current = pResult = pHead1;
                }
                else {
                    current->next = pHead1;
                    current = current->next;
                }
                pHead1 = pHead1->next;
            }
            else {
                if (pResult == NULL) {
                    current = pResult = pHead2;
                }
                else {
                    current->next = pHead2;
                    current = current->next;
                }
                pHead2 = pHead2->next;
            }
        }

        if (pHead1 == NULL) {
            current->next = pHead2;
        }
        if (pHead2 == NULL) {
            current->next = pHead1;
        }

        return pResult;
    }
    };
笔记:

主要思路就是选取合并链表的下一节点,同步遍历两个链表,选择值比较小(相等取其一即可)的链表的当前结点作为合并链表的下一节点。可以通过递归和非递归实现,详见代码。

AC

ReverseList 2016.12.26

题目十五:

输入一个链表,反转链表后,输出链表的所有元素。

解答(C++)
    class Solution {
    public:
    ListNode* ReverseList(ListNode* pHead) 
    {
        ListNode* pReversedHead = nullptr;//记录反转链表的头结点
        ListNode* pNode = pHead;//记录当前遍历结点
        ListNode* pPrev = nullptr;//记录上一结点
        while (pNode != nullptr)
        {
            ListNode* pNext = pNode->next;

            if (pNext == nullptr)
                pReversedHead = pNode;

            pNode->next = pPrev;

            pPrev = pNode;
            pNode = pNext;
        }
        return pReversedHead;
    }
    };
笔记:

设置三个指针pReversedHead、pNode、pPrev分别指向反转链表的头结点
当前遍历结点、上一结点,只需遍历一次即可完成链表反转,在纸上画一下会比较清晰,详见代码。

AC

FindKthToTail2016.12.25

题目十四:

输入一个链表,输出该链表中倒数第k个结点。

解答(C++)
 /*
    struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
            val(x), next(NULL) {
    }
    };*/
    class Solution {
    public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        if (pListHead == nullptr || k == 0)
            return nullptr;

        ListNode *pAhead = pListHead;//设置两个指针
        ListNode *pBehind = nullptr;

        for (unsigned int i = 0; i < k - 1; ++i)
        {
            if (pAhead->next != nullptr)
                pAhead = pAhead->next;
            else
            {
                return nullptr;
            }
        }
        pBehind = pListHead;
        while (pAhead->next != nullptr)
        {
            pAhead = pAhead->next;
            pBehind = pBehind->next;
        }
        return pBehind;   
    }
    };
笔记:

要得到链表的倒数第K个结点,如果直接遍历一次得到链表长度n,然后再遍历到(n-k+1)可得到,但是显然这样的时间复杂度高,并不熟最优解。设置两个指针同时遍历,第一个指针先开始遍历,另一个指针不懂,等到第一个指针遍历到第k(此时走了k-1步)个结点的时候另一个指针再开始遍历(与第一个指针同步),这样等到第一个指针遍历到链表末尾时,第二个结点刚好到倒数第k个结点。

本地用例有构造链表及相关操作,见本地案例

AC

rectCover 2016.12.23

题目十三:

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

解答(C++)
    class Solution {
    public:
    void reOrderArray(vector<int> &array) {
        vector<int> res;//新建临时数组,空间换时间
        for(int i = 0; i < array.size(); i++)
        {
            if(array[i] & 1)//遍历,为奇数存入临时数组
                res.push_back(array[i]);
        }
        for(int i = 0; i < array.size(); i++)
        {
            if((array[i] & 1) == 0)//遍历,为偶数存入临时数组            
                res.push_back(array[i]);
        }
        array = res;       
    }
    };
解答(Python)
 # -*- coding:utf-8 -*-
    class Solution:
    def reOrderArray(self, array):
        # write code here
        odd = []
        even = []
        for i in array:
            if i % 2 == 0:
                even.append(i)
            else:
                odd.append(i)
        return odd+even
笔记:

这题关键在于时间复杂度,如果从头到尾遍历碰到奇数然后移动,这样的时间复杂度是O(n^2).所以考虑用临时空间,可以将时间复杂度降到O(2n),详见代码。

AC

Power 2016.12.21

题目十二:

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

解答(C++)常规解法
class Solution {
    public:
    double Power(double base, int exponent) {
        if(exponent==0)//任何数的0次方都是1
            return 1.0;
        if(base-0.0<0.0000001 && base-0.0>-0.0000001)//如果base为0.0...返回0.0
            return 0.0;        
        int exp;
        if(exponent<0){//如果乘方数为负数,取负,结果取倒数
            exp = -exponent;
        }
        else
            exp = exponent;     

        double result =1.0;

        while(exp>0){//循环累乘
            result = result*base;
            exp--;
        }
        if(exponent<0){//循环累除
            result=1.0/result;
        }       
        return result;            
    }
    };
解答(C++)高效解法,O(logn)
class Solution {
    public:
    double Power(double base, int exponent) {
        if(exponent==0)
            return 1;
        if(exponent==1)
            return base;
        int exp,flag=0;
        if(exponent<0){//如果乘方数为负数,取负,结果取倒数
            exp = -exponent;
            flag=1;
        }
        else
            exp = exponent;

        double result=Power(base,exp>>1);//右移,相当于除以2
        result *=result;
        if(exponent & 0x1 ==1)//取余判断奇数or偶数
            result *= base;

        if(flag==1)
            return 1/result;
        return result;         
    }
    };
笔记:

求数值的正数次方,思路不难,关键要考虑到几个特殊情况,0的0次方无意义,任何数的0次方都为1,指数为负数时要记得取倒数等。(详见代码及注释)

第二种高效解法并没想到…边看边理解。用右移运算代替除以2,用位与运算代替求余来判断奇偶。
假如输入的exponent为32,我们的目标是求出一个数的32次方,那么只要求出整数的16次方然后再取平方就行了,而16次方就是8次方的基础上取平方,依此类推…32次方只需要做5次乘法,显然提高了效率。
用公式表示:a(n)表示a的n次方
①当n为偶数时:a(n)=a(n/2)*a(n/2)
②当n为奇数时:a(n)=a((n-1)/2)*a((n-1)/2)*a
然后类似于斐波那契数列,递归解决。

小插曲,本地测试的时候犯了个小错误,如下:

我把base定义成int型了,结果输出的时候,显示base是0.000000,但是结果却是正确的。无意中发现这个问题,于是跟好基友Zack.J.Dsg刷题小王子请教了一下。
解释如下:
我输入的base=2,64位double分为3段(下段解释),2的二进制0000…..00010,00000……..010的解释成double型就是一个很小的小数值可能是0.00000000000000000000000××,后面显示不出来了,但是参与运算是又把int型提升到double型了。

63是符号位,62-52是指数部分…这个就需要看IEEE754标准了。
浮点标准用V=(-1)^s * M * 2 ^ E. M是小数部分的结果,E是指数部分的结果。根据指数部分(k代替)分为3种情况,k全为0,那个对应的十进制结果(e表示)就是0,E = 1 - Bias。Bias = 2 ^ (k - 1) - 1 double型就是1023
所以E=-1022,2^E=****(自己算好了)
小数部分(f表示)00000…..10。f=0.0000000….10(二进制小数),然后算出十进制就是M,就可以得到V的结果了
k全为1可以提供很大的数,k不全为0和不全为1是另一种情况,就不详细讲了。

AC

rectCover 2016.12.20

题目十一:

输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。比如9的二进制位1001,有2位是1.因此输出2。

解答(C++)常规解法:32位整数需要循环32次
    class Solution {
    public:
     int  NumberOf1(int n) {
         int count=0;
         unsigned int flag=1;
         while(flag)
         {
            if(n & flag)//判断n的最低位是不是1->次低位是不是1->....
                count++;
            flag=flag<<1;//左移一位,1->2->4...
         }
         return count;        
     }
    };
解答(C++)牛X解法: 整数中有几个1就循环几次
    # -*- coding:utf-8 -*-
    class Solution:
    public:
     int  NumberOf1(int n) {
         int count=0;
         while(n)
         {
            ++count;
            n=(n-1)&n;//n与n-1位与运算,相当于把n最右边的1变成0,之后的全为0.如:1100&1011->1000 
         }
         return count;        
     }
    };
笔记:

位运算不太熟悉,看了会书才做的。解题思路也是参考书上的,第二种方法有点难。书上还有一种罪初级的解法(n&1相当于n%2==1,但是位运算效率高多了),以此来判断末位是否为1;迭代,每次对n右移运算。这种方法碰到负数会陷入死循环。

第一种常规解法,依此判断n的最低位是否为1,次低位是否为1…

第二种解法有点开脑洞了,需要想到一个trick,一个整数(n)与这个整数减去一(n-1)做与运算相当于把整数最右边的1变成0.因此,有多少个1就执行多少次这个操作(见代码)。

AC

rectCover 2016.12.19

题目十:

我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

解答(C++)迭代循环解:
  class Solution {
    public:
    int rectCover(int number) {
        if(number<1)
            return 0;
        int f = 1, g = 2;
        while(number-->1) {
        //循环迭代
        //F[n]=F[n-1]+F[n-2](n>=2,F[1]=1,F[2]=2)
            g += f;
            f = g - f;
        }
        return f;   
    }
    };
解答(Python):
# -*- coding:utf-8 -*-
    class Solution:
    def rectCover(self, number):
        # write code here
        if number<1:
            return 0
        f = 1
        g = 1
        for i in range(number):
            f,g = g,f+g
        return f
笔记:

思路在纸上画的,直接上图了,总之还是个斐波那契数列,归纳一下就好。

AC

jumpFloor_plus 2016.12.16

题目九:

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

解答(C++)牛X解…:
class Solution {
    public:
    int jumpFloorII(int number) {
        return  1<<--number;
    }
    };
解答(C++)递归解:
class Solution {
    public:
    int jumpFloorII(int number) {
        if(number == 0)
            return 0;
        else if(number == 1)
            return 1;
        else
            return 2*jumpFloorII(number-1);

    }
    };
解答(C++)迭代循环解:
class Solution {
    public:
    int jumpFloorII(int number) {
        if(number <= 0)     return 0;
        if(number == 1)     return 1;
        int result = 1;
        // 2^(n-1)
        while(number-->1)
        {
           result *= 2; 
        }
        return result;
    }
    };
解答(Python):
    # -*- coding:utf-8 -*-
    class Solution:
    def jumpFloorII(self, number):
        # write code here
        return 2 ** (number - 1)
笔记:

从上一题的思路出发:对于第n个台阶来说,只能从n-1或者n-2的台阶跳上来(只有两种跳法),F(n) = F(n-1) + F(n-2);对于本题来说,对于第n个台阶有可能是从第n-1,n-2,n-3…1,0跳上来,因此可以归纳出:

f(0)=0
f(1)=1
f(2)=f(2-1)+f(2-2)
f(3)=f(3-1)+f(3-2)+f(3-3)
.
.
.
f(n-1)=f(n-2)+f(n-3)+…f(n-n) ①
f(n)=f(n-1)+f(n-2)+f(n-3)+…f(n-n) ②
②-①=f(n-1)
可以得出f(n)=2f(n-1)
这样问题就简单了。

AC

jumpFloor 2016.12.16

题目八:

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

解答(C++):
class Solution {
public:
int jumpFloor(int number) {
    int f = 1, g = 2;
    while(number-->1) {
    //循环迭代
    //F[n]=F[n-1]+F[n-2](n>=2,F[1]=1,F[2]=2)
        g += f;
        f = g - f;
    }
    return f;      
}
};
解答(Python):
# -*- coding:utf-8 -*-
class Solution:
def jumpFloor(self, number):
    # write code here
    f = 1
    g = 1
    for i in range(number):
        f,g = g,f+g
    return f
笔记:

对于第n个台阶来说,只能从n-1或者n-2的台阶跳上来,所以F(n) = F(n-1) + F(n-2)斐波拉契数序列

初始条件
n=1:只能一种方法
n=2:两种
斐波拉契数序列第七题已经写过了,这道题难点在于能不能看出这是一个斐波拉契数序列(只是初始条件改变了),嘿嘿,都是套路。后面还有一题变态跳台阶,关键还是在于问题思路转换。

AC

Fibonacci 2016.12.14

题目七:

大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。 n<=39

解答(C++):
class Solution {
public:
    int Fibonacci(int n) {
        int f = 0, g = 1;
        while(n--) {
        //循环迭代
        //F[n]=F[n-1]+F[n-2](n>=2,F[0]=0,F[1]=1)
            g += f;
            f = g - f;
        }
        return f;
    }
};
解答(Python):
# -*- coding:utf-8 -*-
class Solution:
    def Fibonacci(self, n):
        # write code here
        f = 0
        g = 1
        for i in range(n):
            f,g = g,f+g
        return f
笔记:

斐波那契数列是个非常经典的问题了(F[n]=F[n-1]+F[n-2](n>=2,F[0]=0,F[1]=1))。主要就是迭代(Iteration)VS 递归(Recursion),一般来说递归的代码比较简洁,但是因为递归是调用函数本身,函数调用是有时间和空间消耗的。迭代方法的时间复杂度为O(n),根据f(0)、f(1)可以计算出f(2),f(1)、f(2)可以计算出f(3)…以此类推循环遍历n次就可以计算出f(n)了。

首先,每一次函数调用都需要在内存栈中分配空间保存参数,返回地址及临时变量,在入栈、出栈的过程中也会有时间消耗,因而实现的效率不如迭代循环。

其次,递归的思想是把一个大问题分成一个个小问题,这样造成很多 计算重复,因为这些小问题很可能发生重复现象。比如f(7)=f(6)+f(5);f(6)=f(5)+f(4);这里就发生重复计算了,因而影响效率。

此外,递归层数过多的话还会出现栈溢出,这是程序员最不想看到的吧。

AC

这里写图片描述

minNumberInRotateArray 2016.12.12

题目六:

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

解答(C++):
    class Solution {
    public:
    int MinInOrder(vector<int> num,int index1,int index2)
    {
        int res = num[index1];
        for (int i = index1 + 1; i < index2;++i)//循环遍历,找到最小值
        {
            if (res > num[i])
                res = num[i]; break;
        }
        return res;
    }
    int minNumberInRotateArray(vector<int> rotateArray) {
        int len = rotateArray.size();
        if (len <= 0)
            return 0;
        int index1 = 0;
        int index2 = len - 1;
        int indexMid = index1;//假如第一个值即为最小值,不会进入下面循环了,直接返回
        while(rotateArray[index1]>=rotateArray[index2])
        {
            if (index2 - index1 == 1)
            {
                //如果两个指针的距离是1,说明第一个指针已经指向第一个递增序列的末尾,
                //而第二个指针已经指向第二个递增子数组的开头。
                indexMid = index2;//第二个递增数组的第一个数字即为最小值
                break;
            }
            indexMid = (index1 + index2) / 2;
            //如果下标为index1、index2和indexMid指向的三个数字都相等
            //那么只能进行顺序查找
            if (rotateArray[index1] == rotateArray[index2]&& rotateArray[index1]== rotateArray[indexMid])
            {
                return MinInOrder(rotateArray,index1,index2);
            }
            if (rotateArray[index1] <= rotateArray[indexMid])
                index1 = indexMid;
            else if (rotateArray[index2] >= rotateArray[indexMid])
                index2 = indexMid;
        }
        return rotateArray[indexMid];
    }
    };
解答(Python):
    class Solution:
    def minNumberInRotateArray(self, rotateArray):
        # write code here
        if len(rotateArray) == 0:
            return 0

        ret = rotateArray[0]
        if len(rotateArray) == 1:
            return ret
        for i in range(1,len(rotateArray)):
            now = rotateArray[i]
            if now < ret:
                ret = now
                break
        return ret
笔记:

这道题要答得很好还是有点难得,最简单的思路就是遍历找最小值了,找到了就返回,见Python代码。这种方法效率比较低,时间复杂度为O(n),因此这道题的关键在于提高效率。

观察旋转数组的特点,其实可以分为两个递增数组,第二个数组的第一个元素即为最小值(咱叔还没有考虑特殊情况)。为了提高效率,采用二分查找的思想,时间复杂度提升到O(logn)。具体地,用两个指针p1、p2分别指向数组的开始与末尾,然后和二分查找一样取一个中间位置的值mid,分别与p1、p2所指的值比较,如果mid>=p1说明最小值还在mid的后面,然后把p1指向mid,如果mid<=p2说明最小值在mid前面或者就是它本身,然后把p2指向mid。如此循环,p1最终会指向第一个子序列的末尾,p2指向第二个子序列的开头,此时p2所在位置就是我们要找的最小值。每次查找,范围都会缩小一般,所以效率提高。

以上方法可以解决一般情况,但是题目没有说数组中的元素没有重复值,因此还要考虑这种情况。比如{1,0,1,1,1}、{1,1,1,0,1},它们的原始数组都为{0,1,1,1,1}。但是按照我们上面的思路,会发现p1、P2和mid的值都为1,这个时候就无法判断最小值是在mid之前还是之后了。所以针对这种特殊情况,还是要采用循环遍历解决。

还有数组已经排好序的情况,查找过程就不会进入循环,直接返回第一个值,详细见代码注释。

AC

QueueWithTwoStacks 2016.12.11

题目五:

用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

解答(C++):
    class Solution
    {
    public:
    void push(int node) {
        stack1.push(node);//压入第一个栈
    }
    int pop() {
        if (stack2.empty()) {//如果stack2为空,这里保证了只有stack2完全为空的时候才会继续讲将stack1中元素压入到stack2
            while (!stack1.empty()) {
                int temp = stack1.top();//将stack1栈顶元素保存并压入stack2
                stack2.push(temp);
                stack1.pop();//删除stack1当前栈顶元素
            }
        }
        if (stack2.empty()) {//经过上面的判断,如果stack2还为空说明队列中已经没有元素了。
            printf("queue is empty");
            return -1;
        }
        else{
            int res = stack2.top();//去当前stack2栈顶元素作为pop输出
            stack2.pop();//删除stack2栈顶元素,即新队列的top元素
            return res;
        }
    }
解答(Python):
  class Solution:
    def __init__(self):
        self.stack1 = []
        self.stack2 = []
    def push(self, node):
        # write code here
        self.stack1.append(node)
    def pop(self):
        # return xx
        if self.stack2 == []:
            while self.stack1:
                a = self.stack1.pop()
                self.stack2.append(a)
        return self.stack2.pop()
笔记:

这题相对比较简单,知道栈先进后出,队列先进先出就差不多了。可以在纸上画一下,把抽象问题形象化。

stack1\stack2的出栈入栈顺序需要考虑周到,见代码注释。

AC

reConstructBinaryTree 2016.12.10

题目四:

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如输入前序遍历序列{ 1,2,4,7,3,5,6,8 }和中序遍历序列{ 4,7,2,1,5,3,8,6 },
则重建二叉树并返回。

解答(C++):
    class Solution {

    public:   
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
        if (pre.empty() || vin.empty())
            return NULL;
        return Construct(pre, 0, pre.size() - 1, vin, 0, vin.size() - 1);
    }
    TreeNode* Construct//构建递归函数划分左右子树
        (
            vector<int> pre, int pre_start, int pre_end, //前序遍历序列,序列开始位置,序列结束位置
            vector<int> vin, int vin_start, int vin_end  //中序遍历序列,序列开始位置,序列结束位置
            )
    {
        TreeNode* root = new TreeNode(pre[pre_start]);//前序遍历的第一个元素为当前树的根结点
        if (pre_start == pre_end)//如果当前前序遍历序列只剩下一个结点
        {
            if (vin_start == vin_end && pre[pre_start] == vin[vin_start])//如果中序遍历序列也只有一个结点并且与前序相同
                return root;//返回当前唯一结点
            //else//如果当前前序遍历序列只剩一个结点,但是中序遍历序列还有多个结点或者与前序遍历序列不同
                //throw std::exception("Invalid input.");//说明输入的前序遍历序列跟中序遍历序列不匹配,输出异常
        }
        int i = vin_start;//在中序遍历虚列中找到根结点的位置
        while (i < vin_end && vin[i] != root->val)
            ++i;
        int leftlen = i - vin_start;//左子树长度
        if (leftlen > 0)
            root->left = Construct(pre, pre_start + 1, pre_start + leftlen, vin, vin_start, i - 1);
        if (leftlen < pre_end - pre_start)
            root->right = Construct(pre, pre_start + leftlen + 1, pre_end, vin, i + 1, vin_end);

        return root;
    }

};
笔记:

前序遍历第一个数字就是根结点的值,然后根据中序遍历序列中根结点的位置就可以划分左右子树。然后再在划分后左右子树上递归地调用划分函数,去分别构建它们的左右子树,直至左右子树都不可再分为止。

实现该划分方法后还要考虑各种特殊情况,比如二叉树的根结点为空、输入的前序遍历序列与中序遍历序列不匹配等,考虑的越周到越好。

本地测试用例有点麻烦,需要自己构建二叉树结构以及相关的操作方法。

这题废了挺多时间,Python就不写了。

AC

printListFromTailToHead 2016.12.09

题目三:

输入一个链表,从尾到头打印链表每个节点的值。

解答(C++):
    /**
    /**
    *  struct ListNode {
    *        int val;
    *        struct ListNode *next;
    *        ListNode(int x) :
    *              val(x), next(NULL) {
    *        }
    *  };
    */

    class Solution {
    public:
    vector<int> printListFromTailToHead(ListNode* head) {
        vector<int> v;
        int a[10000];//新建一个数组存储链表数值
        ListNode *p = head;
        int i = 0;
        while (p != NULL)
        {
            a[i++] = p->val;
            p = p->next;
        }
        for (int j = i - 1; j >= 0; j--)//逆序输出
        {
            v.push_back(a[j]);
        }
        return(v);     
    }
  };

#### 解答(Python): ####

    # -*- coding:utf-8 -*-
    # class ListNode:
    #     def __init__(self, x):
    #         self.val = x
    #         self.next = None

    class Solution:
    # 返回从尾部到头部的列表值序列,例如[1,2,3]
    def printListFromTailToHead(self, listNode):
        # write code here
        l = []
        head = listNode
        while head:
            l.insert(0, head.val)
            head = head.next
        return l   
笔记:

因为链表中的内存不是一次性分配的,因此想要得到第i个结点必须从头结点开始遍历,时间效率为O(n),而在数组中可以根据下标在O(1)时间内找到。

从前往后遍历链表,额外使用一个数组或者栈存储数值,然后输出即可。

本地测试用例有点麻烦,需要自己构建链表结构以及相关的操作方法(测试代码)。

AC

ReplaceSpace 2016.12.08

题目二:

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

解答(C++):
    void replaceSpace(char *str, int length) {
    if (str == nullptr && length <= 0)//字符串为空直接返回
        return;
    //str_len为字符串原始长度
    int str_len = 0;
    int space_num = 0;
    int i = 0;
    while (str[i] != '\0')//字符串以'\0'作为结尾符号O(n)
    {
        ++str_len;//统计字符串原始长度
        if (str[i] == ' ')
            ++space_num;//空格数
        ++i;
    }
    //new_len为替换后的字符串长度
    int new_len = str_len + space_num * 2;
    if (new_len > length)
        return;

    int p1 = str_len;//p1指向原始字符串末尾
    int p2 = new_len;//p2指向新字符串末尾
    while (p1 >= 0 && p2 > p1)//从后往前遍历O(n)
    {
        if (str[p1] == ' ')//p1指向的位置为空格
        {
            str[p2--] = '0';//p2填充替换字符串'%20'
            str[p2--] = '2';
            str[p2--] = '%';
        }
        else 
        {
            str[p2--] = str[p1];
        }
        --p1;//继续向前遍历
    }
}
解答(Python):
    # -*- coding:utf-8 -*-
    class Solution:
    # s 源字符串
    def replaceSpace(self, s):
        # write code here
        r = []
        for c in s:
            if c == ' ':
                r.append('%20')
            else:
                r.append(c)
        return ''.join(r)

    print Solution().replaceSpace('str cpy')
笔记:

解题关键在于先遍历一遍字符串记录空格数,然后设置两个指针p1、p2分别指向旧字符串与新字符串的末尾,p1从后往前遍历,碰到空格就将p2所在的位置填充%20(依次往前填充)。这样所有的字符串都只需要进行依此复制即可,时间复杂度为O(n),注意一开始就遍历了一次字符串,因而实际复杂度为O(2n),详细见代码注释即可。

C/C++把常量字符串放到单独的一个内存区域,当几个指针赋值相同的常量字符串时,它们实际上指向相同的内存地址,但是用常量内存初始化数组时就不是这样了。

用python可以抖机灵.. return s.replace(’ ‘,’%20’)即可,这样就没有意义了。不过python可以直接定义一个新的list,然从开头开始遍历,直接append即可(详见代码),不用考虑边界。

AC

FindInPartiallySortedMatrix 2016.12.07

题目一:

在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

解答(C++):
    bool Find(int target, vector<vector<int> > array) {
    int rows, columns, x, y;
    rows = array.size();//行数
    columns = array[0].size();//列数
    x = rows - 1; y = 0;//坐标定在左下角
    while (x >=0 && y <=columns-1)
    {
        if (array[x][y] > target)
        {
            x--;//当前值大于目标值则上移
        }
        else if(array[x][y] < target)
        {
            y++;//当前值小于目标值则下移
        }
        else
        {
            return true;
        }
    }
    return false;     
    }
解答(Python):
    class Solution:
    # array 二维列表
    def Find(self, target, array):
        # write code here
        rows = len(array)
        columns = len(array[0])
        x = rows - 1
        y = 0 
        while x >=0 and y <=columns-1:
            if array[x][y]>target:
                x -= 1
            elif array[x][y]<target:
                y += 1
            else:
                return True

        return False   
笔记:

这道题思路倒不难,从左下角开始遍历,如果当前值大于目标值则上移,当前值小于目标值则下移,最坏时间复杂度为O(M+N),M×N的矩阵。

本地测试的时候vector数组初始化折腾了一会,之前没接触过vector,以为可以像C-style二维数组那样直接初始化,然而并不行。最后找到这个教程,实现了循环初始化,同时学习了vector的优点(安全,鲁棒)。首先,数组的越界可能会引起程序的崩溃,其次是动态性不好,包括动态改变大小,动态申请。

此外,编译遇到一些小问题,在VS2015中编译、运行成功的代码在g++编译时出错,问题出在nullptr。解决方法是g++编译命令需要加上C++11版本:g++ -std=c++11 FindInPartiallySortedMatrix.cpp -o test
还有string Name值作为参数传入方法中(char* Name)会出警告warning: deprecated conversion from string constant to 'char*' [-Wwrite-strings],解决方法将参数改为:const char* testName

AC

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值