剑指offer总结(C++和python)

剑指offer总结(C++和python)

    • 1、二维数组中的查找
    • 2、替换空格
    • 3、从尾到头打印链表
    • 4、重建二叉树
    • 5、用两个栈实现一个队列
    • 6、旋转数组的最小数字
    • 7-10、动态规划题
    • 11、二进制中1的个数
    • 12、数值的整数次方
    • 13、调整数组顺序使奇数位于偶数前面
    • 14、链表中倒数第k个结点
    • 15、反转链表
    • 16、合并两个排序链表
    • 17、树的子结构
    • 18、二叉树的镜像
    • 19、顺时针打印矩阵
    • 20、包含Min函数的栈
    • 21、栈的压入、弹出序列
    • 22、从上往下打印二叉树
    • 23、二叉搜索树的后序遍历序列
    • 24、二叉树中和为某一值的路径
    • 20、
    • 20、

代码有C++和python两个版本。用C++来做题,python用来巩固,简化。
我发现用python做题简直就是在作弊啊。

在做题目之前理一下思路,做完经典的题目要总结算法原理。重要的不是题目本身,而是要在做题中锻炼自己的思维能力和解决问题的能力。

做题的过程也是很有趣的。

1、二维数组中的查找

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

思路分析

最简单的方法是遍历数组,可以用python实现。
从减少复杂度的角度来说分析一下:数组的每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。这样一开始应该和第一行最右边的数比较,比它大在它下面,比它小就在它左边。也可以先和第一列最下边的数字比较,道理是一样的。这样做可以减少一半的计算量。

代码

c++:

