剑指offer 一一 一篇文章能搞懂剑指offer的笔试题(C/C++)?

文章目录

1、二维数组中的查找

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

解决代码:

class Solution {
    
    
public:
    bool Find(int target, vector<vector<int> > array) {
    
    
        int rows = array.size();
        int cols = array[0].size();
        int i=rows-1,j=0;//左下角元素坐标
        while(i>=0 && j<cols){
    
    //使其不超出数组范围
            if(target<array[i][j])
                i--;//查找的元素较少,往上找
            else if(target>array[i][j])
                j++;//查找元素较大,往右找
            else
                return true;//找到
        }
        return false;
    }
};

2、替换空格

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

解决代码:

class Solution {
    
    
public:
	void replaceSpace(char *str,int length) {
    
    
        int SpaceCount = 0;
        //计算空格数量
        for(int i = 0; i < length; i++){
    
    
            if(str[i] == ' '){
    
    
                SpaceCount ++;
            }
        }
        
        for(int i = length - 1; i >= 0; i--){
    
    
            if(str[i] != ' '){
    
    
                str[i+SpaceCount*2] = str[i];
            }
            else{
    
    
                SpaceCount --;
                str[i+SpaceCount*2] = '%';
                str[i+SpaceCount*2+1] = '2';
                str[i+SpaceCount*2+2] = '0';
            }
        }
    }
};

3、从尾到头打印链表

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

解决代码:

/**
*  struct ListNode {
*        int val;
*        struct ListNode *next;
*        ListNode(int x) :
*              val(x), next(NULL) {
*        }
*  };
*/
class Solution {
    
    
public:
    vector<int> printListFromTailToHead(ListNode* head) {
    
    
        int count = 0;
        vector<int> v;
        stack<int> stk;
        while(head != NULL){
    
    
            stk.push((*head).val);
            head = head->next;
        }
        int stk_length = stk.size();
        for(int i = 0; i < stk_length; i++){
    
    
            v.push_back(stk.top());
            stk.pop();
        }
        return v;
    }
};

4、用两个栈实现队列

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

解决代码:

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

    int pop() {
    
    
        int res,val;
        if(stack2.size() > 0){
    
    
            val = stack2.top();
            stack2.pop();
        }
        else if(stack1.size() > 0){
    
    
            while(stack1.size() > 0){
    
    
                res = stack1.top();
                stack2.push(res);
                stack1.pop();
            }
            val = stack2.top();
            stack2.pop();
        }
        return val;
    }

private:
    stack<int> stack1;
    stack<int> stack2;
};

5、旋转数组的最小数字

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

解决代码:

class Solution {
    
    
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
    
    
        int minNumber;
        for(int i = 0; i < rotateArray.size(); i++){
    
    
            for(int j = 1; j < rotateArray.size(); j++){
    
    
                if(rotateArray[j] < rotateArray[i]){
    
    
                    int temp;
                    temp = rotateArray[i];
                    rotateArray[i] = rotateArray[j];
                    rotateArray[j] = temp;
                }
            }
        }
        minNumber = rotateArray[0];
        return minNumber;
    }
};

6、递归循环(斐波那契数列)

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

解决思路:
通项公式为:
f(n) = f(n - 1) + f(n - 2)   (n > 2)
f(0) = 0; f(1) = 1;

解决代码:

class Solution {
    
    
public:
    int Fibonacci(int n) {
    
    
        int n1 = 0,n2 = 1,n3 = 0;
        if(n <= 0)
            return 0;
        if(n == 1)
            return 1;
        else{
    
    
            for(int i = 2; i <= n; i++){
    
    
                n3 = n1 + n2;
                n1 = n2;
                n2 = n3;
            }
            return n3;
        }
    }
};

7、递归循环(跳台阶)

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

解决思路:
当n=1时,1种方法
当n=2时,2种方法
当n=3时,3种方法
当n=4时,5种方法
···· ·····
当n=n时,只有f(n-1)+f(n-2)种跳法,符合斐波那契数列条件

解决代码:

class Solution {
    
    
public:
    int jumpFloor(int number) {
    
    
        int step;
        int a = 1,b = 2;
        if(number == 1)
            return 1;
        if(number == 2)
            return 2;
        step = a + b;
        for(int i = 3; i <= number; i++){
    
    
            step = a + b;
            a = b;
            b = step;
        }
        return step;
    }
};

8、递归循环(变态跳台阶)

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

解决思路(来源于别人):
链接:源地址

1)这里的f(n) 代表的是n个台阶有一次1,2,…n阶的 跳法数。
2)n = 1时,只有1种跳法,f(1) = 1

  1. n = 2时,会有两个跳得方式,一次1阶或者2阶,这回归到了问题(1) ,f(2) = f(2-1) + f(2-2)

  2. n = 3时,会有三种跳得方式,1阶、2阶、3阶,

    那么就是第一次跳出1阶后面剩下:f(3-1);第一次跳出2阶,剩下f(3-2);第一次3阶,那么剩下f(3-3)

    因此结论是f(3) = f(3-1)+f(3-2)+f(3-3)

  3. n = n时,会有n中跳的方式,1阶、2阶…n阶,得出结论:

    f(n) = f(n-1)+f(n-2)+…+f(n-(n-1)) + f(n-n) => f(0) + f(1) + f(2) + f(3) + … + f(n-1)

  4. 由以上已经是一种结论,但是为了简单,我们可以继续简化:

    f(n-1) = f(0) + f(1)+f(2)+f(3) + … + f((n-1)-1) = f(0) + f(1) + f(2) + f(3) + … + f(n-2)

    f(n) = f(0) + f(1) + f(2) + f(3) + … + f(n-2) + f(n-1) = f(n-1) + f(n-1)

    可以得出:

    f(n) = 2*f(n-1)

  5. 得出最终结论,在n阶台阶,一次有1、2、…n阶的跳的方式时,总得跳法为:

                 | 1       ,(n=0 ) 
     f(n) =		 | 1       ,(n=1 )
    			 | 2*f(n-1),(n>=2)
    

解决代码:

class Solution {
    
    
public:
    int jumpFloorII(int number) {
    
    
        int step;
        if(number <= 0)
            return 0;
        else if(number == 1)
            return 1;
        else{
    
    
            step = 2 * jumpFloorII(number - 1);
            return step;
        }
    }
};

9、递归循环(矩阵覆盖)

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

比如n=3时,2*3的矩形块有3种覆盖方法:
在这里插入图片描述

解决思路:
当n=1时,1种方法
当n=2时,2种方法
当n=3时,3种方法
当n=4时,5种方法
当n=5时,8种方法
当n=6时,13种方法
···· ·····
当n=n时,只有f(n-1)+f(n-2)种跳法,符合斐波那契数列条件

解决代码:

class Solution {
    
    
public:
    int rectCover(int number) {
    
    
        int a = 1,b = 2, c = 0;
        if(number <= 0){
    
    
            return 0;
        }
        else if(number > 0 && number < 3){
    
    
            return number;
        }
        for(int i = 3; i <= number; i++){
    
    
            c = a + b;
            a = b;
            b = c;
        }
        return c;
    }
};

10、位操作(二进制中1的个数)

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

解决思路:
当 n 为正数时,好理解,反复对2取余,不为0,1的个数就加1
但是当 n 为负数时,需要对 n 进行转化,将 n 最高位的符号位1变成0,也就是n & 0x7FFFFFFF,这样就把负数转化成正数了,唯一差别就是最高位由1变成0,因为少了一个1,所以 1 的个数加1。然后按照正数进行处理就行了。

解决代码:

class Solution {
    
    
public:
     int  NumberOf1(int n) {
    
    
         int count = 0;
         if(n < 0){
    
    
             n &= 0x7FFFFFFF;
             count ++;
         }

         while(n != 0){
    
    
             if( n % 2 != 0){
    
    
                 count ++;
             }
             n /= 2;
         }
         return count;
     }
};

