剑指Offer算法刷题解答 --基于牛客网C++编译器

注:首先感谢牛客网提供了这样一个平台,让我在看书刷题之余能够刷到书上的题,希望牛客网越办越好,不忘初心~

黑色表示难度较低不会再写粉色表示打算三刷红色表示看答案才会今后必刷。

 

1、赋值运算符函数

注:略。

 

2、实现Singleton模式

注:略。

 

3、数组中重复的数字

题目描述:

在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。

class Solution {
public:
    // Parameters:
    //        numbers:     an array of integers
    //        length:      the length of array numbers
    //        duplication: (Output) the duplicated number in the array number
    // Return value:       true if the input is valid, and there are some duplications in the array number
    //                     otherwise false
    bool duplicate(int numbers[], int length, int* duplication) {
        
        if(length<=0)
            return false;
        
        for(int i=0;i

注:可以排序,也可以用hash,本题用的书上的最优方法。

 

4、二维数组中的查找

题目描述:

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

class Solution {
public:
    bool Find(int target, vector > array) {
        
        if(array.size()==0 && array[0].size()==0)
            return false;
        
        int left = 0, right = array[0].size(), up = 0, down = array.size();
        while(lefttarget)
                    down_c = mid;
                else return true;
            }
            up = up_c;
            if(up>=down)
                return false;
            
            while(left_ctarget)
                    right_c = mid;
                else return true;
            }
            right = left_c;
        }
        return false;
    }
};

注:由于数组的特性,需要从右上或者左下开始二分查找(剑指offer中是顺序查找)。

 

5、替换空格

题目描述:

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