class Solution {
public:
    bool Find(int target, vector > array) {
        int list = array.size()-1;
        int col = 0;
        while(col= 0)
        {
            if(target > array[col][list])
                col++;
            else if(target

python:

# -*- coding:utf-8 -*-
class Solution:
    # array 二维列表
    def Find(self, target, array):
        # write code here
        for i in range(len(array)):
            for j in range(len(array[0])):
                if target==array[i][j]:
                    return True
        return False

2、替换空格

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

思路分析

  1. 题目给的返回值类型是void,只能更改str指向的字符,不能返回一个新的数组。
  2. 更改过后的数组比更改前的数组长,只能从后往前重新赋值,不然内容会被覆盖。
  3. 先确定空格数量、新字符串的长度,然后用一个循环从后往前赋值即可。
  4. 用python的话只用一个函数就行啦。

代码

C++:

class Solution {
public:
	void replaceSpace(char *str,int length) {
        int oldlen=0,newlen=0;
        while(str[oldlen]!='\0')
        {
            if(str[oldlen]==' ')
                newlen+=2;
            newlen++,oldlen++;
        }
       //此时str[i]指向\0,而对于字符串来说结尾必须有\0
        newlen++;//给'\0'占一个空间
        str[newlen--]='\0';
        while(newlen>oldlen&&oldlen>=0)
        {
            if(str[oldlen]==' ')
            {
                str[newlen--]='0';
                str[newlen--]='2';
                str[newlen--]='%';
            }
            else
                str[newlen--]=str[oldlen];
            oldlen--;
        }
	}
};

python:

class Solution:
    def replaceSpace(self, s):
        return s.replace(" ","%20")

3、从尾到头打印链表

使用reverse逆置函数

输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。

思路分析

  1. 借用栈先进后出的特点,把链表的值从头到尾输入到栈中,再打印出来。
  2. 也可以用reverse函数把vector逆置输出

代码

C++:

/**
*  struct ListNode {
*        int val;
*        struct ListNode *next;
*        ListNode(int x) :
*              val(x), next(NULL) {
*        }
*  };
*/
class Solution {
public:
    vector printListFromTailToHead(ListNode* head) {
        vector ArrayList;
        if(head==NULL)return ArrayList;
        ListNode*cur=head;
        while(cur)
        {
            ArrayList.push_back(cur->val);
            cur=cur->next;
        }
        reverse(ArrayList.begin(),ArrayList.end());
        return ArrayList;
    }
};

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
        s1=[]
        while listNode:
            s1.append(listNode.val)
            listNode=listNode.next
        return list(reversed(s1))

4、重建二叉树

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

思路分析

  1. 用递归
  2. 首先根据前序遍历序找到根节点1,再根据中序遍历序列找到左子树{4,7,2}和右子树{5,3,8,6},再根据左右子树的前序遍历序和中序遍历序列进行递归。

代码

C++:

/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* reConstructBinaryTree(vector pre,vector vin) {
        int len=pre.size();
        if(len==0)return nullptr;
        
        TreeNode*ret=new TreeNode(pre[0]);//返回的根节点
        vector left_pre,left_in,right_pre,right_in;
        
        int Inroot;
        for(Inroot=0;InrootInroot)
            {
                right_pre.push_back(pre[i]);
                right_in.push_back(vin[i]);
            }
        }
        ret->left=reConstructBinaryTree(left_pre,left_in);
        ret->right=reConstructBinaryTree(right_pre,right_in);
        return ret;
    }
};

python:

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    # 返回构造的TreeNode根节点
    def reConstructBinaryTree(self, pre, tin):
        length = len(tin)
        if length == 0:
        	return None
        head = TreeNode(pre[0])
        head.left=self.reConstructBinaryTree(pre[1:tin.index(pre[0])+1],tin[:tin.index(pre[0])])
        head.right=self.reConstructBinaryTree(pre[tin.index(pre[0])+1:],tin[tin.index(pre[0])+1:])
        return head

5、用两个栈实现一个队列

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

思路分析

  1. 栈:先进后出。队列:先进先出。
  2. 定义两个栈stake1和stake2,两个栈的内容相同,顺序相反,stake1头朝外,stake2尾巴朝外。Push则在stake2直接Push。Pop则把stake2中的元素依次入栈到stake1,在stake1直接Pop,然后再把stake1中的元素依次入栈到stake2.
  3. 总而言之,stake2存储了队列的所有信息,stake1类似缓存。

代码

C++:

class Solution
{
public:
    void push(int node) {
        stack2.push(node);
    }

    int pop() {
        if(stack1.empty())
        {
            while(!stack2.empty())
            {
                stack1.push(stack2.top());
                stack2.pop();
            }
        }
        int val=stack1.top();
        stack1.pop();
        return val;
    }

private:
    stack stack1;
    stack stack2;
};

python:

class Solution:
    def __init__(self):
        self.stack1=[]
        self.stack2=[]
    def push(self, node):
        self.stack1.append(node)
    def pop(self):
        for x in range(len(self.stack1)):
            self.stack2.append(self.stack1.pop())
        p=self.stack2.pop()
        for x in range(len(self.stack2)):
            self.stack1.append(self.stack2.pop())
        return p

6、旋转数组的最小数字

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

思路分析

  1. 遍历,依次和数组第一个值比较大小即可

代码

C++:

class Solution {
public:
    int minNumberInRotateArray(vector rotateArray) {
        if (rotateArray.size()==0)
            return 0;
        int min_num=rotateArray[0];
        for (int i=1;irotateArray[i])
            {
                min_num=rotateArray[i];
                break;
            }
        }
        return min_num;
    }
};

python:

# -*- coding:utf-8 -*-
class Solution:
    def minNumberInRotateArray(self, rotateArray):
        rotateArray.sort()
        return rotateArray[0]

7-10、动态规划题

剑指offer总结——动态规划篇

11、二进制中1的个数

输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

思路分析

按位做与运算

代码

C++:

class Solution {
public:
     int  NumberOf1(int n) {
         int flag=1,count=0;
         while(flag!=0){
             if(n&flag)
                 count++;
             flag<<=1;
         }
         return count;
     }
};

12、数值的整数次方

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

思路分析

要对整数exponent分情况讨论,exponent大于0,exponent小于0,还有exponent等于0。
如果exponent数值很大,比如计算 15. 0 1000 15.0^{1000} 15.01000,可以用到剑指offer总结——动态规划篇中矩阵快速幂的方法计算。

代码

C++:

class Solution {
public:
    double Power(double base, int exponent) {
    	double end=1.0;
        if (exponent==0)
            return 1;
        if(exponent<0){
            exponent=-exponent;
            while(exponent--){
            	end*=base;
        	}
            return 1/end;
        }
        else{
            while(exponent--){
                end*=base;
            }
        	return end;
        }
    }
};

13、调整数组顺序使奇数位于偶数前面

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

思路分析

用两个数组分别存储奇数和偶数,然后将偶数数组里的数据移到奇数数组里即可。

代码

C++:

class Solution {
public:
    void reOrderArray(vector &array) {
        vector result_even, result_odd;
        for(int i=0;i

python:

class Solution:
    def reOrderArray(self, array):
        # write code here
        return sorted(array,key=lambda c:c%2,reverse=True)

14、链表中倒数第k个结点

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

思路分析

一开始想的是先遍历链表,计算出链表的长度l,然后找到输出l-k结点。
后来看到可以用两个指针来做,设置两个指针指向头节点,第一个指针向前走k-1步,走到第k个结点,此时,第二个指针和第一个指针同时移动,当第一个指针到尾节点的时候,第二个指针指向倒数第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) {
        ListNode* p=pListHead;
        ListNode* q=pListHead;
        if(pListHead==NULL || k<0)
            return NULL;
        for(int i=0;inext==NULL)
                return NULL;
            p=p->next;
        }
        while(p->next!=NULL)
        {
            p=p->next;
            q=q->next;
        }
        return q;
    }
};

15、反转链表

输入一个链表,反转链表后,输出新链表的表头。

思路分析

反转链表可是经典题型。
这里我用的方法是先对原链表做头删操作,再对新链表做头插,循环操作即可

代码

C++:

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        ListNode* last = NULL;
        ListNode* tmp;
        while(pHead != NULL)
        {
        	//头删
            tmp = pHead->next;
            pHead->next = last;
			
			//头插
            last = pHead;
            pHead = tmp;
        }
        return last;
    }
};