11、数值的整数次方

题目: 给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。保证base和exponent不同时为0。

解决代码:

class Solution {
    
    
public:
    double Power(double base, int exponent) {
    
    
        double ret = 1;
        if(base == 0)
            return 0;
        else if(exponent == 0)
            return 1;
        else if(exponent > 0){
    
    
            for(int i = 0; i < exponent; i++){
    
    
                ret = (double)ret * base;
            }
        }
        else{
    
    
            for(int i = 0; i < -exponent; i++){
    
    
                ret = (double)ret * (1/base);
            }
        } 
        return ret;
    }
};

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

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

解决思路:
1.从前往后遍历,第一个元素如果是偶数,记住这个 偶数的角标位。如果不是偶数,啥也不干。
2.当遇到偶数的时候,判断这是不是第一个偶数,如果是 记住角标位。
3.当遇到奇数的时候,判断其前面有没有偶数,如果有,则将前面的连续的多个偶数,顺序往后移动一位,然后将奇数插入第一个偶数位置。

解决代码:

class Solution {
    
    
public:
    void reOrderArray(vector<int> &array) {
    
    
        int lengthA = array.size();
        int i = 0,index = -1;
        while(i < lengthA){
    
    
            if((array[i] % 2) != 0){
    
    
                if(index >= 0){
    
    
                    //和index位置的值交换
                    int temp = array[i];
                    for(int j = i; j > index; j--){
    
    
                        array[j] = array[j-1];
                    }
                    array[index] = temp;
                    index++;
                }
                 i++;
                continue;
            }
            else{
    
    
                //从左边开始的第一个偶数的位置,如果第一个元素就是偶数,这个临界值一定要考虑到。
                //如果第一个元素不是偶数,那当遍历到第一个偶数的时候,开始记住其角标。
                if(i == 0)
                    index = 0;
                if(index < 0)
                    index = i;
                i++;
                continue;
            }
        }
    }
};

13、链表中倒数第k个节点

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

解决思路:
(1)求得链表的总长度
(2)正序找到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) {
    
    
        int len_list = 1;
        int count = 1;
        int pos = 0;
        ListNode* plist = pListHead;
        while(plist){
    
    
            plist = plist->next;
            len_list ++;
        }
        if(k >= len_list){
    
    
            return plist;
        }
        pos = len_list - k + 1;
        plist = pListHead;
        while(plist && count < pos-1){
    
    
            plist = plist->next;
            count++;
        }
        return plist;
    }
};

14、反转链表

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

解决代码:

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
    
    
public:
    ListNode* ReverseList(ListNode* pHead) {
    
    
        ListNode* pNext = NULL;
        ListNode* pPre = NULL;
        if(pHead == NULL){
    
    
            return NULL;
        }
        while(pHead != NULL){
    
    
            //先用pXext保存pHead的下一个节点的信息,保证单链表不会因为失去pHead节点的原pNext节点而就此断裂
            pNext = pHead->next;
            //让pHead从指向pNext变成指向pPre
            pHead->next = pPre;
            //pHead指向pPre后,就继续依次反转下一个节点
            //让pPre,pHead,pNext依次向后移动一个节点,继续下一次的指针反转
            
            pPre = pHead;
            pHead = pNext;
        }
        //如果pHead为NULL的时候,pPre就为最后一个节点了,但是链表已经反转完毕,pPre就是反转后链表的第一个节点
       return pPre;
    }
};

15、合并两个排序的链表

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

解决代码:
(1)递归版本:

/*
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;
        else if(pHead2 == NULL) 
            return pHead1;
        ListNode* pHeadMerge = NULL;
        if(pHead1->val < pHead2->val){
    
    
            pHeadMerge = pHead1;
            pHeadMerge->next = Merge(pHead1->next,pHead2);
        }
        else{
    
    
            pHeadMerge = pHead2;
            pHeadMerge->next = Merge(pHead1,pHead2->next);
        }
        return pHeadMerge;
    }
};

(2)非递归版本

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

16、字符串最后一个单词的长度

题目:计算字符串最后一个单词的长度,单词以空格隔开。

解决代码:

#include <iostream>
#include <string>
using namespace std;
 
void lenLastWord(string s)
{
    
    
    while(getline(cin, s)) {
    
    
        cout << (s.size() - s.rfind(' ') - 1)<< endl;
    }
}
 
int main()
{
    
    
    string s;
    lenLastWord(s);
    return 0;
}

17、顺时针输出矩阵

题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下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<int> printMatrix(vector<vector<int> > matrix) {
    
    
        vector<int> result;
        if(matrix.empty())
            return result;
        //矩阵行数
        int row = matrix.size();
        //矩阵列数
        int col = matrix[0].size();
        //矩阵四角
        int left = 0;
        int right = col - 1;
        int top = 0;
        int botm = row - 1;
        //循环
        while((left <= right)&&(top <= botm)){
    
    
            //第一步
            for(int i = left; i <= right; i++){
    
    
                result.push_back(matrix[top][i]);
            }
            //第二步
            if(top < botm){
    
                               //第二步边界条件
                for(int i = top + 1; i <= botm; i++){
    
    
                    result.push_back(matrix[i][right]);
                }
            }
            //第三步
            if((top < botm) && (left < right)){
    
           //第三步边界条件
                for(int i = right - 1; i >= left; i--){
    
    
                    result.push_back(matrix[botm][i]);
                }
            }
            //第四步
            if(top+1 < botm && left < right){
    
               //第四步边界条件
                for(int i = botm - 1; i >= top + 1; i--){
    
    
                    result.push_back(matrix[i][left]);
                }
            }
            left++;
            right--;
            top++;
            botm--;
        }
        return result;
    }
};

18、包含min函数的栈

题目:定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
注意:保证测试中不会当栈为空的时候,对栈调用pop()或者min()或者top()方法。

解决代码:一开始没有太理解题意,参考别人的代码

class Solution {
    
    
public:
    stack<int> data;  //数据栈
    stack<int> mMin;  //最小栈
    //
    void push(int value) {
    
    
        data.push(value);  //无论怎样,数据栈首先入栈
        //当最小栈为空,或者要入的元素<最小栈栈顶,则给最小栈入栈
        if(mMin.empty() || value<=mMin.top()){
    
    
            mMin.push(value);
        }
        else{
    
                    //大于等于就入最小栈原来栈顶元素
            mMin.push(mMin.top());
        }
    }
    void pop() {
    
    
        //保证两个栈都不为空
        if(!data.empty() && !mMin.empty()){
    
    
            data.pop();
            mMin.pop();
        }
    }
    //最小元素(直接就是最小栈的栈顶)
    int top() {
    
    
        return data.top();
    }
    int min() {
    
    
        return mMin.top();
    }
};

19、栈的压入、弹出序列

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

解决思路:借用一个辅助的栈,遍历压栈顺序,先讲第一个放入栈中,这里是1,然后判断栈顶元素是不是出栈顺序的第一个元素,这里是4,很显然1≠4,所以我们继续压栈,直到相等以后开始出栈,出栈一个元素,则将出栈顺序向后移动一位,直到不相等,这样循环等压栈顺序遍历完成,如果辅助栈还不为空,说明弹出序列不是该栈的弹出顺序。

解决代码:

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

20、复杂链表的复制

题目:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
这个自己没有什么思路,随机这个有点困难,记录一下大神的解题思路,自己也是要搞懂,借鉴的。

解题思路:(分为三步)
在这里插入图片描述
解决代码:

/*
struct RandomListNode {
    int label;
    struct RandomListNode *next, *random;
    RandomListNode(int x) :
            label(x), next(NULL), random(NULL) {
    }
};
*/
class Solution {
    
    
public:
    RandomListNode* Clone(RandomListNode* pHead)
    {
    
    
        /*
        1、复制每个节点,如:复制节点A得到A1,将A1插入节点A后面
        2、遍历链表,A1->random = A->random->next;
        3、将链表拆分成原链表和复制后的链表
    */
       if(!pHead) return NULL;
        RandomListNode *currNode = pHead;
        while(currNode){
    
    
            RandomListNode *node = new RandomListNode(currNode->label);
            node->next = currNode->next;
            currNode->next = node;
            currNode = node->next;
        }
        currNode = pHead;
        while(currNode){
    
    
            RandomListNode *node = currNode->next;
            if(currNode->random){
    
                   
                node->random = currNode->random->next;
            }
            currNode = node->next;
        }
        //拆分
        RandomListNode *pCloneHead = pHead->next;
        RandomListNode *tmp;
        currNode = pHead;
        while(currNode->next){
    
    
            tmp = currNode->next;
            currNode->next =tmp->next;
            currNode = tmp;
        }
        return pCloneHead;
    }
};