class Solution {
public:
	void replaceSpace(char *str,int length) {
        
        if(length<0 || *str=='\0')
            return ;
        
        char* p = str;
        int numOfspace = 0, lengthOfstring = 0;
        
        while(*p!='\0')
        {
            if(*p==' ')
                numOfspace++;
            p++;
            lengthOfstring++;
        }
        
        int total_Length = lengthOfstring + 2*numOfspace;
        
        while(total_Length>=0 && total_Length

注:从字符串尾部开始替换。

 

6、从尾到头打印链表

题目描述:

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

/**
*  struct ListNode {
*        int val;
*        struct ListNode *next;
*        ListNode(int x) :
*              val(x), next(NULL) {
*        }
*  };
*/
class Solution {
public:
    vector printListFromTailToHead(ListNode* head) {
        
        vector output;
        stack s;
        
        while(head)
        {
            s.push(head);
            head = head->next;
        }
        
        while(!s.empty())
        {
            output.push_back(s.top()->val);
            s.pop();
        }
        
        return output;
    }
};

注:使用栈来取到链表尾部,然后挨个push入vector。

 

7、重建二叉树

题目描述:

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

/**
 * 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) {
        
        if(pre.size()==0 || vin.size()==0 || pre.size()!=vin.size())
            return nullptr;
        
        int left_pre = 0;
        
        return recover(pre, vin, left_pre, pre.size(), 0, vin.size());
    }
    
    TreeNode* recover(vector pre,vector vin, int& left_pre, int right_pre, int left_vin, int right_vin)
    {
        if(left_pre>=right_pre || left_vin>=right_vin)
            return nullptr;
        
        TreeNode* root = new TreeNode(pre[left_pre]);
        
        int target = Findtarget(vin, left_vin, pre[left_pre]);
        left_pre++;
        
        root->left = recover(pre, vin, left_pre, pre.size(), left_vin, target);
        root->right = recover(pre, vin, left_pre, pre.size(), target+1, right_vin);
        
        return root;
    }
    int Findtarget(vector vin, int init, int target)
    {
        for(int i=init;i

注:在前序遍历中找根节点,在中序遍历中找左右子树,然后递归即可。

 

8、二叉树的下一个节点

题目描述:

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

/*
struct TreeLinkNode {
    int val;
    struct TreeLinkNode *left;
    struct TreeLinkNode *right;
    struct TreeLinkNode *next;
    TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) {
        
    }
};
*/
class Solution {
public:
    TreeLinkNode* GetNext(TreeLinkNode* pNode)
    {
        if(!pNode)
            return nullptr;
        
        if(pNode->right)
        {
            TreeLinkNode* p = pNode->right;
            while(p->left)
                p = p->left;
            return p;
        }
        
        while(pNode->next)
        {
            if(pNode == pNode->next->left)
                return pNode->next;
            pNode = pNode->next;
        }
        return nullptr;
    }
};

注:总共有四种情况,如果它是空,则返回空,如果他有右节点,则输出右节点的最左节点,如果没有右节点,则如果他是他父节点的左节点,则输出父节点,如都不是,则递归向上找是父节点的左节点的节点,他就是下一个节点。

 

9、用两个栈实现队列

题目描述:

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

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

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

private:
    stack stack1;
    stack stack2;
};

注:定义两个栈,来回倒腾就行了,倒腾的最后一个数就弹出做输出。

 

10、斐波那契数列

题目一描述:斐波那契数列

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

class Solution {
public:
    int Fibonacci(int n) {
        
        vector cache(n+1,0);
        
        for(int i=0;i<=n;i++)
        {
            if(i==0)
                cache[i] = 0;
            else if(i==1)
                cache[i] = 1;
            else cache[i] = cache[i-1] + cache[i-2];
        }
        return cache[n];
    }
};

注:简单的动态规划题,斐波那契数列是f(0)=0,f(1)=1,f(n)=f(n-1)+f(n-2),可以从i=0到i=n从下至上的考虑。这样简单很多。

题目二描述:跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

class Solution {
public:
    int jumpFloor(int number) {
        
        vector cache(number+1,0);
        
        for(int i=0;i<=number;i++)
        {
            if(i==0 || i==1)
                cache[i] = 1;
            else cache[i] = cache[i-1] + cache[i-2];
        }
        return cache[number];
    }
};

注:与题目思想一致,是斐波那契数列的一种变体,它这里f(0)也为1。

题目三描述:变态跳台阶

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

class Solution {
public:
    int jumpFloorII(int number) {
        
        vector cache(number+1,0);
        
        for(int i=0;i<=number;i++)
        {
            if(i==0 || i==1)
                cache[i] = 1;
            else 
            {
                for(int j=1;j<=i;j++)
                    cache[i] += cache[i-j];
            }
        }
        return cache[number];
    }
};

注:与题目二思想一致,是斐波那契数列的一种变体,只需用for循环模拟即可。

题目四描述:矩形覆盖

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

class Solution {
public:
    int rectCover(int number) {
        
        if(number == 0)
            return 0;
        
        vector cache(number+1,0);
        
        for(int i=0;i<=number;i++)
        {
            if(i==0 || i==1)
                cache[i] = 1;
            else cache[i] = cache[i-1] + cache[i-2];
        }
        return cache[number];
    }
};

注:与题目二思想一致,是斐波那契数列的一种变体,不同的是当number数为0时,覆盖方法数也为0。

 

11、旋转数组的最小数字

题目描述:

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

class Solution {
public:
    int minNumberInRotateArray(vector rotateArray) {
        
        if(rotateArray.size()==0)
            return 0;
        
        int left = 0, right = rotateArray.size()-1;
        
        while(leftrotateArray[left])
                left = mid + 1;
            else if(rotateArray[mid]

注:利用旋转数组的性质与二分法,如果left指针小于mid指针就说明左面是连续数组,right指针大于mid就说明右面是连续数组,如果都相等则left自加即可。

 

12、矩阵中的路径

题目描述:

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。 例如 a b c e s f c s a d e e 这样的3 X 4 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

class Solution {
public:
    bool hasPath(char* matrix, int rows, int cols, char* str)
    {
        if(rows * cols<=0 || *matrix == '\0')
            return false;
        vector cache(rows * cols, 0);
        
        for(int i =0;i cache, int i, int j, int rows, int cols, char* str)
    {
        if(*str=='\0')
            return true;
        
        if(*(matrix+i*cols+j)==*str && cache[i*cols+j]==0)
        {
            cache[i*cols+j] = 1;
            
            if(i+1=0 && Path(matrix, cache, i-1, j, rows, cols, str+1))
                return true;
            
            if(j-1>=0 && Path(matrix, cache, i, j-1, rows, cols, str+1))
                return true;
            
            if(*(str+1)=='\0')
                return true;
            
            cache[i*cols+j] = 0;
        }
        return false;
    }
};

注:在所有路径当中挨个递归查询就好了。

 

13、机器人的运动范围

题目描述:

地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

class Solution {
public:
    int movingCount(int threshold, int rows, int cols)
    {
        
        if(rows*cols == 0)
            return 0;
        
        vector > cache(rows,vector(cols, 0));
        
        return PathCount(cache, threshold, 0, 0, rows, cols);
        
    }
    
    int PathCount(vector > &cache, int threshold, int row, int col, int rows, int cols)
    {
        if(row>=rows || col>=cols || row<0 || col<0 || cache[row][col]==1 || cumsum(row)+cumsum(col)>threshold)
            return 0;
        cache[row][col] = 1;
        
        return 1 + 
            PathCount(cache, threshold, row-1, col, rows, cols) + 
            PathCount(cache, threshold, row+1, col, rows, cols) + 
            PathCount(cache, threshold, row, col-1, rows, cols) + 
            PathCount(cache, threshold, row, col+1, rows, cols);
    }
    
    int cumsum(int cache)
    {
        int sum = 0;
        while(cache)
        {
            sum += cache%10;
            cache/=10;
        }
        return sum;
    }
};

注:跟12题一样的思路,不过增加了额外的限制条件。

 

14、剪绳子

注:略。

 

15、二进制中1的个数

题目描述:

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

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

注:由于存在负数,所以只能通过左移flag来实现按位与;还有一种方法就是n=(n-1)&n,把一个整数减去1以后再和原来的整数做位与运算,得到的结果相当于把整数的二进制表示中的最右面的1变成0。

 

16、数值的整数次方

题目描述:

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

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

注:递归二分求解的思想,需要考虑指数为0和负数的特殊情况。

 

17、打印从1到最大的n位数

 

注:略。

 

18、删除链表中重复的结点

题目描述:

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* deleteDuplication(ListNode* pHead)
    {
        ListNode* m = new ListNode(0);
        m->next = pHead;
        ListNode* p = m;
        ListNode* q = pHead;
        
        while(q && q->next)
        {
            if(q->val == q->next->val)
                q = q->next;
            else
            {
                if(p->next == q)
                    p = p->next;
                else
                    p->next = q->next;
                q = q->next;
            }
        }
        if(p->next!=q)
            p->next = nullptr;
        
        return m->next;
    }
};

注:找规律的链表题。

 

19、正则表达式匹配

题目描述:

请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配

class Solution {
public:
    bool match(char* str, char* pattern)
    {
        if(*str == '\0' && *pattern== '\0')
            return true;
        
        if(*pattern!= '\0' && *(pattern+1) == '*')
        {
            if(*str != '\0' && (*str == *pattern || *pattern=='.'))
                return match(str+1, pattern+2) || match(str, pattern+2) || match(str+1, pattern);
            else return match(str, pattern+2);
        }
        
        if(*str != '\0' && (*str == *pattern || *pattern=='.'))
            return match(str+1, pattern+1);
        return false;
    }
};

注:这种题首先就要考虑特殊条件,即下一个字符为*时的情况,随后递归各条件即可。

 

20、表示数值的字符串

题目描述:

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。

class Solution {
public:
    bool isNumeric(char* string)
    {
        
        if(!string)
            return false;
        
        isUnsignedNumber(&string);
        
        if(*string == '.')
            ++string;
        
        isNumber(&string);
        
        char* p = string;
        if(*string == 'E' || *string == 'e')
        {
            ++string;
            isUnsignedNumber(&string);
        }
        
        if(*string=='\0' && (*p=='\0' || *(p+1)!='\0'))
            return true;
        return false;
    }
    
    void isNumber(char** string)
    {
        while(**string>='0' && **string<='9')
            ++(*string);
    }
    
    void isUnsignedNumber(char** string)
    {
        if(**string=='+' || **string=='-')
            ++(*string);
        isNumber(string);
    }
};

注:这里唯一需要注意的就是使用指针的指针。

 

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

题目描述:

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

class Solution {
public:
    void reOrderArray(vector &array) {
        
        vector cache(array.size(), 0);
        
        int left = 0;
        
        for(int i=0;i

注:如果不要求相对顺序不变则可以使用双指针,但是题目要求相对顺序不变,定义一个额外数组,遍历两遍原数组即可。

 

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

题目描述:

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

/*
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(k<=0 || !pListHead)
            return nullptr;
        
        ListNode* slow = pListHead, *fast = pListHead;
        
        while(k)
        {
            if(!fast)
                return nullptr;
            fast = fast->next;
            --k;
        }
        
        while(fast)
        {
            slow = slow->next;
            fast = fast->next;
        }
        return slow;
    }
};

注:使用快慢指针,快的先比慢的走k步,然后再一起一步一步的走,这里需要注意k有可能是非法值,有可能小于等于0,或者直接就大于整个链表长度,这时需要返回nullptr指针。

 

23、链表中环的入口结点

题目描述:

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        ListNode* m = HasLoop(pHead);
        ListNode* slow = pHead, *fast = pHead;
        if(!m)
            return nullptr;
        
        ListNode* p = m;
        int num = 1;
        while(p->next != m)
        {
            ++num;
            p = p->next;
        }
        
        while(num)
        {
            fast = fast->next;
            --num;
        }
        
        while(slow != fast)
        {
            slow = slow->next;
            fast = fast->next;
        }
        
        return slow;
    }
    
    ListNode* HasLoop(ListNode* pHead)
    {
        if(!pHead)
            return nullptr;
        
        ListNode* slow = pHead, *fast = pHead;
        
        while(fast && fast->next)
        {
            slow = slow->next;
            fast = fast->next->next;
            if(fast == slow)
                return slow;
        }
        return nullptr;
    }
};

注:首先使用快慢(快的步长是慢的二倍)指针判断有没有环,如果有快慢指针一定会相交,其次找到相交点计算环的长度,然后根据环的长度再次使用快慢指针(快指针比慢指针先走了环的长度大小,步长均为1)即可找到环的入口。

 

24、反转链表

题目描述:

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

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        
        ListNode* p = nullptr;
        ListNode* q = pHead;
        
        while(q)
        {
            ListNode* r = q->next;
            q->next = p;
            p = q;
            q = r;
        }
        return p;
    }
};

注:定义p、q、r三个指针,从头结点还是反转每一个结点即可。

 

25、合并两个排序的链表

题目描述:

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

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        
        ListNode* m = new ListNode(0);
        ListNode* p = m;
        while(pHead1 || pHead2)
        {
            int num1 = INT_MAX, num2 = INT_MAX;
            
            if(pHead1)
                num1 = pHead1->val;
            
            if(pHead2)
                num2 = pHead2->val;
            
            p->next = new ListNode(min(num1, num2));
            p = p->next;
            
            if(num1>num2)
                pHead2 = pHead2->next;
            else pHead1 = pHead1->next;
        }
        
        return m->next;
    }
};

注:只需要挨个比对链表头结点就好了,我这里的代码是使用了哨兵的思想,但是会有一个隐藏bug,当这两个节点的尾指针全都等于哨兵INT_MAX或者pHead2等于INT_MAX时我的程序就会报错。

 

26、树的子结构

题目描述:

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

/*
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)
    {
        if(!pRoot1 || !pRoot2)
            return false;
        
        if(IsSubtree(pRoot1, pRoot2))
            return true;
        
        return HasSubtree(pRoot1->left, pRoot2) || HasSubtree(pRoot1->right, pRoot2);
    }
    
    bool IsSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        if(!pRoot2)
            return true;
        
        if(!pRoot1)
            return false;
        
        if(pRoot1->val == pRoot2->val)
            return IsSubtree(pRoot1->left, pRoot2->left) && IsSubtree(pRoot1->right, pRoot2->right);
        return false;
    }
};

注:递归判断各子树有没有这个结构就好了。

 

27、二叉树的镜像

题目描述:

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

/*
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) {
        
        if(!pRoot)
            return ;
        
        TreeNode *temp = pRoot->left;
        pRoot->left = pRoot->right;
        pRoot->right = temp;
        
        Mirror(pRoot->left);
        Mirror(pRoot->right);
    }
};

注:递归交换左右子树就好了。

 

28、对称的二叉树

题目描述:

请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

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

注:递归判断两个子树,左子树等于右子树,右子树等于左子树即可。

 

29、顺时针打印矩阵

题目描述:

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下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.

class Solution {
public:
    vector printMatrix(vector > matrix) {
        
        if(matrix.size()==0 || matrix[0].size()==0)
            return {};
        
        vector output;
        int left = 0, right = matrix[0].size()-1, up = 0, down = matrix.size()-1;
        
        while(left<=right && up<=down)
        {
            int left_c = left, right_c = right, up_c = up, down_c = down;
            
            while(left_c<=right)
                output.push_back(matrix[up][left_c++]);
            up++;
            up_c++;
            
            while(up_c<=down)
                output.push_back(matrix[up_c++][right]);
            right--;
            right_c--;
            
            while(left<=right_c && up<=down)
                output.push_back(matrix[down][right_c--]);
            down--;
            down_c--;
            
            while(up<=down_c && left<=right)
                output.push_back(matrix[down_c--][left]);
            left++;
        }
        return output;
    }
};

注:主要注意一下从右至左以及从下至上打印的边界条件就好了。

 

30、包含min函数的栈

题目描述:

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

class Solution {
private:
    stack s,m;
    int minimum = INT_MAX;
public:
    void push(int value) {
        
        s.push(value);
        if(m.empty())
            minimum = value;
        else 
            minimum = ::min(value, minimum);
        m.push(minimum);
    }
    void pop() {
        
        s.pop();
        m.pop();
        minimum = m.top();
    }
    int top() {
        return s.top();
    }
    int min() {
        return minimum;
    }
};

注:需要额外定义一个最小栈,每次压入弹出栈的时候都需要对最小栈进行操作以取得当前最小值。

 

31、栈的压入、弹出序列

题目描述:

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

class Solution {
public:
    bool IsPopOrder(vector pushV,vector popV) {
        
        if(pushV.size() == 0 || popV.size() == 0)
            return true;
        
        int pushV_ptr = 0, popV_ptr = 0;
        stack s;
        while(pushV_ptr

注:使用一个栈来模拟压入弹出即可。

 

32、从上往下打印二叉树

题目一描述:从上往下打印二叉树

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

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

注:使用队列进行层序遍历即可。

题目二描述:按之字形顺序打印二叉树

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    vector > Print(TreeNode* pRoot) {
        
        if(!pRoot)
            return {};
        
        vector > output;
        vector cache;
        
        stack s1,s2;
        s1.push(pRoot);
        bool flag = true;
        
        while(!s1.empty() || !s2.empty())
        {
            if(flag)
            {
                int size = s1.size();
                while(size--)
                {
                    TreeNode* p = s1.top();
                    cache.push_back(p->val);
                    
                    if(p->left)
                        s2.push(p->left);
                    if(p->right)
                        s2.push(p->right);
                    s1.pop();
                }
                flag = !flag;
            }
            else
            {
                int size = s2.size();
                while(size--)
                {
                    TreeNode* p = s2.top();
                    cache.push_back(p->val);
                    
                    if(p->right)
                        s1.push(p->right);
                    if(p->left)
                        s1.push(p->left);
                    s2.pop();
                }
                flag = !flag;
            }
            output.push_back(cache);
            cache.clear();
        }
        return output;
    }
};

注:使用两个栈即可,一个先压入左子树,一个先压入右子树。

 

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

题目描述:

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

class Solution {
public:
    bool VerifySquenceOfBST(vector sequence) {
        
        if(sequence.size()==0)
            return false;
        
        int left = 0, right = sequence.size()-1;
        
        return Verify(sequence, left, right);
        
    }
    
    bool Verify(vector &sequence, int left, int right)
    {
        if(left>=right)
            return true;
        
        int major = sequence[right];
        int mid = right;
        for(int i=left;imajor)
            {
                mid = i;
                break;
            }
        }
        
        for(int i=mid;i

注:根据二叉搜索树的性质,从后往前找根节点,找到根节点后找左右子树,分别验证左右子树是不是二叉搜索树(即左子树都比根节点小,右子树都比根节点大)即可。

 

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

题目描述:

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

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    vector > FindPath(TreeNode* root,int expectNumber) {
        
        vector > output;
        vector cache;
        
        Path(output, root, cache, expectNumber);
        sort(output.begin(),output.end(), setting);
        return output;
    }
    
    void Path(vector >& output, TreeNode* root, vector cache, int expectNumber)
    {
        if(!root)
            return ;
        
        expectNumber-=root->val;
        cache.push_back(root->val);
        
        if(!root->left && !root->right && expectNumber==0)
        {
            output.push_back(cache);
            return ;
        }
        
        Path(output, root->left, cache, expectNumber);
        Path(output, root->right, cache, expectNumber);
    }
    
    static bool setting(vector a, vector b)
    {
        return a.size()>b.size();
    }
};

注:挨个路径去验证就好了,验证完还需要对数组长度进行排序才能输出。

 

35、复杂链表的复制

题目描述:

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

/*
struct RandomListNode {
    int label;
    struct RandomListNode *next, *random;
    RandomListNode(int x) :
            label(x), next(NULL), random(NULL) {
    }
};
*/
class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead)
    {
        if(!pHead)
            return nullptr;
        
        RandomListNode* m = new RandomListNode(0);
        m->next = pHead;
        
        while(pHead)
        {
            RandomListNode* p = new RandomListNode(pHead->label);
            p->next = pHead->next;
            pHead->next = p;
            pHead = p->next;
        }
        
        RandomListNode* cache = m->next;
        while(cache)
        {
            if(cache->random)
                cache->next->random = cache->random->next;
            cache = cache->next->next;
        }
        
        RandomListNode* cache_2 = m;
        RandomListNode* cache_3 = m->next;
        while(cache_2->next)
        {
            cache_2->next = cache_2->next->next;
            cache_2 = cache_2->next;
            
            cache_3->next = cache_3->next->next;
            cache_3 = cache_3->next;
        }
        
        return m->next;
    }
};

注:本题分三步走,第一步是复制链表,复制每一个结点为它的下一结点,例如ABCD就复制为AABBCCDD,第二步是复制random结点,第三步是断开连接(这里连接要全部断开,否则要报错)。

 

36、二叉搜索树与双向链表

题目描述:

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        if(!pRootOfTree)
            return nullptr;
        
        TreeNode* temp = Con(pRootOfTree, pRootOfTree);
        while(temp->left)
            temp = temp->left;
        return temp;
    }
    
    TreeNode* Con(TreeNode* pRootOfTree, TreeNode* cur)
    {
        if(!pRootOfTree)
            return nullptr;
        
        TreeNode* temp = Con(pRootOfTree->left,pRootOfTree);
        pRootOfTree->left = temp;
        if(temp)
            temp->right = pRootOfTree;
        
        temp = Con(pRootOfTree->right,pRootOfTree);
        if(temp)
            temp->left = pRootOfTree;
        pRootOfTree->right = temp;
        
        
        if(pRootOfTree == cur->right)
            while(pRootOfTree->left)
                pRootOfTree = pRootOfTree->left;
        else
            while(pRootOfTree->right)
                pRootOfTree = pRootOfTree->right;
        
        return pRootOfTree;
    }
};

注:其实就是中序遍历整个树,每次把它左子树的最右节点的right=root,它左子树的最左节点的left=root,这里需要注意的就是,当这个子树是父节点的左子树时它需要递归到最右节点,当这个子树是父节点的右子树时它需要递归到最左节点,所以就有了如上代码。

 

37、序列化二叉树

题目描述:

请实现两个函数,分别用来序列化和反序列化二叉树

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    char* Serialize(TreeNode *root) {
        
        if(!root)
            return nullptr;
        
        string s = "";
        Encode(root, s);
        
        char *str = new char[s.size()+1];
        strcpy(str, s.c_str());
        return str;
    }
    
    void Encode(TreeNode *root, string &s)
    {
        if(!root)
        {
            s += "#,";
            return;
        }
        
        s += to_string(root->val);
        s.push_back(',');
        
        Encode(root->left, s);
        Encode(root->right, s);
    }
    
    TreeNode* Deserialize(char *str) {
        
        if(!str)
            return nullptr;
        
        string s(str);
        return  Decode(s);
    }
    
    TreeNode* Decode(string &s)
    {
        if(s.size()==0)
            return nullptr;
        
        if(s[0]=='#')
        {
            s = s.substr(2);
            return nullptr;
        }
        
        TreeNode* root = new TreeNode(stoi(s));
        
        s = s.substr(s.find_first_of(',')+1);
        
        root->left = Decode(s);
        root->right = Decode(s);
        
        return root;
    }
};

注:https://blog.csdn.net/u011475210/article/details/78889876

 

38、字符串的排列

题目描述:

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

输入描述:

输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
class Solution {
public:
    vector Permutation(string str) {
        
        if(str.size()==0)
            return {};
        
        vector output;
        sort(str.begin(), str.end());
        Permutation(output, str, 0);
        return output;
    }
    
    void Permutation(vector &output, string str, int k)
    {
        if(k == str.size())
        {
            output.push_back(str);
            return ;
        }
        
        for(int i=k;i

注:将每个字符分别与第一个字符交换,然后递归即可,同时需要考虑去重。

 

39、数组中出现次数超过一半的数字

题目描述:

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

class Solution {
public:
    int MoreThanHalfNum_Solution(vector numbers) {
        
        if(numbers.size()==0)
            return 0;
        
        int cache = numbers[0],num = 1;
        
        for(int i=1;inumbers.size()/2?cache:0;
    }
};

注:本题如果允许用额外空间可以使用hash表,但是最优解是空间复杂度为O(1),时间复杂度为O(n)。

 

40、最小的K个数

题目描述:

输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。

class Solution {
public:
    vector GetLeastNumbers_Solution(vector input, int k) {
        
        if(input.size()==0 || input.size() output;
        
        int index = FindPrivot(input, 0, input.size()-1);
        while(index != k-1)
        {
            if(index &input, int left, int right)
    {
        if(left == right)
            return left;
        
        int privot = input[right], index = left;
        
        for(int i=left;i

注:利用快速排序中patition的思想,通过寻找主元的位置来确定前K个数。

 

41、数据流中的中位数

题目描述:

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

class Solution {
private:
    vector maximum, minimum;
    
public:
    void Insert(int num)
    {
        int sum = maximum.size() + minimum.size();
        
        if(sum&1)
        {
            if(minimum.size()>0 && num>minimum[0])
            {
                minimum.push_back(num);
                push_heap(minimum.begin(), minimum.end(), greater());
                
                num = minimum[0];
                
                pop_heap(minimum.begin(), minimum.end(), greater());
                minimum.pop_back();
            }
            maximum.push_back(num);
            push_heap(maximum.begin(), maximum.end(), less());
        }
        else
        {
            if(maximum.size()>0 && num());
                
                num = maximum[0];
                
                pop_heap(maximum.begin(), maximum.end(), less());
                maximum.push_back(num);
            }
            minimum.push_back(num);
            push_heap(minimum.begin(), minimum.end(), greater());
        }
    }
    
    double GetMedian()
    { 
        int sum = maximum.size() + minimum.size();
        
        if(sum&1)
            return double(minimum[0]);
        else return double((minimum[0]+maximum[0]))/2;
    }

};

注:首先要保证数据平均分配到两个堆中,因此两个堆中的数据的数目之差不能为1。为了实现平均分配,可以在数据的总数目是偶数的情况下把新数据插入最小堆,否则插入最大堆。还要保证最大堆的所有数都大于最小堆,因此按照前面的分配规则,会把新的数据插入最小堆。如果此时插入最小堆的数据大于最大堆的最小值,那么它就不能成为最小堆的数据,因此我们需要把这个新数据先插入最大堆,然后取出最大堆里最小值,把最小值插入最小堆中,以此来满足我们的规则——最小堆中所有数字都大于最大堆的数字。同理可插入新数据进入最大堆。
 

42、连续子数组的最大和

题目描述:

HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)

class Solution {
public:
    int FindGreatestSumOfSubArray(vector array) {
        
        if(array.size()==0)
            return 0;
        
        int maximum = INT_MIN, temp = 0;
        
        for(int i=0;i

注:使用temp记录增量和,当temp<0时代表无论之后的序列为何值,加上之前的序列都会变小,所以当temp<0时令temp=0。

 

43、整数中1出现的次数(从1到n整数中1出现的次数)

题目描述:

求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

 class Solution {
    public:
     int NumberOf1Between1AndN_Solution(int n) {
        
        int count = 0;//1的个数
        int i = 1;//当前位
        int current = 0,after = 0,before = 0;
        while((n/i)!= 0){           
            current = (n/i)%10; //高位数字
            before = n/(i*10); //当前位数字
            after = n-(n/i)*i; //低位数字
            //如果为0,出现1的次数由高位决定,等于高位数字 * 当前位数
            if (current == 0)
                count += before*i;
            //如果为1,出现1的次数由高位和低位决定,高位*当前位+低位+1
            else if(current == 1)
                count += before * i + after + 1;
            //如果大于1,出现1的次数由高位决定,//(高位数字+1)* 当前位数
            else{
                count += (before + 1) * i;
            }    
            //前移一位
            i = i*10;
        }
        return count;
    }
};

注: 设N = abcde ,其中abcde分别为十进制中各位上的数字。 如果要计算百位上1出现的次数,它要受到3方面的影响:百位上的数字,百位以下(低位)的数字,百位以上(高位)的数字。 ① 如果百位上数字为0,百位上可能出现1的次数由更高位决定。比如:12013,则可以知道百位出现1的情况可能是:100~199,1100~1199,2100~2199,,...,11100~11199,一共1200个。可以看出是由更高位数字(12)决定,并且等于更高位数字(12)乘以 当前位数(100)。 ② 如果百位上数字为1,百位上可能出现1的次数不仅受更高位影响还受低位影响。比如:12113,则可以知道百位受高位影响出现的情况是:100~199,1100~1199,2100~2199,,....,11100~11199,一共1200个。和上面情况一样,并且等于更高位数字(12)乘以 当前位数(100)。但同时它还受低位影响,百位出现1的情况是:12100~12113,一共114个,等于低位数字(113)+1。 ③ 如果百位上数字大于1(2~9),则百位上出现1的情况仅由更高位决定,比如12213,则百位出现1的情况是:100~199,1100~1199,2100~2199,...,11100~11199,12100~12199,一共有1300个,并且等于更高位数字+1(12+1)乘以当前位数(100)。

https://www.nowcoder.com/profile/3371548/codeBookDetail?submissionId=16319486

 

44、数字序列中的某一位的数字

题目描述:Leetcode:400. 第N个数字

在无限的整数序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...中找到第 个数字。

class Solution {
public:
    int findNthDigit(int n) {
        
        if(n<10)
            return n;
        
        long base = 1, multi = 1;

        while(n>9*base*multi)
        {
            n -= 9*base*multi;
            base *= 10;
            ++multi;
        }
        
        base/=10;
        
        int firstnum = pow(10, multi-1);
        int ptr = n/multi;
        int rest = n%multi;

        int num = firstnum + ptr - 1;
        
        if(rest)
        {
            ++num;
            for(int i=0;i

注:https://blog.csdn.net/kk55guang2/article/details/86179021 跟我的思想一样,解释的很清晰

 

45、把数组排成最小的数

题目描述:

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

class Solution {
public:
    string PrintMinNumber(vector numbers) {
        
        if(numbers.size()==0)
            return {};
        string s;
        sort(numbers.begin(), numbers.end(), compare);
        
        for(int i=0;i

注:两个数组合起来比较。

 

46、把数字翻译成字符串

题目描述:leetcode:91. 解码方法

一条包含字母 A-Z 的消息通过以下方式进行了编码:

'A' -> 1,'B' -> 2,...'Z' -> 26
给定一个只包含数字的非空字符串,请计算解码方法的总数。

class Solution {
public:
    int numDecodings(string s) {
        if (s.empty() || (s.size() > 1 && s[0] == '0')) return 0;
        vector dp(s.size() + 1, 0);
        dp[0] = 1;
        for (int i = 1; i < dp.size(); ++i) {
            dp[i] = (s[i - 1] == '0') ? 0 : dp[i - 1];
            if (i > 1 && (s[i - 2] == '1' || (s[i - 2] == '2' && s[i - 1] <= '6'))) {
                dp[i] += dp[i - 2];
            }
        }
        return dp.back();
    }
};

这是典型的动态规划问题
dp[i]表示前i个字符的解码方式。那么考虑加进来的第i个字符,如果i个字符可以自己构成一个信息,也就第i个不等于0,那么dp[i] = dp[i-1],如果i和i-1合在一起还能表示一个信息,那么这时,dp[i] = dp[i-2]
所以考虑上面的情况,我们可以分析得出,当第i个字符不等于0的时候,最少dp[i] = dp[i-1],如果i和i-1还能构成一个信息,也就是在11-19到21-26之间的时候,自然dp[i] = dp[i-1]+ dp[i-2].

https://blog.csdn.net/dongyanwen6036/article/details/86559178 , 

https://www.cnblogs.com/ariel-dreamland/p/9159520.html

 

47、礼物的最大价值

题目描述:Leetcode64:最小路径和

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

class Solution {
public:
    int minPathSum(vector>& grid) {
        
        if(grid.size()==0 || grid[0].size()==0)
            return 0;
        
        for(int i=1;i

注:动态规划。

 

48、最长不含重复字符的子字符串

题目描述:Leetcode3:无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        
        if(s.size()==0)
            return 0;
        
        int maximum = 0, temp = 0, left = 0;
        map hash;
        
        for(int i=0;i

注:相当于运用双指针法计算长度。

 

49、丑数

题目描述:

把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        
        if(index<=0)
            return 0;
        
        vector cache(index,1);
        
        int Ugly_2 = 2, Ugly_3 = 3, Ugly_5 = 5;
        int Uglyptr_2 = 0, Uglyptr_3 = 0, Uglyptr_5 = 0;
        int ptr = 1;
        
        while(ptr

注:使用空间换时间的方法,我们不需要挨个判断丑数,只需要找2/3/5他们三个的乘积从小到大排列就可以了,定义三个指针和一个数组记录从小到大的乘积(也就是丑数)即可。

 

50、第一个只出现一次的字符

题目一描述:第一个只出现一次的字符

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).

class Solution {
public:
    int FirstNotRepeatingChar(string str) {
        
        map hash;
        
        for(int i=0;i

注:使用空间换时间,定义哈希表遍历一次,然后再验证一次即可。

题目二描述:字符流中第一个不重复的字符

请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。如果当前字符流没有存在出现一次的字符,返回#字符。

class Solution
{
private:
    string str;
    map hash;
public:
  //Insert one char from stringstream
    void Insert(char ch)
    {
        if(hash.find(ch)==hash.end())
            hash[ch] = 1;
        else hash[ch]++;
        
        str.push_back(ch);
    }
  //return the first appearence once char in current stringstream
    char FirstAppearingOnce()
    {
        for(char ch :str)
            if(hash[ch]==1)
                return ch;
        return '#';
    }

};

注:与题目一思想一致。

 

51、数组中的逆序对

题目描述:

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

输入描述:题目保证输入的数组中没有的相同的数字

数据范围:对于%50的数据,size<=10^4;对于%75的数据,size<=10^5;对于%100的数据,size<=2*10^

class Solution {
public:
    int InversePairs(vector data) {
        
        if(data.size()==0)
            return 0;
        
        long long num = 0;
        MergeSort(data, 0, data.size()-1, num);
        
        return num%1000000007;
    }
    
    void MergeSort(vector& data, int left, int right, long long &num)
    {
        if(left &data, int left, int mid, int right, long long &num)
    {
        if(right>left)
        {
            vector cache(right-left+1, 0);
            int p = mid, q = right, r = right-left;
            
            while(p>=left || q>=mid+1)
            {
                int a = INT_MIN, b = INT_MIN;
                
                if(p>=left)
                    a = data[p];
                if(q>=mid+1)
                    b = data[q];
                
                if(b>a)
                {
                    cache[r] = b;
                    --r;
                    --q;
                }
                else
                {
                    cache[r] = a;
                    --r;
                    --p;
                    if(b!=INT_MIN)
                        num += q-mid;
                }
            }
            
            for(int i=0;i

注:利用归并排序的思想找逆序对即可,需要注意num得设置为longlong类型。

 

52、两个链表的第一个公共结点

题目描述:

输入两个链表,找出它们的第一个公共结点。

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        
        stack s1,s2;
        
        while(pHead1)
        {
            s1.push(pHead1);
            pHead1 = pHead1->next;
        }
        
        while(pHead2)
        {
            s2.push(pHead2);
            pHead2 = pHead2->next;
        }
        
        ListNode* p = nullptr;
        
        while(!s1.empty() && !s2.empty())
        {
            if(s1.top() == s2.top())
            {
                p = s1.top();
                s1.pop();
                s2.pop();
            }
            else break;
        }
        return p;
    }
};

注:将两个链表全部压入栈,从尾结点开始对比,如果不同则输出后一个节点。还有一种是先计算链表的长度,然后使用快慢指针在链表上,快指针比慢指针先走了两链表长度之差。

 

53、数字在排序数组中出现的次数

题目描述:

统计一个数字在排序数组中出现的次数。

class Solution {
public:
    int GetNumberOfK(vector data ,int k) {
        
        if(data.size()==0)
            return 0;
        
        int left = 0, right = data.size();
        
        while(left=data.size() || data[left]!=k)
            return 0;
        
        int first = left;
        left = 0, right = data.size();
        
        while(leftk)
                right = mid;
            else left = mid + 1;
        }
        int last = left;
        return last - first;
    }
};

注:应用二分查找法,找到第一个出现和最后一个出现的索引,然后相减即可。

 

54、二叉搜索树的第k个结点

题目描述:

给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8)    中,按结点数值大小顺序第三小结点的值为4。

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    TreeNode* KthNode(TreeNode* pRoot, int k)
    {
        if(k<=0 || !pRoot)
            return nullptr;
        
        vector inorder;
        Inorder(inorder, pRoot);
        
        if(k>inorder.size())
            return nullptr;
        
        int target = inorder[k-1];
        
        return FindTargetNode(pRoot, target);
        
    }
    
    TreeNode* FindTargetNode(TreeNode* pRoot, int target)
    {
        if(pRoot->val == target)
            return pRoot;
        else if(pRoot->val>target)
            return FindTargetNode(pRoot->left, target);
        else return FindTargetNode(pRoot->right, target);
    }
    
    void Inorder(vector &inorder, TreeNode* pRoot)
    {
        if(!pRoot)
            return ;
        
        Inorder(inorder, pRoot->left);
        inorder.push_back(pRoot->val);
        Inorder(inorder, pRoot->right);
    }
};

注:中序遍历即可。

 

55、二叉树的深度

题目一描述:二叉树的深度

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    int TreeDepth(TreeNode* pRoot)
    {
        if(!pRoot)
            return 0;
        
        return 1 + max(TreeDepth(pRoot->left), TreeDepth(pRoot->right));
    }
};

注:递归求解即可。

题目二描述:平衡二叉树

输入一棵二叉树,判断该二叉树是否是平衡二叉树。

class Solution {
public:
    bool IsBalanced_Solution(TreeNode* pRoot) {
        
        int depth = 0;
        
        return IsBalanced(pRoot,depth); 
    }
    
    bool IsBalanced(TreeNode* pRoot, int &depth) 
    {
        if(!pRoot)
            return true;
        depth++;
        int leftdepth = depth, rightdepth = depth;
        
        bool left = IsBalanced(pRoot->left, leftdepth); 
        bool right = IsBalanced(pRoot->right, rightdepth); 
        
        depth = max(leftdepth, rightdepth);
        return left && right && (abs(leftdepth-rightdepth)<=1);
    }
};

注:本题递归求解会导致大量的重复子问题,解决方法是从下至上的遍历。

 

56、数组中只出现一次的数字

题目描述:

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

class Solution {
public:
    void FindNumsAppearOnce(vector data,int* num1,int *num2) {
        
        if(data.size()==0)
            return ;
        
        int cache = data[0];
        for(int i=1;i>1;
            bit++;
        }
        
        *num1 = 0, *num2 = 0;
        for(int i=0;i>bit;
        return (num & 1);
    }
};

注:如果面试时没思路直接就用哈希表。

 

 

57、和为S的数字

题目一描述:和为S的两个数字

输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

class Solution {
public:
    vector FindNumbersWithSum(vector array,int sum) {
        
        int left = 0, right = array.size()-1;
        int multyply = INT_MAX,num1,num2;
        
        while(leftsum)
                --right;
            else
            {
                if(array[left]*array[right]

注:本题的数组是连续的,所以是典型的双指针算法应用场景,分别设定左右指针在数组的首尾,两数之和大于target,则right指针自减,反之则left指针自加,由于本题还需要求最小的成绩,所以还需要引入哨兵。本题测试用例不考虑乘积越界。

题目二描述:和为S的连续正数序列

小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!

class Solution {
public:
    vector > FindContinuousSequence(int sum) {
        
        vector > output;
        vector cache;
        int left = 1, cumsum = 0;
        
        for(int i=1;isum)
            {
                cumsum -= left;
                left ++;
            }
            
            if(cumsum == sum)
            {
                for(int j=left;j<=i;j++)
                    cache.push_back(j);
                output.push_back(cache);
                cache.clear();
            }
        }
        return output;
    }
};

注:本题的思想与题目一完全一致。

 

58、翻转单词顺序列

题目一描述:翻转单词顺序列

牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?

class Solution {
public:
    string ReverseSentence(string str) {
        
        if(str.size()==0)
            return str;
        
        int start = 0;
        
        for(int i=0;i

注:先翻转每一个单词,然后再翻转整个字符串。

题目二描述:左旋转字符串

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

class Solution {
public:
    string LeftRotateString(string str, int n) {
        
        if(str.size()==0)
            return str;
        int size = str.size();
        n %= size;
        if(n==0)
            return str;
        
        reverse(str.begin(), str.begin()+n);
        reverse(str.begin()+n, str.end());
        reverse(str.begin(), str.end());
        return str;
    }
};

注:本题的思想与题目一完全一致,需要注意当n大于等于str.size()时,要求余。

 

59、队列的最大值

题目一描述:滑动窗口的最大值

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

class Solution {
public:
    vector maxInWindows(const vector& num, unsigned int size)
    {
        if(num.size()==0 || size > num.size() || size==0)
            return {};
        
        deque q;
        vector output;
        
        for(int i=0;i

注:维护一个双端队列。

 

60、n个骰子的点数

 

注:略。

 

61、扑克牌顺子

题目描述:

LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)...他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子.....LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。

class Solution {
public:
    bool IsContinuous( vector numbers ) {
        
        if(numbers.size()==0)
            return false;
        
        sort(numbers.begin(), numbers.end());
        
        int left = 0;
        for(int i=numbers.size()-1;i>=left;i--)
        {
            if(i-1>=0 && numbers[i-1]!=0 && (numbers[i]-numbers[i-1]>1 || numbers[i]-numbers[i-1]==0))
            {
                int gap = numbers[i]-numbers[i-1]-1;
                while(numbers[left]==0 && gap>0)
                {
                    left++;
                    gap--;
                }
                if(gap!=0)
                    return false;
            }
        }
        return true;
    }
};

注:先将数组进行排序,然后从后往前遍历,看看0的数量是不是能填补空缺。

 

62、孩子们的游戏(圆圈中最后剩下的数)

题目描述:

每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

class Solution {
public:
    int LastRemaining_Solution(int n, int m)
    {
        
        if(n<1 || m<1)
            return -1;
        
        list q;
        
        for(int i=0;i1)
        {
            for(int i=1;i

注:使用list模拟环形链表。

 

 

63、股票的最大利润

题目描述:(Leetcode121. 买卖股票的最佳时机)

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。注意你不能在买入股票前卖出股票。

class Solution {
public:
    int maxProfit(vector& prices) {
        
        if(prices.size()<2)
            return 0;
        
        int temp = 0, maximum = 0;
            
        for(int i=1;i

注:与连续子数组最大和的思想完全一致,不过本题内使用的是后一项减去前一项的差值。

 

64、求1+2+3+...+n

题目描述:

求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

class Solution {
public:
    int Sum_Solution(int n) {
        
        int sum = n;
        bool flag = (n!=0) && (sum += Sum_Solution(n-1));
        return sum;
    }
};

注:利用短路特性来做递归条件。

 

65、不用加减乘除做加法

题目描述:

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

class Solution {
public:
    int Add(int num1, int num2)
    {
        int sum, carry;
        
        do
        {
            sum = num1^num2;
            carry = (num1 & num2)<<1;
            
            num1 = carry;
            num2 = sum;
        }
        while(num1!=0);
        
        return num2;
    }
};

注:采用三步走的方式,首先第一步用异或相加但不进位,第二步求与左移一位算进位,第三步相加,循环即可。

 

66、构建乘积数组

题目描述:

给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。

class Solution {
public:
    vector multiply(const vector& A) {
        
        if(A.size()==0)
            return {};
        
        vector B(A.size(), 1);
        
        for(int i=1;i=0;i--)
        {
            temp *= A[i+1];
             B[i] *= temp;
        }
        return B;
    }
};

注:分成两段来考虑,每一段都是增量乘积。

 

67、把字符串转换成整数

题目描述:

将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能,但是string不符合数字要求时返回0),要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。

输入描述:

输入一个字符串,包括数字字母符号,可以为空

输出描述:

如果是合法的数值表达则返回该数字,否则返回0
class Solution {
public:
    int StrToInt(string str) {
        
        bool flag = true;
        int sign = 1;
        int num = 0;
        
        for(int i=0;i='0' && str[i]<='9')
            {
                num = num * 10 + str[i] - '0';
                flag = false;
            }
            else return 0;
        }
        if(sign == -1)
            return -1 * num;
        return num;
    }
};

注:注意各种边界条件即可,本题中没有考虑num越界以及正负号非法的测试用例。

 

68、树中两个节点的最低公共祖先

题目描述:

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == NULL) {
            return NULL;
        }
        if(root == p || root == q) {
            return root;
        }
        TreeNode* left = lowestCommonAncestor(root->left, p, q);
        TreeNode* right = lowestCommonAncestor(root->right, p, q);
        if(left != NULL && right != NULL) {
            return root;
        } else if(left != NULL) {
            return left;
        } else {
            return right;
        }
        
    }
};

注:

如果root为null或者p、q中有一个就是root,直接返回root即可。

在左子树中寻找p、q的最近公共祖先,记录所得结果为left。

在右子树中寻找p、q的最近公共祖先,记录所得结果为right。

如果left为null,直接返回right即可。

如果right为null,直接返回left即可。

如果left和right均不为null,我们应该直接返回root。

时间复杂度是O(n),其中n为二叉树中的节点数。空间复杂度是O(h),其中h为二叉树的高度。

https://blog.csdn.net/qq_41231926/article/details/87092480

你可能感兴趣的:(程序)