16、合并两个排序链表

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

思路分析

比较头节点大小,小的作为合并后链表的头节点,再比较剩余部分和另一个链表的头节点,取小的,然后一直递归此过程。

代码

C++:

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        if(pHead1==NULL)
        {
            return pHead2;
        }
        if(pHead2==NULL)
        {
            return pHead1;
        }
        
        ListNode* p;
        if(pHead1->val >= pHead2->val)
        {
            p = pHead2;
            p->next = Merge(pHead1, pHead2->next);
        }
        else
        {
            p = pHead1;
            p->next = Merge(pHead1->next, pHead2);
        }
        return p;
    }
};

17、树的子结构

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

思路分析

要先写一个函数DoesRoot1HaveRoot2(),作用是判断二叉树A,B是否相同,这个用递归很容易写出来。写出来之后判断B是不是A的子结构就简单了,只需要判断B与A的左右子树是否相同。

为了结构好看,可以定义一个flag作为标志位。

代码

c++:

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        bool flag = false;
        if(pRoot1 != NULL && pRoot2 != NULL)
        {
            if(pRoot1->val == pRoot2->val)
            {
                flag = DoesRoot1HaveRoot2(pRoot1, pRoot2);
            }
            if(!flag)
            {
                flag = DoesRoot1HaveRoot2(pRoot1->left, pRoot2);
            }
            if(!flag)
            {
                flag = DoesRoot1HaveRoot2(pRoot1->right, pRoot2);
            }
        }
        return flag;
    }
    
    bool DoesRoot1HaveRoot2(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        if(pRoot2 == NULL)
        {
            return true;
        }
        if(pRoot1 == NULL)
        {
            return false;
        }
        if(pRoot1->val != pRoot2->val)
        {
            return false;
        }
        return DoesRoot1HaveRoot2(pRoot1->left, pRoot2->left) && DoesRoot1HaveRoot2(pRoot1->right, pRoot2->right);
            
    }
};

18、二叉树的镜像

操作给定的二叉树,将其变换为源二叉树的镜像。

思路分析

递归。只需要调换每个结点的子节点即可

代码

c++:

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    void Mirror(TreeNode *pRoot) {
        TreeNode* p;
        if(pRoot == NULL)
        {
            return;
        }
        p = pRoot->left;
        pRoot->left = pRoot->right;
        pRoot->right = p;
        Mirror(pRoot->left);
        Mirror(pRoot->right);
    }
};

19、顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

思路分析

不难,就是麻烦

代码

c++:

class Solution {
public:
    vector<int> printMatrix(vector<vector<int> > matrix) {
        int cir = 0;
        int row = matrix.size();
        int col = matrix[0].size();
        vector<int> ans;
        while(row>2*cir && col>2*cir){
            //上面
            for(int i=cir;i<=col-cir-1;i++)
                ans.push_back(matrix[cir][i]);
            //右面
            if(cir<row-cir-1){
                for(int i=cir+1;i<=row-cir-1;i++)
                    ans.push_back(matrix[i][col-cir-1]);
            }
            //下面
            if(col-cir-1>cir && row-1-cir>cir){
                for(int i=col-cir-2;i>=cir;i--)
                    ans.push_back(matrix[row-1-cir][i]);
            }
            //左面
            if(cir<col-cir-1 && cir<row-cir-2){
                for(int i = row-cir-2;i>=cir+1;i--)
                    ans.push_back(matrix[i][cir]);
            }
            cir++;
        }
        return ans;
    }
};

20、包含Min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。

思路分析

这题首先定义栈的数据结构,定义一个栈stake1,其中pop(),push()的函数定义直接调用即可。但是现在要求栈中的最小函数,则需要再定义一个栈stackmin,stackmin中的元素数量和stake1一样,第n元素的值则是stake1前n个值的最小值。

代码

c++:

class Solution {
public:
    void push(int value) {
        stack1.push(value);
        if(stackmin.empty())
            stackmin.push(value);
        else if (stackmin.top()<value)
            stackmin.push(stackmin.top());
        else
            stackmin.push(value);
    }
    void pop() {
        stack1.pop();
        stackmin.pop();
    }
    int top() {
        return stack1.top();
    }
    int min() {
        return stackmin.top();
    }
private:
    stack<int> stack1;
    stack<int> stackmin;
};

21、栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)

思路分析

代码

c++:

class Solution {
public:
    bool IsPopOrder(vector<int> pushV,vector<int> popV) {
        stack<int> stack1;
        for(int i=0;i<pushV.size();i++)
        {
            stack1.push(pushV[i]);
            while(!stack1.empty() && stack1.top()==popV[0])
            {
                stack1.pop();
                popV.erase(popV.begin());
            }
        }
        if(stack1.empty())
            return true;
        else
            return false;
    }
};

22、从上往下打印二叉树

从上往下打印出二叉树的每个节点,同层节点从左至右打印。

思路分析

首先想到广度优先搜索算法,

  1. 初始化一个队列,将二叉树的根节点放入队列
  2. 然后把队列的最前面的元素的左右子节点放入队列,如果有的话。
  3. 打印队列的最前面节点的值
  4. 取出队列的最前面节点
  5. 循环进行234,直至队列为空。

代码

c++:

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
        vector<int> res;
        queue<TreeNode *> Q;
        if(root==NULL){
            return res;
        }
        Q.push(root);
        TreeNode* first;
        while(Q.size())
        {
            first=Q.front();
            res.push_back(first->val);
            if(first->left!=NULL){
                Q.push(first->left);
            }
            if(first->right!=NULL)
            {
                Q.push(first->right);
            }
            Q.pop();
        }
        return res;
    }
};

23、二叉搜索树的后序遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

思路分析

二叉搜索树:根节点的值大于其左子树中任意一个节点的值,小于其右节点中任意一节点的值,这一规则适用于二叉查找树中的每一个节点。

二叉搜索树的后序遍历规律:小大中。其子树都是小大中的排序。

左子树节点的值小于根节点,右子树节点的值大于根节点,根节点在最后一位,找到根节点即可将二叉搜索树分为:左子树、右子树和根。

其左子树和右子树又可以再分为左子树、右子树和根三个部分。

后序遍历可能的情况:1.除最后一个数以外所有的数均大于或小于最后一个数(左子树或右子树为空);2.除最后一个数以外所有的数分为两部分,前面一部分小于最后一个数,后面一部分大于最后一个数。出现这两种情况则再继续划分左右子树,否则输出NO

直到叶子节点。

代码

c++:

class Solution {
public:
    bool VerifySquenceOfBST(vector<int> sequence) {
        if(sequence.size()==0){
            return false;
        }
        return Solve(sequence,0,sequence.size());
    }
    
    bool Solve(vector<int> sequence,int index,int length)
    {
        if(length<=1){
            return true;
        }
        int i=0;
        for(;i<length-1;++i)
        {
            if(sequence[i+index]>sequence[index+length-1])
                break;
        }
        for(int j=i;j<length-1;++j)
        {
            if(sequence[j+index]<sequence[index+length-1])
                return false;
        }
        return Solve(sequence,index,i) && Solve(sequence,index+i,length-i-1);
    }
};

24、二叉树中和为某一值的路径

输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)

思路分析

想到深度优先算法,用到递归,回溯。

本题使用前序遍历的方式访问节点,使用二维向量result存储全部路径,使用一维向量tmp存储当前路径。

遍历二叉树的过程:按前序遍历顺序访问每一个节点。访问每个结点时,将结点添加到路径向量tmp中。如果当前结点是叶子结点,则判断当前路径是否是符合条件的路径,符合条件的路径存入到二维向量result;如果当前结点不是叶子结点,则递归当前节点的左右子节点。

代码

c++:

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
        if(root)
            Solve(root, expectNumber);
        return result;
    }
    
    void Solve(TreeNode * node , int target)
    {
        tmp.push_back(node->val);
         
        if(!node->left && !node->right)
        {
            if(target - node->val == 0)
                result.push_back(tmp);
        }
        else
        {
            if(node->left) Solve(node->left, target - node->val);
            if(node->right) Solve(node->right, target - node->val);
        }
         
        if(!tmp.empty())
            tmp.pop_back(); 
    }
private:
    vector<vector<int> > result;
    vector<int> tmp;
};

20、

思路分析

代码

c++:


20、

思路分析

代码

c++:


你可能感兴趣的:(剑指offer)