21、字符串的排序

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

输入描述:输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母

解决思路:
链接:https://www.nowcoder.com/questionTerminal/fe6b651b66ae47d7acce78ffdd9a96c7

递归算法

 * 对于无重复值的情况
 * 固定第一个字符,递归取得首位后面的各种字符串组合;
 * 再把第一个字符与后面每一个字符交换,并同样递归获得首位后面的字符串组合; *递归的出口,就是只剩一个字符的时候,递归的循环过程,就是从每个子串的第二个字符开始依次与第一个字符交换,然后继续处理子串。
 *
 * 假如有重复值呢?
 * *由于全排列就是从第一个数字起,每个数分别与它后面的数字交换,我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这两个数就不交换了。
 * 例如abb,第一个数与后面两个数交换得bab,bba。然后abb中第二个数和第三个数相同,就不用交换了。
 * 但是对bab,第二个数和第三个数不 同,则需要交换,得到bba。
 * 由于这里的bba和开始第一个数与第三个数交换的结果相同了,因此这个方法不行。
 *
 * 换种思维,对abb,第一个数a与第二个数b交换得到bab,然后考虑第一个数与第三个数交换,此时由于第三个数等于第二个数,
 * 所以第一个数就不再用与第三个数交换了。再考虑bab,它的第二个数与第三个数交换可以解决bba。此时全排列生成完毕!

解决代码:

class Solution {
    
    
public:
    vector<string> result;
    vector<string> Permutation(string str) {
    
    
        if(str.length()==0){
    
    
            return result;
        }
        Permutation1(str,0);
        sort(result.begin(),result.end());
        return result;
    }
    void Permutation1(string str,int begin){
    
    
        if(begin == str.length()){
    
    
            result.push_back(str);
            return ;
        }
        for(int i = begin; str[i]!='\0';i++){
    
    
            if(i!=begin&&str[begin]==str[i]){
    
    
                continue;
			}
            swap(str[begin],str[i]);
            Permutation1(str,begin+1);
            swap(str[begin],str[i]);
        }
    }
};

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

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

解决思路:
先对数组中元素进行排序,如果存在数组中某一个数字大于数组大小一半的话,那么这个数字一定是数组的中位数,反之为0.但是算法时间复杂度偏高,为O(nlogn)。

解决代码:

class Solution {
    
    
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
    
    
        if(numbers.empty())
        	return 0;
        int length = numbers.size();
        sort(numbers.begin(),numbers.end());
        int middle = numbers[length/2];
        int count = 0;
        for(int i = 0; i < length; i++){
    
    
           if(middle == numbers[i])
               count++;
        }
        return count>length/2 ? middle : 0;
    }
};

另一种算法复杂度为O(n)的解法

class Solution {
    
    
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
    
    
        int n = numbers.size();
        if (n == 0) return 0;
         
        int num = numbers[0], count = 1;
        for (int i = 1; i < n; i++) {
    
    
            if (numbers[i] == num) count++;
            else count--;
            if (count == 0) {
    
    
                num = numbers[i];
                count = 1;
            }
        }
        // Verifying
        count = 0;
        for (int i = 0; i < n; i++) {
    
    
            if (numbers[i] == num) count++;
        }
        if (count * 2 > n) return num;
        return 0;
    }
};

23、最小的K个数

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

解决思路:
(1)投机的简单方法,调用已经C++中vector容器中排序的方法,排序完直接输出前K个数
(2)冒泡排序,不过不用全排序,只需要排出前K个就行

解决代码:
(1)投机的简单方法

class Solution {
    
    
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
    
    
        int len = input.size();
        vector<int> result;
        if(len < k)
            return result;
        sort(input.begin(),input.end());
        for(int i = 0; i < k; i++)
            result.push_back(input[i]);
        return result;
    }
};

(2)

class Solution {
    
    
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
    
    
        vector<int> result;
        if (k > input.size()) {
    
    
            return result;
        }
        for (int i = 0; i < k; i++) {
    
    
            for (int j = 0; j < input.size() - i - 1; j++) {
    
    
                if (input[j] < input[j + 1]) {
    
    
                    int temp = input[j];
                    input[j] = input[j + 1];
                    input[j + 1] = temp;
                }
            }
            result.push_back(input[input.size() - i - 1]);
        }
        return result;
    }
};

24、连续子数组的最大和

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

解决代码:

class Solution {
    
    
public:
    int FindGreatestSumOfSubArray(vector<int> array) {
    
    
        if(array.size() == 0){
    
    
            return 0;
        }
        int maxNumArray = INT_MIN;
        int currentNumArray = 0;
        for(int i = 0; i < array.size(); i++){
    
    
            if(currentNumArray < 0){
    
    
                currentNumArray = array[i];
            }
            else{
    
    
                currentNumArray += array[i];
            }
            maxNumArray = max(maxNumArray,currentNumArray);
        }
        return maxNumArray;
    }
};

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

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

解决思路: n的个位、十位、百位、千位…都要进行判断。

解决代码:

class Solution {
    
    
public:
    int NumberOf1Between1AndN_Solution(int n)
    {
    
    
        int count = 0;
        if(n < 1)
            return 0;
        for(int i = 1; i <= n; i++){
    
    
            int temp = i;
            while(temp){
    
    
                if(temp%10 == 1)
                    count++;
                temp /= 10;
            }
        }
        return count;
    }
};

26、把数组排成最小的数

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

解决代码:

class Solution {
    
    
public:
    string PrintMinNumber(vector<int> numbers) {
    
    
        int len = numbers.size();
        if(len == 0) return "";
        sort(numbers.begin(), numbers.end(), cmp);
        string res;
        for(int i = 0; i < len; i++){
    
    
            res += to_string(numbers[i]);
        }
        return res;
    }
    static bool cmp(int a, int b){
    
    
        string A = to_string(a) + to_string(b);
        string B = to_string(b) + to_string(a);
        return A < B;
    }
};

27、丑数

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

解决思路(来自牛客网,自己的代码运行超出内存限制):
链接:https://www.nowcoder.com/questionTerminal/6aa9e04fc3794f68acf8778237ba065b
来源:牛客网

首先从丑数的定义我们知道,一个丑数的因子只有2,3,5,那么丑数p = 2 ^ x * 3 ^ y * 5 ^ z,换句话说一个丑数一定由另一个丑数乘以2或者乘以3或者乘以5得到,那么我们从1开始乘以2,3,5,就得到2,3,5三个丑数,在从这三个丑数出发乘以2,3,5就得到4,6,10,6,9,15,10,15,25九个丑数,我们发现这种方***得到重复的丑数,而且我们题目要求第N个丑数,这样的方法得到的丑数也是无序的。那么我们可以维护三个队列:
(1)丑数数组: 1
乘以2的队列:2
乘以3的队列:3
乘以5的队列:5
选择三个队列头最小的数2加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
(2)丑数数组:1,2
乘以2的队列:4
乘以3的队列:3,6
乘以5的队列:5,10
选择三个队列头最小的数3加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
(3)丑数数组:1,2,3
乘以2的队列:4,6
乘以3的队列:6,9
乘以5的队列:5,10,15
选择三个队列头里最小的数4加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
(4)丑数数组:1,2,3,4
乘以2的队列:6,8
乘以3的队列:6,9,12
乘以5的队列:5,10,15,20
选择三个队列头里最小的数5加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
(5)丑数数组:1,2,3,4,5
乘以2的队列:6,8,10,
乘以3的队列:6,9,12,15
乘以5的队列:10,15,20,25
选择三个队列头里最小的数6加入丑数数组,但我们发现,有两个队列头都为6,所以我们弹出两个队列头,同时将12,18,30放入三个队列;
……………………
疑问:
1.为什么分三个队列?
丑数数组里的数一定是有序的,因为我们是从丑数数组里的数乘以2,3,5选出的最小数,一定比以前未乘以2,3,5大,同时对于三个队列内部,按先后顺序乘以2,3,5分别放入,所以同一个队列内部也是有序的;
2.为什么比较三个队列头部最小的数放入丑数数组?
因为三个队列是有序的,所以取出三个头中最小的,等同于找到了三个队列所有数中最小的。

解决代码:

class Solution {
    
    
public://别人的代码就是精简,惭愧啊,继续学习。
    int GetUglyNumber_Solution(int index) {
    
    
        if (index < 7)return index;
        vector<int> res(index);
        res[0] = 1;
        int t2 = 0, t3 = 0, t5 = 0, i;
        for (i = 1; i < index; ++i)
        {
    
    
            res[i] = min(res[t2] * 2, min(res[t3] * 3, res[t5] * 5));
            if (res[i] == res[t2] * 2)t2++;
            if (res[i] == res[t3] * 3)t3++;
            if (res[i] == res[t5] * 5)t5++;
        }
        return res[index - 1];
    }
};

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

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

解决思路:
步骤一:都是字母,一定在ASCII码范围内,所以就直接定义一直字符数组,遍历整个字符串,当字符串中出现一个字母时候,就将该字符的ASCII位置的 0 加一;
步骤二:再次遍历字符串,直到字符串ASCII码对应的位置为1,则返回坐标,反之,返回0.

解决代码:

class Solution {
    
    
public:
    int FirstNotRepeatingChar(string str) {
    
    
        int len = str.length();
        if(len == 0)
            return -1;
        char czeros[256] = {
    
    0};
        for(int i = 0; i < len; i++){
    
    
            czeros[str[i]]++;
        }
        for(int i = 0; i < len; i++){
    
    
            if(czeros[str[i]] == 1)
                return i;
        }
        return 0;
    }
};

29、数组中的逆序对

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

输入描述:

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

数据范围:

	对于%50的数据,size<=10^4

	对于%75的数据,size<=10^5

	对于%100的数据,size<=2*10^5

示例1:

输入:1,2,3,4,5,6,7,0
输出:7

解决思路:
方法一:暴力循环法,但是这种算法复杂度太大,不推荐,而且牛客网上运行时间过不去
方法一代码:

class Solution {
    
    
public:
    int InversePairs(vector<int> data) {
    
    
        int P = 0;
        for(int i = 0; i < data.size(); i++){
    
    
            for(int j = i + 1; j < data.size(); j++){
    
    
                if(data[i] > data[j]){
    
    
                    P++;
                }
            }
        }
        return P%1000000007;
    }
};

方法二: (此方法来自与大神的分享,原链接为归并排序
分治思想,采用归并排序的思路来处理,如下图,先分后治:
在这里插入图片描述
先把数组分解成两个长度为2的子数组,再把这两个子数组分解成两个长度为1的子数组。接下来一边合并相邻的子数组,一边统计逆序对的数目。在第一对长度为1的子数组{7}、{5}中7>5,因此(7,5)组成一个逆序对。同样在第二对长度为1的子数组{6},{4}中也有逆序对(6,4),由于已经统计了这两对子数组内部的逆序对,因此需要把这两对子数组进行排序,避免在之后的统计过程中重复统计。
在这里插入图片描述
逆序对的总数 = 左边数组中的逆序对的数量 + 右边数组中逆序对的数量 + 左右结合成新的顺序数组时中出现的逆序对的数量

  • 这是一个归并排序的合并过程,主要是考虑合并两个有序序列时,计算逆序对数。
  • 对于两个升序序列,设置两个下标:两个有序序列的末尾。每次比较两个末尾值,如果前末尾大于后末尾值,则有”后序列当前长度“个逆序对;否则不构成逆序对。然后把较大值拷贝到辅助数组的末尾,即最终要将两个有序序列合并到辅助数组并有序。
  • 这样,每次在合并前,先递归地处理左半段、右半段,则左、右半段有序,且左右半段的逆序对数可得到,再计算左右半段合并时逆序对的个数。
    方法二代码为:
class Solution {
    
    
public:
    int InversePairs(vector<int> data) {
    
    
        if(data.size() == 0){
    
    
            return 0;
        }
        // 排序的辅助数组
        vector<int> copy;
        for(int i = 0; i < data.size(); ++i){
    
    
            copy.push_back(data[i]);
        }
        return InversePairsCore(data, copy, 0, data.size() - 1) % 1000000007;
    }
    long InversePairsCore(vector<int> &data, vector<int> &copy, int begin, int end){
    
    
        // 如果指向相同位置,则没有逆序对。
        if(begin == end){
    
    
            copy[begin] = data[end];
            return 0;
        }
        // 求中点
        int mid = (end + begin) >> 1;
        // 使data左半段有序,并返回左半段逆序对的数目
        long leftCount = InversePairsCore(copy, data, begin, mid);
        // 使data右半段有序,并返回右半段逆序对的数目
        long rightCount = InversePairsCore(copy, data, mid + 1, end);
        
        int i = mid; // i初始化为前半段最后一个数字的下标
        int j = end; // j初始化为后半段最后一个数字的下标
        int indexcopy = end; // 辅助数组复制的数组的最后一个数字的下标
        long count = 0; // 计数,逆序对的个数,注意类型
        
        while(i >= begin && j >= mid + 1){
    
    
            if(data[i] > data[j]){
    
    
                copy[indexcopy--] = data[i--];
                count += j - mid;
            }
            else{
    
    
                copy[indexcopy--] = data[j--];
            }
        }
        for(;i >= begin; --i){
    
    
            copy[indexcopy--] = data[i];
        }
        for(;j >= mid + 1; --j){
    
    
            copy[indexcopy--] = data[j];
        }
        return leftCount + rightCount + count;
    }
};

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

题目:输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)

解决思路:
因为是单链表,如果出现公共节点,则两个链表的尾部一定是一样的。

  • 假定 List1长度: a+n List2 长度:b+n, 且 a<b
  • 那么 p1 会先到链表尾部, 这时p2 走到 a+n位置,将p1换成List2头部
  • 接着p2 再走b+n-(n+a) =b-a 步到链表尾部,这时p1也走到List2的b-a位置,还差a步就到可能的第一个公共节点。
  • 将p2 换成 List1头部,p2走a步也到可能的第一个公共节点。如果恰好p1==p2,那么p1就是第一个公共节点。 或者p1和p2一起走n步到达列表尾部,二者没有公共节点,退出循环。 同理a>=b.
  • 时间复杂度O(n+a+b)

解决代码:

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
    
    
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
    
    
        ListNode* p1 = pHead1;
        ListNode* p2 = pHead2;
        int len1 = 0;
        int len2 = 0;
        int diff = 0;
        while(p1 != NULL){
    
    
            p1 = p1->next;
            len1 ++;
        }
        while(p2 != NULL){
    
    
            p2 = p2->next;
            len2 ++;
        }
        if(len1 > len2){
    
    
            diff = len1 - len2;
            p1 = pHead1;
            p2 = pHead2;
        }
        else{
    
    
            diff = len2 - len1;
            p1 = pHead2;
            p2 = pHead1;
        }
        for(int i = 0; i < diff; i++){
    
    
            p1 = p1->next;
        }
        while(p1 != NULL && p2 != NULL){
    
    
            if(p1 == p2){
    
    
                break;
            }
            p1 = p1->next;
            p2 = p2->next;
        }
        return p1;
    }
};

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

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

解题思路:
方法一:暴力遍历,不利用排序数组这个条件直接求,当数字为 k 时,计数加一即可,时间复杂度为O(n)

方法一解决代码:

class Solution {
    
    
public:
    int GetNumberOfK(vector<int> data ,int k) {
    
    
        int len = data.size();
        int count  = 0;
        for(int i = 0; i < len; i++){
    
    
            if(k == data[i]){
    
    
                count ++;
            }
        }
        return count;
    }
};

方法二:

  • 观察数组本身的特性可以发现,排序数组这样做没有充分利用数组的特性,可以使用二分查找,找出数据,然后进行左右进行统计
  • 具体算法设计: 二分查找找到k的所在位置
    在原数组里面分别左右对k的出现次数进行统计

方法二:解决代码

class Solution {
    
    
public:
int BinarySearch(vector<int> data, int low, int high, int k)
{
    
    
    while (low<=high)
    {
    
    
        int m = (high + low) / 2;
        if (data[m] == k)return m;
        else if (data[m] < k) low = m+ 1;
        else high = m - 1;
    }
    return -1;
}
int GetNumberOfK(vector<int> data ,int k) {
    
    
    if(data.size()==0)return 0;
    int len=data.size();
    int KeyIndex=0;
         
    KeyIndex=BinarySearch(data,0,len-1,k);
    if(KeyIndex==-1) return 0;
    int sumber=1;
    int m=KeyIndex-1;
    int n=KeyIndex+1;
       
    while(m>=0&&data[m]==k){
    
    
        m--;sumber++;
    }
    while(n<len&&data[n]==k){
    
    
        n++; sumber++;
    }
    return sumber;
}
};

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

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

解决思路:

  • 首先:位运算中异或的性质:两个相同数字异或=0,一个数和0异或还是它本身。
  • 当只有一个数出现一次时,我们把数组中所有的数,依次异或运算,最后剩下的就是落单的数,因为成对儿出现的都抵消了。
  • 依照这个思路,我们来看两个数(我们假设是AB)出现一次的数组。我们首先还是先异或,剩下的数字肯定是A、B异或的结果,这个结果的二进制中的1,表现的是A和B的不同的位。我们就取第一个1所在的位数,假设是第3位,接着把原数组分成两组,分组标准是第3位是否为1。如此,相同的数肯定在一个组,因为相同数字所有位都相同,而不同的数,肯定不在一组。然后把这两个组按照最开始的思路,依次异或,剩余的两个结果就是这两个只出现一次的数字。

解决代码:

class Solution {
    
    
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int* num2) {
    
    
        if(data.size() < 2)
            return;
          
        int resultExclusiveOR = 0;
        for(int i = 0; i < data.size(); i++){
    
    
            resultExclusiveOR ^= data[i];
        }
          
        unsigned int indexOf1 = FindFirstBitIs1(resultExclusiveOR);
          
        *num1 = *num2 = 0;
        for(int j = 0; j < data.size(); j++){
    
    
            if(IsBit1(data[j], indexOf1))
                *num1 ^= data[j];
            else
                *num2 ^= data[j];
        }
  
    }
      
    unsigned int FindFirstBitIs1(int num){
    
    
        int indexBit = 0;
        while(((num & 1) == 0) && (indexBit < 8*sizeof(int))){
    
    
            num = num >> 1;
            indexBit++;
        }
          
        return indexBit;
    }
      
    bool IsBit1(int num, unsigned int indexBit){
    
    
        num = num >> indexBit;
        return (num&1);
    }
      
};

33、和为S的连续正数序列

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

输出描述:

 输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序

解决思路:

  • 题目求的是连续正数序列,而且至少含有两个数,那么我们可以从1,2这两个数开始,
  • 以求和为9的所有连续序列为例,假设两个指针pSmall和pBig,分别指向正数序列的首尾,pSum表示序列之和,一开始pSmall=1,pBig=2,,pSum=3<9,序列需要包含更多的数,于是pBig+1,此时pSum=6,依旧小于9,于是pBig+1,此时pSum=10,大于9,序列需要删除一些数,于是pSmall-1,pSum=9,找到第一个满足条件的序列;接着pBig+1,按照前面的方法继续查找满足条件的序列,直到pSmall等于(s+1)/2.

解决代码:

class Solution {
    
    
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
    
    
        vector<vector<int> > res;
        vector<int> sequence;
        int pSmall = 1,pBig = 2;
        int currentSum = pSmall + pBig;
        int halfSum = (sum+1) / 2;
        while(pSmall < halfSum){
    
    
            if(currentSum == sum){
    
    
                getSequence(sequence,pSmall,pBig);
                res.push_back(sequence);
            }
            while(currentSum > sum && pSmall < pBig-1){
    
    
                currentSum -= pSmall;
                pSmall++;
                
                if(currentSum == sum){
    
    
                    getSequence(sequence,pSmall,pBig);
                    res.push_back(sequence);
                    break;
                }
            }
            pBig++;
            currentSum += pBig;
        }
        return res;
    }
    
    void getSequence(vector<int> &sequence,int small,int big)
    {
    
    
        sequence.clear();
        for(int i = small; i <= big; i++){
    
    
            sequence.push_back(i);
        }
    }
};

34、和为S的两个数字

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

输出描述:

对应每个测试案例,输出两个数,小的先输出。

解决思路:一开始被 如果有多对数字的和等于S,输出两个数的乘积最小的 给误导了,乘积最小,则外层乘积最小,证明如下:

假设:若b>a,且存在,
a + b = s;
(a - m ) + (b + m) = s
则:(a - m )(b + m)=ab - (b-a)m - m*m < ab;说明外层的乘积更小
也就是说依然是左右夹逼法!!!只需要2个指针

所以,只需要两个指针,一个指向头,一个指向尾部,和小的时候头++,大的时候尾部++即可。

解决代码:

class Solution {
    
    
public:
    vector<int> FindNumbersWithSum(vector<int> array,int sum) {
    
    
    vector<int> res;
    int length = array.size();
    if (length <= 1)
        return res;
 
    int i = 0;
    int j = length - 1;
    int currentSum;
     
    while (i < j)
    {
    
    
        currentSum = array[i] + array[j];
        if (currentSum > sum)
            j--;
        else if (currentSum < sum)
            i++;
        else
        {
    
    
            res.push_back(array[i]);
            res.push_back(array[j]);
            break;
        }      
    }
    return res;
    }
};

35、左旋转字符串

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

解决思路:
这题用最简单的思维是,发现新数组和n的规律,然后for循环输入新数组就行,算法复杂度为O(n)
当然还有其他的思路,请发动脑袋想一下。

解决代码:

class Solution {
    
    
public:
    string LeftRotateString(string str, int n) {
    
    
        string res = "";
        int len = str.length();
        for(int i = n; i < len; i++){
    
    
            res.push_back(str[i]);
        }
        for(int i = 0; i < n; i++){
    
    
            res.push_back(str[i]);
        }
        return res;
    }
};

36、翻转单词顺序列

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

解决思路先翻转单词,再翻转句子,调用的C++中的翻转程序,也可以自己重新写

解决代码:

class Solution {
    
    
public:
    string ReverseSentence(string str) {
    
    
        int len = str.length();
        int start = 0;
        for(int i = 0; i < len; i++){
    
    
            if(str[i] == ' '){
    
    
                reverse(str.begin()+start,str.begin()+i);
                start = i + 1;
            }
            if(i == len-1){
    
    
                reverse(str.begin()+start, str.end());
            }
        }
        reverse(str.begin(),str.end());
        return str;
    }
};

37、扑克牌顺子

题目: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。

解决思路:
如果是顺子的话,那么 5 张牌最大数和最小数的差应该小于5,同时,每个数字只能出现一次,满足这种条件,则为顺子。

解决代码:

class Solution {
    
    
public:
    bool IsContinuous( vector<int> numbers ) {
    
    
        int count[14] = {
    
    0};
        int len = numbers.size();
        int max = -1;
        int min = 14;
        if(len == 0){
    
    
            return false;
        }
        for(int i = 0; i < len; i++){
    
    
            count[numbers[i]]++;
            if(numbers[i] == 0)
                continue;
            if(count[numbers[i]] > 1)
                return false;
            if(numbers[i] > max)
                max = numbers[i];
            if(numbers[i] < min)
                min = numbers[i];
        }
        if(max - min < 5)
            return true;
        
        return false;
    }
};

38、孩子们的游戏(圆圈中最后剩下的数)(标记一下)

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

如果没有小朋友,请返回-1

解决思路:

  • 利用数学分析得到的解法
  • f(n,m)=[f(n-1,m)+m]%n,其中f(n,m)为长度为n的删除第m个节点,最后剩下的数字

解决代码:

class Solution {
    
    
public:
    int LastRemaining_Solution(int n, int m)
    {
    
    
        if(n<=0 || m<=0)
            return -1;
        int last = 0;
        for(int i = 2; i <= n; i++){
    
    
            last = (last+m)%i;
        }
        return last;
    }
};

39、求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;
        sum && (sum += Sum_Solution(n - 1));
        return sum;
    }
};

40、不用加减乘除做加法

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

解决思路:

  • 1、按位与是查看两个数哪些二进制位都为1,这些都是进位位,结果需左移一位,表示进位后的结果
  • 2、异或是查看两个数哪些二进制位只有一个为1,这些是非进位位,可以直接加、减,结果表示非进位位进行加操作后的结果
  • 3、n1&n2是查看有没有进位位了,如果有,需要重复step1、step2;如果没有,保留n1、n2上二进制为1的部分,用或将之合为一个数,即为最后结果

解决代码:

class Solution {
    
    
public:
    int Add(int num1, int num2)
    {
    
    
        int n1 = (num1 & num2) << 1;
        int n2 = num1 ^ num2;
        while(n1 & n2){
    
    
            num1 = n1;
            num2 = n2;
            n1 = (num1 & num2) << 1;
            n2 = num1 ^ num2;
        }
        return n1 | n2;
    }
};

41、把字符串转换为整数

题目:将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0

输入描述

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

输出描述

如果是合法的数值表达则返回该数字,否则返回0

示例1:

输入:
+2147483647
1a33

输出:
2147483647
0

解决代码(超出 int 范围的数无法表示):

class Solution {
    
    
public:
    int StrToInt(string str) {
    
    
        if(str.length() == 0)
            return 0;
        int i = 0;
        int flag = 1;
        int num = 0;
        if(str[i] == ' '){
    
    
            i++;
        }
        if(str[i] == '+'){
    
    
            i++;
            flag = 1;
        }else if(str[i] == '-'){
    
    
            i++;
            flag = -1;
        }
        while(str[i] != '\0'){
    
    
            if(str[i] >= '0' && str[i] <= '9'){
    
    
                num = num * 10 + flag * (str[i] - '0');
            }
            else{
    
    
                num = 0;
                break;
            }
            i++;
        }
        return num;
    }
};

42、数组中重复的数字

题目:在一个长度为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 < length; i++){
    
    
            for(int j = i+1; j < length; j++){
    
    
                if(numbers[i] == numbers[j]){
    
    
                    *duplication = numbers[i];
                    return true;
                }
            }
        }
        return false;
    }
};

43、构建乘积数组

题目:给定一个数组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]。不能使用除法。(注意:规定B[0] = A[1] * A[2] * … * A[n-1],B[n-1] = A[0] * A[1] * … * A[n-2];)

解决思路:
B[i] 的值可以看作下图每行元素相乘的结果;

在这里插入图片描述
解决代码:

class Solution {
    
    
public:
    vector<int> multiply(const vector<int>& A) {
    
    
        vector<int> B;
        int sz = A.size();
        if(sz == 0)
            return B;
        B.push_back(1);
        for(int i = 0; i < sz-1; i++)
            B.push_back(B.back() * A[i]);
        int tmp = 1;
        for(int i = sz - 1; i >= 0; i--)
        {
    
    
            B[i] = B[i] * tmp;
            tmp = tmp * A[i];
        }
        return B;
    }
};

44、正则表达式匹配

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

解决思路和代码(参考牛客的第一个评论):
链接:https://www.nowcoder.com/questionTerminal/45327ae22b7b413ea21df13ee7d6429c
来源:牛客网

/*
    解这题需要把题意仔细研究清楚,反正我试了好多次才明白的。
    首先,考虑特殊情况:
         1>两个字符串都为空,返回true
         2>当第一个字符串不空,而第二个字符串空了,返回false(因为这样,就无法
            匹配成功了,而如果第一个字符串空了,第二个字符串非空,还是可能匹配成
            功的,比如第二个字符串是“a*a*a*a*”,由于‘*’之前的元素可以出现0次,
            所以有可能匹配成功)
    之后就开始匹配第一个字符,这里有两种可能:匹配成功或匹配失败。但考虑到pattern
    下一个字符可能是‘*’, 这里我们分两种情况讨论:pattern下一个字符为‘*’或
    不为‘*’:
          1>pattern下一个字符不为‘*’:这种情况比较简单,直接匹配当前字符。如果
            匹配成功,继续匹配下一个;如果匹配失败,直接返回false。注意这里的
            “匹配成功”,除了两个字符相同的情况外,还有一种情况,就是pattern的
            当前字符为‘.’,同时str的当前字符不为‘\0’。
          2>pattern下一个字符为‘*’时,稍微复杂一些,因为‘*’可以代表0个或多个。
            这里把这些情况都考虑到:
               a>当‘*’匹配0个字符时,str当前字符不变,pattern当前字符后移两位,
                跳过这个‘*’符号;
               b>当‘*’匹配1个或多个时,str当前字符移向下一个,pattern当前字符
                不变。(这里匹配1个或多个可以看成一种情况,因为:当匹配一个时,
                由于str移到了下一个字符,而pattern字符不变,就回到了上边的情况a;
                当匹配多于一个字符时,相当于从str的下一个字符继续开始匹配)
    之后再写代码就很简单了。
*/
class Solution {
    
    
public:
    bool match(char* str, char* pattern)
    {
    
    
        //(这题代码确实有点难)
        if (*str == '\0' && *pattern == '\0')
            return true;
        if (*str != '\0' && *pattern == '\0')
            return false;
        //if the next character in pattern is not '*'
        if (*(pattern+1) != '*')
        {
    
    
            if (*str == *pattern || (*str != '\0' && *pattern == '.'))
                return match(str+1, pattern+1);
            else
                return false;
        }
        //if the next character is '*'
        else
        {
    
    
            if (*str == *pattern || (*str != '\0' && *pattern == '.'))
                return match(str, pattern+2) || match(str+1, pattern);
            else
                return match(str, pattern+2);
        }
    }
};

45、表示数值的字符串

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

解决思路及代码:

class Solution {
    
    
public:
    bool isNumeric(char* string)
    {
    
    
       //标记符号、小数点、e是否出现过
       bool sign = false,decimal = false,hasE = false;
       for(int i = 0; i < strlen(string); i++){
    
    
           if(string[i] == 'E' || string[i] == 'e'){
    
    
               //e后面一定要接数字
               if(i == strlen(string)-1) 
                   return false;
               if(hasE)
                   return false;
               hasE = true;
           }
           else if(string[i] == '+' || string[i] == '-'){
    
    
               //第二次出现+-符号,则必须紧接在e之后
               if(sign && string[i-1] != 'e' && string[i-1] != 'E')
                   return false;
               // 第一次出现+-符号,且不是在字符串开头,则也必须紧接在e之后
               if(!sign && i > 0 && string[i-1] != 'e' && string[i-1] != 'E')
                   return false;
               sign = true;
           }
           else if(string[i] == '.'){
    
    
               //e后面不能接小数点,小数点不能出现两次
               if(hasE || decimal)
                   return false;
               decimal = true;
           }
           else if(string[i] < '0' || string[i] > '9'){
    
    
               return false;
           }
       }
        return true;
    }
};

46、字符流中第一个不重复的字符

题目:请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。

输出描述:

如果当前字符流没有存在出现一次的字符,返回#字符。

解决代码:

class Solution
{
    
    
public:
  //Insert one char from stringstream
    vector<char> v;
    void Insert(char ch)
    {
    
    
         v.push_back(ch);
    }
  //return the first appearence once char in current stringstream
    char FirstAppearingOnce()
    {
    
    
        int count[255] = {
    
    0};
        for(int i = 0; i < v.size(); i++){
    
    
            if(!count[v[i]]){
    
    
                count[v[i]] = 1;
            }
            else{
    
    
                count[v[i]] = 2;
            }
        }
        
        for(int i = 0; i < v.size(); i++){
    
    
            if(count[v[i]] == 1)
                return v[i];
        }
        return '#';
    }

};

47、链表中环的入口结点

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

解决思路:
假设有两个指针,一个fast指针,一个slow指针,每次走两步,一个slow指针,每次走一步,当fast指针与slow指针相遇时,假设fast指针走了2x,那么slow指针走了x,由于有环,那么为了便于理解,分为两种情况

  • 第一种情况
    (1)当fast指针仅仅只比slow指针多走一个环,如图所示在这里插入图片描述
    (2)第一次相遇的时候,如图
    在这里插入图片描述
    (3)这个时候将fast 重新赋值为开头,如图
    在这里插入图片描述
    (4)再走两次,则找到了环的入口结点
    在这里插入图片描述
    所以思路为:
    a、第一步,找环中相汇点。分别用fast,slow指向链表头部,slow每次走一步,fast每次走二步,直到fast= =slow找到在环中的相汇点。
    b、第二步,找环的入口。接上步,当fast==slow时,fast所经过节点数为2x,slow所经过节点数为x,设环中有n个节点,fast比slow多走一圈有2x=n+x; n=x;
    可以看出slow实际走了一个环的步数,再让fast指向链表头部,slow位置不变。
    假设链表开头到环接口的距离是y,如下图所示,那么x-y表示slow指针走过的除链表开头y在环中走过的距离,那么slow再走y步,此时fast结点与slow结点相遇,fast == slow ,x-y+y=x = n,即此时slow指向环的入口。
    在这里插入图片描述
  • 第二种情况:当fast比slow 多走n个环
    在这里插入图片描述
    所以,思路为:
    a、第一步,找环中相汇点。分别用fast,slow指向链表头部,slow每次走一步,fast每次走二步,直到fast= =slow找到在环中的相汇点。
    b、第二步,找环的入口。接上步,当fast==slow时,fast所经过节点数为2x,slow所经过节点数为x,设环中有n个节点,fast比slow多走r圈有2x=rn+x; x=rn;(r为圈数,n为一圈的结点数)
    可以看出slow实际走了多个环的步数,再让fast指向链表头部,slow位置不变。
    假设链表开头到环接口的距离是y,那么x-y表示slow指针走过的除链表开头y在环中走过的距离,那么slow再走y步,此时fast结点与slow结点相遇,fast == slow ,x-y+y=x = rn,即此时slow指向环的入口。(原文连接

解决代码:

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
    
    
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
    
    
        ListNode* pFast = pHead;
        ListNode* pSlow = pHead;
        
        while(pFast!=NULL && pFast->next!=NULL){
    
    
            pFast = pFast->next->next;
            pSlow = pSlow->next;
            //当快指针和慢指针相遇时
            if(pFast == pSlow){
    
    
                pFast = pHead;
                //再次相遇
                while(pFast != pSlow){
    
    
                    pFast = pFast->next;
                    pSlow = pSlow->next;
                }
                return pFast;
            }
        }
        return NULL;
    }
};

48、删除链表中重复的节点

题目:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表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* pre = NULL;
        ListNode* p = pHead;
        ListNode* q = NULL;
        while(p != NULL){
    
    
            //当前结点p,(其实是p指向当前结点),与它下一个结点p->next的val相同,说明要删掉有这个val的所有结点
            if(p->next != NULL && p->next->val == p->val){
    
    
                q = p->next;
                //找到q,它指向最后一个与p val相同的结点,那p 到 q (包含) 都是要删除的
                while(q != NULL && q->next != NULL && q->next->val == p->val){
    
    
                    q = q->next;
                }
                //如果p指向链表中第一个元素,p -> ... -> q ->... , 要删除p到q, 将指向链表第一个元素的指针pHead指向q->next。
                if(p == pHead){
    
    
                    pHead = q->next;
                }
                //如果p不指向链表中第一个元素,pre -> p ->...->q ->... ,要删除p到q,即pre->next = q->next
                else{
    
    
                    pre->next = q->next;
                }
                 //当前处理的p要向链表尾部移动
                p = q->next;
            }
            else{
    
    
                pre = p;
                p = p->next;
            }
        }
        return pHead;
    }
};

49、数据流中的中位数

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

解决代码一:
直接利用算法进行排序,然后直接输出

class Solution {
    
    
public:
    vector<int> v;
    void Insert(int num)
    {
    
    
        v.push_back(num);
    }

    double GetMedian()
    {
    
     
        sort(v.begin(),v.end());
        int len = v.size();
        double midNum = 0.0;
        int mid = len / 2;
        if(len % 2 == 0){
    
    
            
            midNum = double(double((v[mid] + v[mid-1])) / 2); 
        }
        else{
    
    
            midNum = double(v[mid]);
        }
        return midNum;
    }

};

解决代码二:(这个要细看)

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

50、滑动窗口的最大值

题目:给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{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<int> maxInWindows(const vector<int>& num, unsigned int size)
    {
    
    
        vector<int> max;
        if(num.empty() || size > num.size() || size < 1)
            return max;
        int m;
        for(int i = 0; i < num.size() - size + 1; i++)
        {
    
    
            m = num[i];
            for(int j = i + 1; j < i + size; j++)
            {
    
    
                if(num[j] > m)
                {
    
    
                    m = num[j];
                } 
             }
            max.push_back(m);
        }
             
        return max;    
    }
};

51、矩阵中的路径

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

这题标记一下,有点困难,慢慢再看
解决思路:

分析:回溯算法
 这是一个可以用回朔法解决的典型题。首先,在矩阵中任选一个格子作为路径的起点。如果路径上的第i个字符不是ch,那么这个格子不可能处在路径上的
第i个位置。如果路径上的第i个字符正好是ch,那么往相邻的格子寻找路径上的第i+1个字符。除在矩阵边界上的格子之外,其他格子都有4个相邻的格子。
重复这个过程直到路径上的所有字符都在矩阵中找到相应的位置。
  由于回朔法的递归特性,路径可以被开成一个栈。当在矩阵中定位了路径中前n个字符的位置之后,在与第n个字符对应的格子的周围都没有找到第n+1个
字符,这个时候只要在路径上回到第n-1个字符,重新定位第n个字符。
  由于路径不能重复进入矩阵的格子,还需要定义和字符矩阵大小一样的布尔值矩阵,用来标识路径是否已经进入每个格子。 当矩阵中坐标为(row,col)的
格子和路径字符串中相应的字符一样时,从4个相邻的格子(row,col-1),(row-1,col),(row,col+1)以及(row+1,col)中去定位路径字符串中下一个字符
如果4个相邻的格子都没有匹配字符串中下一个的字符,表明当前路径字符串中字符在矩阵中的定位不正确,我们需要回到前一个,然后重新定位。
  一直重复这个过程,直到路径字符串上所有字符都在矩阵中找到合适的位置

解决代码:

class Solution {
    
    
public:
    bool hasPath(char* matrix, int rows, int cols, char* str)
    {
    
      
      if(str==NULL||rows<=0||cols<=0)
           return false;
      bool *isOk=new bool[rows*cols]();
      for(int i=0;i<rows;i++)
      {
    
    
           for(int j=0;j<cols;j++)
                if(isHsaPath(matrix,rows,cols,str,isOk,i,j))
                   return true;
      }
      return false;
    }
 bool isHsaPath(char *matrix,int rows,int cols,char *str,bool *isOk,int curx,int cury)
 {
    
    
      if(*str=='\0')
           return true;
      if(cury==cols)
      {
    
    
           curx++;
           cury=0;
      }
      if(cury==-1)
      {
    
    
           curx--;
           cury=cols-1;
      }
      if(curx<0||curx>=rows)
           return false;
      if(isOk[curx*cols+cury]||*str!=matrix[curx*cols+cury])
           return false;
      isOk[curx*cols+cury]=true;
      bool sign=isHsaPath(matrix,rows,cols,str+1,isOk,curx-1,cury)
       ||isHsaPath(matrix,rows,cols,str+1,isOk,curx+1,cury)
       ||isHsaPath(matrix,rows,cols,str+1,isOk,curx,cury-1)
       ||isHsaPath(matrix,rows,cols,str+1,isOk,curx,cury+1);
      isOk[curx*cols+cury]=false;
      return sign;
    }
};

52、机器人的运动范围

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

解决思路:

  • 回溯法
    核心思路:
    1.从(0,0)开始走,每成功走一步标记当前位置为true,然后从当前位置往四个方向探索,
    返回1 + 4 个方向的探索值之和。
    2.探索时,判断当前节点是否可达的标准为:
    1)当前节点在矩阵内;
    2)当前节点未被访问过;
    3)当前节点满足limit限制。

解决代码:

class Solution {
    
    
public:
    int movingCount(int threshold, int rows, int cols)
    {
    
    
       bool *flag = new bool[rows * cols];
        for(int i = 0; i < rows * cols; i++)
            flag[i] = false;
        int count = moving(threshold, rows, cols, 0, 0, flag);
        return count; 
    }
     int moving(int threshold, int rows, int cols, int i, int j, bool* flag)
        {
    
    
        int count = 0;
        if(i >= 0 && i < rows && j >= 0 && j < cols && getsum(i) + getsum(j) <= threshold && flag[i * cols + j] == false)
            {
    
    
            flag[i * cols + j] = true;
            count =1 + moving(threshold, rows, cols, i + 1, j, flag)
                + moving(threshold, rows, cols, i - 1, j, flag)
                + moving(threshold, rows, cols, i , j - 1, flag)
                + moving(threshold, rows, cols, i, j + 1, flag);
        }
        return count;
    }
    int getsum(int num)
        {
    
    
        int sum = 0;
        while(num)
            {
    
    
            sum += num % 10;
            num /= 10;
              
        }
        return sum;
    }
};

53、剪绳子

题目:给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],…,k[m]。请问k[0]xk[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

输入描述:

输入一个数n,意义见题面。(2 <= n <= 60

输出描述:

输出答案。

示例:

输入:
8
输出:
18

解决思路:(来源牛客官网题解)
方法:(1)暴力递归

链接:https://www.nowcoder.com/questionTerminal/57d85990ba5b440ab888fc72b0751bf8?answerType=1&f=discussion
来源:牛客网

暴力递归就要想到递归三部曲:

递归函数的设计和功能:back_track(n); 含义是:求长度为n的数,最后分段后的最大乘积,这里我们不需要关心分成多少段
递归函数的终止条件: 如果n <= 4, 显然back_track(n) = n,初始条件也就是我们不用计算就能得到的。
下一步递归:对于长度n,我们需要减少递归参数n,如果第一段为1, 显然下一步递归为back_track(n-1),如果第一段为2, 则下一步递归为
back_track(n-2)...因为要至少分2段,所以,最后一次可能的情况为最后一段为n-1, 下一步递归为back_track(1),因此,每一步可能的结果为
1 * back_track(n-1), 2 * back_track(n-2), ..., (n-1) * back_track(1),在n-1种情况中取一个最大值即可。 这里我们不用关系back_track(n-1)等的值为多少,因为最终会递归到我们的终止条件,因此绝对是可以求出来。
时间复杂度:O(n!)
空间复杂度:O(n), 最多分n段,每段长度为1, 所以递归深度为n

方法一:解决代码:

class Solution {
    
    
public:
    int back_track(int n) {
    
    
        // n <= 4, 表明不分,长度是最大的
        if (n <= 4) {
    
    
            return n;
        }

        int ret = 0;
        for (int i = 1; i < n; ++i) {
    
    
            ret = max(ret, i * back_track(n - i));
        }
        return ret;
    }
    int cutRope(int number) {
    
    
        // number = 2 和 3 时,分 2 段和分 1 段的结果是不一样的,所以需要特判一下
        if (number == 2) {
    
    
            return 1;
        }
        else if (number == 3) {
    
    
            return 2;
        }
        return back_track(number);
    }
};

方法(2):记忆化递归
根据方法一,假设求back_track(7),如下:
在这里插入图片描述

f() 替代 back_track(),可知,红色的部分重复了。
因此,我们可以开一个数组,把计算过的结果存起来。
步骤如下:

初始化一个大小为 n+1 的数组,初始值为 -1 , 也可以-2, 反正是不可能得到的值
时间复杂度:O(n^2)
空间复杂度:O(n)
在方法一的代码上,记录一下,详细代码如下

方法二代码:

class Solution {
    
    
public:
  int back_track(int n, vector<int> &mark) {
    
    
      if (n <= 4) {
    
    
          return n;
      }
      // 在方法一的基础上添加
      if (mark[n] != -1) {
    
    
          return mark[n];
      }

      int ret = 0;
      for (int i = 1; i < n; ++i) {
    
    
          ret = max(ret, i * back_track(n - i));
      }
      // 添加部分
      return mark[n] = ret;
  }
  int cutRope(int number) {
    
    
      if (number == 2) {
    
    
          return 1;
      }
      else if (number == 3) {
    
    
          return 2;
      }
      // 添加部分
      vector<int> mark(number, -1);
      return back_track(numberm, mark);
  }
};

方法(3):动态规划
将方法二修改为迭代版本的动态规划
时间复杂度:O(n^2)
空间复杂度:O(n)

方法三代码:

class Solution {
    
    
public:
    int cutRope(int number) {
    
    
        if (number == 2) {
    
    
            return 1;
        }
        else if (number == 3) {
    
    
            return 2;
        }

        vector<int> f(number + 1, -1);
        for (int i = 1; i <= 4; ++i) {
    
    
            f[i] = i;
        }
        for (int i = 5; i <= number; ++i) {
    
    
            for (int j = 1; j < i; ++j) {
    
    
                f[i] = max(f[i], j * f[i - j]);
            }
        }
        return f[number];
    }
};

剑指offer,第一遍笔记完成,但是没有二叉树的,二叉树的专题题目见下一篇博客!!!

猜你喜欢

转载自blog.csdn.net/qq_41782149/article/details/106451612