剑指offer题目合集及解法

最近做了剑指offer的题目,很多题目的最优解很有意思,很多时候想不到,写篇博客专门来记录一下。

 

二维数组中的查找

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

利用排序的特性

class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {
        if(array.empty()) return 0;
        int n=array.size(),m=array[0].size();
        int p=0,q=m-1;
        while(p<n&&q>=0)    //利用排序特性不断的改变座标
        {
            if(target==array[p][q]) return 1;
            else if(target<array[p][q]) q--;
            else p++;
        }
        return 0;
    }
};

替换空格

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

看评论的做法是从后往前扫描空格,这样的话可以预先处理出变换后的字符串的长度,然后就是O(n)的变化了,我自己用来新的string类= =,这样空间就是O(n)了。

class Solution {
public:
	void replaceSpace(char *str,int length) {   //很弱智的做法
        string s;
        for(int i=0;i<length;i++)
        {
            if(str[i]==' ') s+="%20";
            else s+=str[i];
        }
        int ls=s.length();
        for(int i=0;i<ls;i++) str[i]=s[i];
        str[ls]='\0';
	}
};
class Solution {
public:
	void replaceSpace(char *str,int length) {   //从后往前扫描
        int cnt=0;
        for(int i=0;i<length;i++)
        {
            if(str[i]==' ')
                cnt++;
        }
        int ls=length+cnt*2;
        for(int i=length-1;i>=0;i--)
        {
            if(str[i]!=' ') str[i+cnt*2]=str[i];    //计算出当前字符处理后的位置
            else    //遇到空格的处理
            {
                cnt--;
                str[i+2*cnt]='%',str[i+1+2*cnt]='2',str[i+2+2*cnt]='0';
            }
        }
        str[ls]='\0';
	}
};

从尾到头打印链表

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

这个题目,我按照链表头到尾顺序输出到vector中最后reverse了一下,当然也可以使用递归输出。

/**
*  struct ListNode {
*        int val;
*        struct ListNode *next;
*        ListNode(int x) :
*              val(x), next(NULL) {
*        }
*  };
*/
class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
        vector<int> v;
        ListNode *p=head;
        while(p)    //顺序输出
        {
            v.push_back(p->val);
            p=p->next;
        }
        reverse(v.begin(),v.end()); //反转vector
        return v;
    }
};
class Solution {
public:
    vector<int> res;
    void cal(ListNode* root)    //递归版
    {
        if(root==nullptr) return ;
        cal(root->next);
        res.push_back(root->val);
    }
    vector<int> printListFromTailToHead(ListNode* head) {
        res.clear();
        if(head==nullptr) return res;
        cal(head);
        return res;
    }
};

重建二叉树

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

根据前序中序得到建二叉树,数据结构实验做过很多,就是不断递归就好。

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
 
class Solution {
public:
    TreeNode* findroot(vector<int> pre,int pl,int pr,vector<int> vin,int vl,int vr)
    {
        if(pl>pr||vl>vr) return nullptr;
        TreeNode *root=new TreeNode(pre[pl]);
        int pos=0;
        for(int i=vl;i<=vr;i++)
        {
            if(vin[i]==pre[pl]) //找到这个节点分界线
            {
                pos=i;
                break;
            }
        }
        root->left=findroot(pre,pl+1,pl+pos-vl,vin,vl,pos-1);
        root->right=findroot(pre,pl+pos-vl+1,pr,vin,pos+1,pr);
        return root;
    }
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
        if(pre.size()==0||vin.size()==0||pre.size()!=vin.size()) return nullptr;
        TreeNode *root=findroot(pre,0,pre.size()-1,vin,0,vin.size()-1); //确定下一次递归的范围
        return root;
    }
};

用两个栈实现队列

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

假设两个栈s1 s2 。push的时候只加入s1,当需要pop的时候,将s1的数pop出加入s2直到s1只剩一个数,弹出这个数,再将s2的数一个个加回s1,感觉复杂度蛮高的,也没啥好方法。。

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

    int pop() {
        while(stack1.size()>1)
        {
            stack2.push(stack1.top());
            stack1.pop();
        }
        int tmp=stack1.top();
        stack1.pop();
        while(!stack2.empty())
        {
            stack1.push(stack2.top());
            stack2.pop();
        }
        return tmp;
    }

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

旋转数组的最小数字

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

这个题搞了一会儿,主要就是这个二分搞得我不是很清楚,跟一般的二分不太一样。

假设 l r mid,最大数和最小数之间称为分界线。

因为是旋转数组,所以第一个元素大于最后一个元素,接下来就是中间元素的问题,如果 v[mid]<v[r],说明mid在分界线右边,这个时候调整r=mid,如果v[mid]>v[r],说明在分界线左边,这个时候调整l=mid+1,至于为什么这个时候是l=mid+1,我的理解是因为两个数的时候r=mid-1会出错,但是l=mid+1仍然是正确答案,反正这个二分我也还有点迷= =

class Solution {
public:
    int minNumberInRotateArray(vector<int> v) {
        int l=0,r=v.size()-1;
        if(r==0) return 0;
        while(l<r)
        {
            int mid=(l+r)>>1;
            if(v[mid]>v[r]) l=mid+1;
            else if(v[mid]==v[r]) r--;
            else r=mid;
        }
        return v[l];
    }
};

斐波那契数列  f(n)=f(n-1)+f(n-2)

跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)(仔细推一下也是斐波那契入门题 应该第n阶台阶可以从 n-1阶 和 n-2阶得到)

矩形覆盖

我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?(其实也是斐波那契 想一下 对于 2*n的矩阵 用 2*1去填它  如果紧贴左边贴一个2*1 那么还剩下2*(n-1)的矩阵要贴,如果贴了1*2,剩下的就是2*(n-2)的矩阵要贴)所以都是斐波那契。

class Solution {
public:
    int F(int number) {
        int x=1,y=2,z;
        if(number==0) return 0;
        if(number==1) return 1;
        if(number==2) return 2;
        for(int i=3;i<=number;i++)
        {
            z=x+y;
            x=y;
            y=z;
        }
        return z;
    }
};

变态跳台阶

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

这个利用上面的性质很容易推 f(n)=f(n-1)+f(n-2)+f(n-3)+f(n-4)+......+f(1),简单推理得f(n)=2*f(n-1)。

class Solution {
public:
    int jumpFloorII(int number) {
        int x=1;
        for(int i=2;i<=number;i++) x=x*2;
        return x;
    }
};

二进制中1的个数

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

这个做完才想起来 树状数组里的lowbit 也就是 x&(-x),利用lowbit可以在很短时间得出答案

class Solution {
public:
     int  NumberOf1(int n) {
         int ans=0;
         while(n!=0)
         {
             ans++;
             n-=(n&(-n));   //n&(-n)就是二进制1的最后一位
         }
         return ans;
     }
};

数值的整数次方

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。(简单快速幂 注意指数可能为负)

class Solution {
public:
    double Power(double base, int exponent) {
        double ans=1;
        int p=abs(exponent);
        while(p)
        {
            if(p&1) ans*=base;
            base*=base;
            p=p/2;
        }
        return exponent<0 ? 1/ans:ans;
    }
};

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

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

这个题目如果不新建空间感觉还是挺麻烦的,所以就新建空间好一点。不然也没什么特别好的方法,其实可以只建一个的,在原有的里面不断删除数即可,我为了方便建了2个vector。


class Solution {
public:
    void reOrderArray(vector<int> &array) {
        vector<int> v1,v2;
        for(auto it=array.begin();it!=array.end();it++)
        {
            if((*it)&1) v1.push_back(*it);
            else v2.push_back(*it);
        }
        array.clear();
        for(auto it=v1.begin();it!=v1.end();it++) array.push_back(*it);
        for(auto it=v2.begin();it!=v2.end();it++) array.push_back(*it);
    }
};

链表中倒数第k个结点

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

这个刚开始我做法是先跑一遍看一共多少个节点,然后判断倒数第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 cnt=0;
        ListNode *p1=pListHead,*p2=pListHead;
        while(pListHead)
        {
            if(cnt>=k) p1=p1->next;
            cnt++;
            pListHead=pListHead->next;
            p2=p2->next;
        }
        if(cnt<k) p1=nullptr;
        return p1;
    }
};

反转链表

输入一个链表,反转链表后,输出新链表的表头。(学过数据结构的应都会啊)

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        ListNode *p=NULL;
        while(pHead!=NULL)
        {
            ListNode *tmp=pHead->next;
            pHead->next=p;
            p=pHead;
            pHead=tmp;
        }
        return p;
    }
};

合并两个排序的链表

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

class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        ListNode *tmp=NULL,*p=NULL;
        if(pHead1==NULL) return pHead2;
        if(pHead2==NULL) return pHead1;
        while(pHead1&&pHead2)   //两表都不为空
        {
            if(pHead1->val<=pHead2->val)    //选择较小的加入新链表
            {
                if(tmp==NULL) tmp=p=pHead1;
                else p->next=pHead1,p=p->next;
                pHead1=pHead1->next;
            }
            else
            {
                if(tmp==NULL) tmp=p=pHead2;
                else p->next=pHead2,p=p->next;
                pHead2=pHead2->next;
            }
        }
        if(pHead1) p->next=pHead1;  //最后将剩下链表所有数加入
        if(pHead2) p->next=pHead2;
        return tmp;
    }
};

树的子结构

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

本来感觉挺麻烦的,后来仔细想想就是对于每个节点递归处理判断,同时用到了大一学到的一个知识点 A||B 如果第一个满足则不会执行B语句的判断。利用这一点这个题代码会简单很多

class Solution {
public:
    bool check(TreeNode *r1,TreeNode *r2)
    {
        if(r2==nullptr) return 1;//这个函数如果r2为空就代表判断结束 这个节点符合要求
        if(r1==nullptr) return 0;//r1为空 r2不为空 则一定不符合要求
        if(r1->val==r2->val)    //只有当前节点相同往下判断
            return check(r1->left,r2->left)&&check(r1->right,r2->right);
        return 0;
    }
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        if(pRoot1==nullptr||pRoot2==nullptr) return 0;//其实这里应该有点小问题 B上来为空应该是符合要求的感觉
        return check(pRoot1,pRoot2)||HasSubtree(pRoot1->left,pRoot2)||check(pRoot1->right,pRoot2);  //检查当前节点 不符合则检查儿子节点
    }
};

二叉树的镜像

 

操作给定的二叉树,将其变换为源二叉树的镜像。(直接在当前层将左右儿子swap即可)

class Solution {
public:
    void Mirror(TreeNode *pRoot) {
        if(pRoot==NULL) return ;
        swap(pRoot->left,pRoot->right); //直接交换即可
        if(pRoot->left) Mirror(pRoot->left);
        if(pRoot->right) Mirror(pRoot->right);
    }
};

顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下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.(这个题想到了大一困扰我许久的回形矩阵不过那个题目n*n 这个是n*m的)

class Solution {
public:
    vector<int> printMatrix(vector<vector<int> > matrix) {
        vector<int> res;
        int n=matrix.size();
        if(n==0) return res;
        int m=matrix[0].size();
        int cr=(min(n,m)-1)/2+1;    //计算旋转层数
        for(int i=0;i<cr;i++)
        {
            for(int j=i;j<m-i;j++) res.push_back(matrix[i][j]);
            for(int j=i+1;j<n-i;j++) res.push_back(matrix[j][m-i-1]);//其实中间这一堆条件是因为某些情况出错 
            for(int j=m-i-2;(i!=m-i-1)&&(i!=n-i-1)&&j>=i;j--) res.push_back(matrix[n-i-1][j]);  //因为n m值不同 避免重复计算某些数
            for(int j=n-i-2;(i!=m-i-1)&&j>i;j--) res.push_back(matrix[j][i]);
        }
        return res;
    }
};

包含min函数的栈

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

栈s1正常存数,栈s2压数的时候只有当前要压入的数<=s2栈顶元素的时候才压入,弹出时如果s1.top()==s2.top(),则一起弹出。

class Solution {
    stack<int> s1,s2;
public:
    void push(int value) {
        s1.push(value);
        if(s2.empty()||value<=s2.top()) s2.push(value);
    }
    void pop() {
        if(s1.empty()) return ;
        if(s1.top()==s2.top()) s2.pop();
        s1.pop();
    }
    int top() {
        return s1.top();
    }
    int min() {
        return s2.top();
    }
};

栈的压入、弹出序列

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

class Solution {
public:
    bool IsPopOrder(vector<int> pushV,vector<int> popV) {
        stack<int> s;
        auto gk=popV.begin();
        for(auto it=pushV.begin();it!=pushV.end();it++)
        {
            s.push(*it);
            while(!s.empty()&&s.top()==*gk) s.pop(),gk++;
        }
        if(gk==popV.end()) return 1;
        return 0;
    }
};

从上往下打印二叉树

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

class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
        queue<TreeNode*> que;
        vector<int> res;
        if(root==NULL) return res;
        que.push(root);
        while(!que.empty())
        {
            TreeNode *tmp=que.front();
            que.pop();
            res.push_back(tmp->val);
            if(tmp->left!=NULL) que.push(tmp->left);
            if(tmp->right!=NULL) que.push(tmp->right);
        }
        return res;
    }
};

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

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

刚开始看错题因为是二叉树的后序遍历,心想这怎么搞,后来发现是二叉搜索树,后序遍历的顺序是左 右 根,那么在二叉搜索树里左小于根 右大于跟  利用这个性质递归判断看是否符合即可

class Solution {
public:
    bool check(vector<int> v,int l,int r)
    {
        if(l>r) return 1;
        if(l==r) return 1;
        int root=v[r],pos=-1;
        for(int i=l;i<r;i++)//找分界点
        {
            if(v[i]>root)
            {
                pos=i;
                break;
            }
        }
        if(pos==-1) return check(v,l,r-1);  //找不到的情况
        for(int i=pos;i<r;i++)  //判断是否合法
            if(v[i]<root) return 0;
        return check(v,l,pos-1)&&(v,pos,r-1);
    }
    bool VerifySquenceOfBST(vector<int> sequence) {
        if(sequence.size()==0) return 0;
        return check(sequence,0,sequence.size()-1);//递归判断
    }
};

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

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

这个题目刚开始也看错了,以为路径就是任意两点间的路径,感觉真的挺复杂的。后来发现路径的定义是根到叶子节点,服了。直接dfs就好

class Solution {
public:
    vector<vector<int> > res;
    vector<int> v;
    void dfs(TreeNode *root,int x)
    {
        if(root==nullptr) return ;
        v.push_back(root->val);
        if(x-root->val==0&&!root->left&&!root->right) res.push_back(v);//叶子节点并且符合要求
        dfs(root->left,x-root->val);
        dfs(root->right,x-root->val);
        v.erase(v.end()-1);
    }
    vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
        if(root) dfs(root,expectNumber);
        return res;
    }
};

复杂链表的复制

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

这个稍微有点复杂,就是首先在每个节点后面增加一个他自己的复制节点。

例如原链表A->B->C->D->E 转换后为 A->A'->B->B'->C->C'->D->D'->E->E',这样之后将原先随机指的节点同样复制过去,例如原先有A->D 则将A'->D' 方法也很简单 A'->random=A->random->next即可。最后在断开恢复正常链表 返回A‘

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==nullptr) return nullptr;
        RandomListNode *p=pHead;
        while(p)
        {
            RandomListNode *tmp=new RandomListNode(p->label);   //复制节点
            tmp->next=p->next;
            p->next=tmp;
            p=p->next->next;
        }
        p=pHead;
        while(p)    //修正random的位置
        {
            if(p->random) p->next->random=p->random->next;
            p=p->next->next;
        }
        RandomListNode *pc=pHead->next,*tmp;
        p=pHead;
        while(p->next)  //依次断开
        {
            tmp=p->next;
            p->next=tmp->next;
            p=tmp;
        }
        return pc;
    }
};

二叉搜索树与双向链表

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

这个题没想出来,最后看了评论里的一个大佬的做法,真的很厉害也很简洁。

首先也知道的就是二叉搜索树的排序状态就是中序遍历,那么中序遍历的结果就是答案,那么问题就是边的问题了,具体看代码,很难讲清楚。

class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        if(pRootOfTree==nullptr) return nullptr;
        TreeNode *tmp=nullptr;
        create(pRootOfTree,tmp);//开始遍历
        while(pRootOfTree->left) pRootOfTree=pRootOfTree->left;//头节点是最小的节点
        return pRootOfTree;
    }
    void create(TreeNode *root,TreeNode* &tmp)//注意tmp引用
    {
        if(root==nullptr) return ;
        create(root->left,tmp);
        root->left=tmp;
        if(tmp) tmp->right=root;
        tmp=root;
        create(root->right,tmp);
    }
};

字符串的排列

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。(感觉就是dfs找所有情况,没什么太难的东西,写一种邪道方法)

class Solution {
public:
    vector<string> Permutation(string str) {
        vector<string> res;
        if(str=="") return res;
        do
        {
            res.push_back(str);
        }while(next_permutation(str.begin(),str.end()));
        return res;
    }
};

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

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

这个我自己没想出最优解,最优解蛮神奇的,感觉直到是对的但是自己有点难想,看代码应该就明白了。

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        int cnt=numbers.size(),res=numbers[0],co=1;
        if(cnt==0) return 0;
        for(auto it=numbers.begin()+1;it!=numbers.end();it++)
        {
            if(*it==res) co++;  //如果和前一个数相同 co++
            else co--;  //不相同--
            if(co==0) res=*it,co=1; //co为0则重新赋值
        }
        co=0;
        for(auto it=numbers.begin();it!=numbers.end();it++) //检查res是否符合要求
        {
            if(*it==res) co++;
        }
        if(co>cnt/2) return res;
        return 0;
    }
};

最小的K个数

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

求最小的k个数,其实就是用堆吧,很多大佬手写堆,真的厉害。。。我这里用的c++优先队列

class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        priority_queue<int> que;
        vector<int> res; 
        if(k>input.size()) return res;
        for(auto it=input.begin();it!=input.end();it++)
        {
            que.push(*it);
            while(que.size()>k) que.pop();
        }
        while(!que.empty()) res.push_back(que.top()),que.pop();
        return res;
    }
};

连续子数组的最大和

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

class Solution {
public:
    int dp[100005];
    int INF=1e9+7;
    int FindGreatestSumOfSubArray(vector<int> array) {
        int ans=-INF;
        dp[0]=array[0];
        for(int i=1;i<array.size();i++)
        {
            dp[i]=max(dp[i-1]+array[i],array[i]);
            ans=max(ans,dp[i]);
        }
        return ans;
    }
};

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

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

这个题就是针对每一位计算即可,个位十位百位,看一下每一位1出现的规律。

class Solution {
public:
    int NumberOf1Between1AndN_Solution(int n)
    {
        int tmp=n,ans=0,gs=1;
        while(tmp)
        {
            int k=tmp%10;
            tmp=tmp/10;
            if(k>1) ans+=(tmp+1)*gs;
            else if(k==1) ans+=(tmp*gs+n%gs+1);
            else ans+=(tmp*gs);
            gs=gs*10;
        }
        return ans;
    }
};

把数组排成最小的数

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

这个题也挺神奇,刚开始没想出来= = 就是把所有数丢进去排序 排序规则是 a+b<b+a,因为位数一定相同所以直接使用string的比较就行。做完发现以前好像做过这道题(不过我电脑运行不了 to_string()函数 烦)

class Solution {
public:
    static bool cmp(int a,int b)
    {
        string s1=to_string(a)+to_string(b);
        string s2=to_string(b)+to_string(a);
        return s1<s2;
    }
    string PrintMinNumber(vector<int> numbers) {
        string ans="";
        if(numbers.size()==0) return ans;
        sort(numbers.begin(),numbers.end(),cmp);
        for(auto it=numbers.begin();it!=numbers.end();it++)
            ans+=to_string(*it);
        return ans;
    }
};

丑数

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

丑数一定是 2 3 5相乘得到 又1是第一个丑数,直接让 1*2 1*3 1*5丢到优先队列里就好,bfs,其实也没必要直接乘出来最小的数继续乘即可,我只是闲着没事干

class Solution {
public:
    map<long long,bool> mp;   //检查是否出现过
    int GetUglyNumber_Solution(int index) {
        if(index<=0) return 0;
        priority_queue<long long,vector<long long>,greater<long long> > que;//注意long long会爆int
        que.push(1);
        mp[1]=1; 
        int cnt=0,ans=0;
        while(!que.empty())
        {
            long long x=que.top();
            que.pop(),cnt++;
            if(cnt==index)
            {
                ans=x;
                break;
            }
            if(mp[x*2]==0) que.push(x*2),mp[x*2]=1;
            if(mp[x*3]==0) que.push(x*3),mp[x*3]=1;
            if(mp[x*5]==0) que.push(x*5),mp[x*5]=1;
        }
        return ans;
    }
};

第一个只出现一次的字符

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

这个感觉也没啥方法,map储存字符出现次数,最后扫一遍看只出现一次的是谁。

class Solution {
public:
    int FirstNotRepeatingChar(string str) {
        map<char,int> mp;
        for(auto it=str.begin();it!=str.end();it++) mp[*it]++;
        for(auto it=str.begin();it!=str.end();it++)
        {
            if(mp[*it]==1) return it-str.begin();
        }
        return -1;
    }
};

数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007(归并排序就完事,好久没写归并了,出好多问题)

class Solution {
public:
    int ans=0;
    int mod=1e9+7;
    vector<int> res;
    void mer(int l,int r)
    {
        if(l==r) return ;
        int mid=(l+r)>>1;
        mer(l,mid);
        mer(mid+1,r);
        vector<int> v;
        int pl=l,ql=mid+1;
        while(pl<=mid&&ql<=r)   //合并左右的结果
        {
            if(res[pl]<=res[ql]) v.push_back(res[pl++]);
            else v.push_back(res[ql]),ans+=(mid+1-pl),ans%=mod,ql++;//合并时更新ans的结果
        }
        while(pl<=mid) v.push_back(res[pl++]);
        while(ql<=r) v.push_back(res[ql++]);
        int cnt=0;
        for(int i=l;i<=r;i++) res[i]=v[cnt++];
    }
    int InversePairs(vector<int> data) {
        res=data;
        int l=0,r=res.size()-1;
        mer(l,r);   //归并排序
        return ans;
    }
};

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

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

又是被大佬折服的题目,思路不麻烦,首先找到长度差,让长的链表先走链表差,然后一起走,如果有公共节点则会相同,主要是被一个写法折服了。巨简洁。。。

class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        ListNode *p1=pHead1,*p2=pHead2;
        while(p1!=p2)   //仔细思考发现它就是对的
        {
            p1= p1==NULL ? pHead2:p1->next;
            p2= p2==NULL ? pHead1:p2->next;
        }
        return p1;
    }
};

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

统计一个数字在排序数组中出现的次数。(两次二分即可)

class Solution {
public:
    int GetNumberOfK(vector<int> data ,int k) {
        if(data.size()==0) return 0;
        int p=lower_bound(data.begin(),data.end(),k)-data.begin();
        int q=upper_bound(data.begin(),data.end(),k)-data.begin();
        if(data[p]==k) return q-p;
        return 0;
    }
};

二叉树的深度

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。(简单dfs遍历一遍不断取最大值即可)

class Solution {
public:
    int res=0;
    void finddep(TreeNode* root,int dep)
    {
        if(root==NULL) return ;
        res=max(res,dep);
        finddep(root->left,dep+1);
        finddep(root->right,dep+1);
    }
    int TreeDepth(TreeNode* pRoot)
    {
        finddep(pRoot,1);
        return res;
    }
};

平衡二叉树

根据定义出发递归判断,两儿子的深度差不超过1

class Solution {
public:
    int dep(TreeNode* root)
    {
        if(root==NULL) return 0;
        return max(dep(root->left),dep(root->right))+1;
    }
    bool IsBalanced_Solution(TreeNode* pRoot) {
        if(pRoot==NULL) return 1;
        if(abs(dep(pRoot->left)-dep(pRoot->right))<=1&&IsBalanced_Solution(pRoot->left)&&IsBalanced_Solution(pRoot->right))
           return 1;
        return 0;
    }
};

数组中只出现一次的数字

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

这个题很有意思,如果只有1个数字,那么直接所有数异或就是答案,因为偶数次的数异或和为0.

这里有两个数,那么我们可以将异或出来的结果中二进制某一位为1的位置挑出来,然后以这个位置的0 1情况将所有数分为两类,然后每一类里进行异或就是答案。因为这两个数的这个位置01情况一定不同,所以按这样的情况分组。

class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
        int res=0;
        for(auto it=data.begin();it!=data.end();it++) res^=*it;
        int cnt=0;
        for(int i=0;(1<<i)<=2e9+7;i++)
        {
            if((1<<i)&res)    //记录二进制1的位置
            {
                cnt=i;
                break;
            }
        }
        vector<int> v1,v2;
        for(auto it=data.begin();it!=data.end();it++)//分组
        {
            if(*it&(1<<cnt)) v1.push_back(*it);
            else v2.push_back(*it);
        }
        int r1=0,r2=0;
        for(auto it=v1.begin();it!=v1.end();it++) r1^=*it;//分别异或得出答案
        for(auto it=v2.begin();it!=v2.end();it++) r2^=*it;
        *num1=r1,*num2=r2;
    }
};

和为S的连续正数序列

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

class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        int l=1,r=1,res=0;  //双指针扫描
        vector<vector<int> > ans;
        while(r<=sum)
        {
            res+=r;
            while(res>sum) res-=l,l++;//比结果大移动左指针
            if(res==sum&&r-l+1>=2)//符合储存答案
            {
                vector<int> v;
                for(int i=l;i<=r;i++) v.push_back(i);
                ans.push_back(v);
            }
            r++;
        }
        return ans;
    }
};

和为S的两个数字

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

和相同的数,乘积最小必然是从最左开始找到的第一对符合要求的数(感觉还是双指针,因为是排好序的)。

class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> array,int sum) {
        vector<int> ans;
        if(array.size()==0) return ans;
        int head=0,tail=array.size()-1;
        int res=array[head]+array[tail];
        while(head<tail)
        {
            while(res>sum&&head<tail) res-=array[tail--],res+=array[tail];
            while(res<sum&&head<tail) res-=array[head++],res+=array[head];
            if(res==sum)
            {
                ans.push_back(array[head]);
                ans.push_back(array[tail]);
                break;
            }
        }
        return ans;
    }
};

左旋转字符串

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

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

翻转单词顺序列

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

class Solution {
public:
    string ReverseSentence(string str) {
        reverse(str.begin(),str.end());//整体反转
        int ls=str.length();
        int l=0,r=0;
        for(int i=0;i<ls;i++)
        {
            if(str[i]==' ')
            {
                reverse(str.begin()+l,str.begin()+i);//单词局部反转
                l=i+1;
            }
        }
        reverse(str.begin()+l,str.end());
        return str;
    }
};

扑克牌顺子

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<int> numbers ) {
        if(numbers.size()==0) return 0;
        int mi=100,ma=0,cnt=0,x=0;
        int flag=0;
        for(auto it=numbers.begin();it!=numbers.end();it++)
        {
            if(*it!=0)
            {
                mi=min(mi,*it);
                ma=max(ma,*it);
                if((x&(1<<(*it)))==0) x|=(1<<(*it));//位运算储存牌有无
                else flag=1;
            }
            else cnt++; //王的数目
        }
        if(flag) return 0;
        for(int i=mi;i<=ma;i++)//判断是否顺子
        {
            if((x&(1<<i))==0)
            {
                if(cnt) cnt--;
                else flag=1;
            }
        }
        if(flag) return 0;
        return 1;
    }
};

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

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

两种方法,暴力模拟和公式,我这里暴力模拟用的队列,感觉简单一些。

class Solution {
public:
    bool vis[1000005];
    int LastRemaining_Solution(int n, int m)
    {
        if(n<1||m<1) return -1;
        queue<int> que;
        for(int i=0;i<n;i++) que.push(i);
        while(que.size()>1)
        {
            for(int i=0;i<m-1;i++)    //读m-1个数
            {
                que.push(que.front());
                que.pop();
            }
            que.pop();    //第m个弹出
        }
        return que.front(); //返回最后一个数
    }
};

公式法不是很会证明,贴别人的。牛客网

 

求1+2+3+...+n

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

很蒙蔽,说用什么短路求值,以为什么高深的东西 结果就是 A&&B A不满足不执行B

class Solution {
public:
    int Sum_Solution(int n) {
        int res=n;
        res&&(res+=Sum_Solution(n-1));
        return res;
    }
};

不用加减乘除做加法

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

这个题目也很有意思,两个数之和,经过研究可以发现 x^y的结果是 x+y(不带进位),而(x&y)<<1是单纯的进位,那么不停的利用这个性质直到进位为0即可

class Solution {
public:
    int Add(int num1, int num2)
    {
        if(num1&num2) return Add(num1^num2,(num1&num2)<<1);
        else return num1^num2;
    }
};

把字符串转换成整数

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

暴力模拟

class Solution {
public:
    int StrToInt(string str) {
        int ls=str.length(),ans=0;
        if(str[0]=='+'||str[0]=='-'||(str[0]>='0'&&str[0]<='9'))//第一个位置的要求
        {
            int fa=0;    //标记是否正负号
            if(str[0]=='+'||str[0]=='-') fa=1;    //
            if(fa) ans=0;
            else ans=str[0]-'0';
            for(int i=1;i<ls;i++)
                if(str[i]<'0'||str[i]>'9') return 0;
            for(int i=1;i<ls;i++) ans=ans*10+(str[i]-'0');
            if(fa&&str[0]=='-') ans=-ans;
            return ans;
        }
        else return 0;
    }
};

数组中重复的数字

在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2(很有趣的方法,因为所有数0~n-1,当遇到num[i]的时候 可以让 num[num[i]]+=n,这样碰到一个num[num[i]]>length 如果大于n的话就说明重复了)

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) {
        for(int i=0;i<length;i++)
        {
            int x=numbers[i];
            if(x>=length) x-=length;
            if(numbers[x]>=length)
            {
                *duplication=x;
                return 1;
            }
            numbers[x]+=length;
        }
        return 0;
    }
};

构建乘积数组

给定一个数组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<int> multiply(const vector<int>& A) {
        int n=A.size();
        vector<int> v1(n,1),v2(n,1),ans(n,1);
        if(n==0) return ans;
        v1[0]=A[0],v2[n-1]=A[n-1];
        for(int i=1;i<n;i++) v1[i]=v1[i-1]*A[i];    //计算前缀和后缀
        for(int i=n-2;i>=0;i--) v2[i]=v2[i+1]*A[i];
        ans[0]=v2[1],ans[n-1]=v1[n-2];//然后计算答案
        for(int i=1;i<n-1;i++) ans[i]=v1[i-1]*v2[i+1];
        return ans;
    }
};

正则表达式匹配

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

class Solution {
public:
    bool check(char *str,char *s,int p1,int p2)
    {
        int lt=strlen(str),ls=strlen(s);
        //printf("%d %d\n",p1,p2);
        if(p1==lt)//str串空了 有一种情况符合要求
        {
            for(int i=p2;i<ls;i+=2)
                if(s[i+1]!='*') return 0;
            return 1;
        }
        if(p2==ls) return 0;//str没空s空了 失败
        if(s[p2+1]!='*'&&(str[p1]==s[p2]||s[p2]=='.'))//没有*只能往前推进
            return check(str,s,p1+1,p2+1);
        if(s[p2+1]=='*')//如果是*号 相同有两种匹配方式
        {
            if(str[p1]==s[p2]||s[p2]=='.') return check(str,s,p1+1,p2)||check(str,s,p1,p2+2);
            return check(str,s,p1,p2+2);//否则只能跳过
        }
        return 0;
    }
    bool match(char* str, char* pattern)
    {
        return check(str,pattern,0,0);
    }
};

 

表示数值的字符串

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

这个题目我的代码应该还有很多缺陷,不过真的感觉没什么办法 应该评论区什么的用自动机做的是对的,我暴力判断。

讨论区:牛客网

class Solution {
public:
    bool isNumeric(char* str)
    {
        bool flag1=0,flag2=0,flag3=0; //分别代表   +-  .  E
        int ls=strlen(str);
        for(int i=0;i<ls;i++)
        {
            if(str[i]=='E'||str[i]=='e')
            {
                if(i==0||i==ls-1||flag3) return 0;    //不能是第一个和最后一个 并且之前没出现过
                flag3=1;
            }
            else if(str[i]=='.')
            {
                if(i==ls-1||flag2||flag3) return 0; //. 和 E都没出现过并且不是最后一位 试了发现能做第一位 
                flag2=1;
            }
            else if(str[i]=='+'||str[i]=='-')
            {
                if(i>0&&(str[i-1]!='E'&&str[i-1]!='e')) return 0;//+-前一位不能是E 或者 e
                flag1=1;
            }
            else if(str[i]<'0'||str[i]>'9') return 0;
        }
        return 1;
    }
};

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

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

直接将字符推入队列 同时一个数组计数,弹出队列头时如果出现次数大于1 则循环弹出直到 队列头为只出现1次的字符。

class Solution
{
public:
  //Insert one char from stringstream
    int vis[128];
    queue<char> que;
    void Insert(char ch)
    {
        if(vis[ch]==0) que.push(ch);
        vis[ch]++;
    }
  //return the first appearence once char in current stringstream
    char FirstAppearingOnce()
    {
        while(!que.empty())
        {
            char tmp=que.front();
            if(vis[tmp]==1) return tmp;
            que.pop();
        }
        return '#';
    }
};

链表中环的入口结点

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

这个很有趣的题目 贴评论区吧 解法很有趣:牛客网

正常的方法

class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        if(pHead->next==NULL||pHead->next->next==NULL) return nullptr;
        ListNode *fast=pHead->next->next;
        ListNode *slow=pHead->next;
        while(fast!=slow)
        {
            if(fast!=NULL&&slow!=NULL)
            {
                fast=fast->next->next;
                slow=slow->next;
            }
            else return NULL;
        }
        fast=pHead;
        while(fast!=slow)
        {
            fast=fast->next;
            slow=slow->next;
        }
        return fast;
    }
};

断链法

class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        if(pHead->next==NULL||pHead->next->next==NULL) return nullptr;
        ListNode *fast=pHead->next;
        ListNode *slow=pHead;
        while(fast!=nullptr)
        {
            slow->next=nullptr;
            slow=fast;
            fast=fast->next;
        }
        return slow;
    }
};

删除链表中重复的结点

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

这个删除重复节点,可以先新建一个空的头节点,然后统一处理即可。

class Solution {
public:
    ListNode* deleteDuplication(ListNode* pHead)
    {
        if(pHead==NULL||pHead->next==NULL) return pHead;
        ListNode *tmp=new ListNode(-1);
        tmp->next=pHead;
        ListNode *p=tmp;
        while(p!=NULL)
        {
            if(p->next!=NULL&&p->next->next!=NULL&&p->next->val==p->next->next->val)
            {
                while(p->next!=NULL&&p->next->next!=NULL&&p->next->val==p->next->next->val) p->next=p->next->next;
                p->next=p->next->next;
            }
            else p=p->next;
        }
        return tmp->next;
    }
};

二叉树的下一个结点

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

如果当前节点有右儿子,则找右儿子最左边的节点。没有则不断返回父亲节点,看当前节点是否是父亲节点的左儿子,如果是,那么就将当前节点做为答案,否则就不断向上找。直到为根,返回空

/*
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->right)    //如果有右儿子
        {
            pNode=pNode->right;
            while(pNode->left) pNode=pNode->left;
            return pNode;
        }
        while(pNode->next)//向上返回
        {
            TreeLinkNode *fa=pNode->next;
            if(fa->left==pNode) return fa;//是否是左儿子
            pNode=fa;
        }
        return NULL;
    }
};

对称的二叉树

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

就,就递归判断啊= = 注意某些特殊的东西 镜像的注意镜像的位置

class Solution {
public:
    bool isSymmetrical(TreeNode* pRoot)
    {
        if(pRoot==NULL) return 1;
        return cmp(pRoot->left,pRoot->right);//检查左右儿子
    }
    bool cmp(TreeNode *lson,TreeNode *rson)
    {
        if(lson==NULL&&rson==NULL) return 1;
        if(lson==NULL&&lson!=rson) return 0;
        if(rson==NULL&&lson!=rson) return 0;//只有当前节点相同 检查下一层 注意镜像性
        if(lson->val==rson->val) return cmp(lson->left,rson->right)&&cmp(lson->right,rson->left);
        return 0;
    }
};

按之字形顺序打印二叉树

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

使用两个栈进行bfs,刚好满足了之字形的要求。

class Solution {
public:
    vector<vector<int> > Print(TreeNode* pRoot) {
        stack<TreeNode*> s1,s2;
        vector<vector<int> > res;
        if(pRoot==NULL) return res;
        int flag=1;//标记使用s1 还是 s2
        s1.push(pRoot);//s1开始
        while(!s1.empty()||!s2.empty())
        {
            vector<int> ans;
            if(flag&1)
            {
                while(!s1.empty())//s1 bfs的值进入s2
                {
                    TreeNode *tmp=s1.top();
                    s1.pop();
                    ans.push_back(tmp->val);
                    if(tmp->left) s2.push(tmp->left);
                    if(tmp->right) s2.push(tmp->right);
                }
                res.push_back(ans);
                flag^=1;
            }
            else
            {
                while(!s2.empty())//同上
                {
                    TreeNode *tmp=s2.top();
                    s2.pop();
                    ans.push_back(tmp->val);
                    if(tmp->right) s1.push(tmp->right);
                    if(tmp->left) s1.push(tmp->left);
                }
                res.push_back(ans);
                flag^=1;
            }
        }
        return res;
    }
};

把二叉树打印成多行

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

用一个队列即可,用一个变量记录下一层的节点数进行bfs

class Solution {
public:
        vector<vector<int> > Print(TreeNode* pRoot) {
            queue<TreeNode*> que;
            que.push(pRoot);
            vector<vector<int> > res;
            if(pRoot==NULL) return res;
            int op=0,ed=1;
            while(!que.empty())
            {
                vector<int> ans;
                int tp=ed;
                while(op<ed)//ed不断更新 当前层的节点
                {
                    TreeNode *tmp=que.front();
                    que.pop();
                    ans.push_back(tmp->val);
                    if(tmp->left) que.push(tmp->left),tp++;//tp不断更新个数
                    if(tmp->right) que.push(tmp->right),tp++;
                    op++;
                }
                ed=tp;
                res.push_back(ans);
            }
            return res;
        }
};

序列化二叉树

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

很奇怪的题目,其实并不难,主要是字符串和数字的转换很恶心,但是跟着大佬学了一种神奇的方法。

class Solution {
public:
    vector<int> res;
    void dfs1(TreeNode *root)
    {
        if(root==nullptr)//空节点存入-1
        {
            res.push_back(-1);
            return ;
        }
        res.push_back(root->val);
        dfs1(root->left);
        dfs1(root->right);
    }
    TreeNode* dfs2(int* &p)
    {
        if(*p==-1)//空节点 return
        {
            p++;
            return nullptr;
        }
        TreeNode *root=new TreeNode(*p);//不断扩展二叉树
        p++;
        root->left=dfs2(p);
        root->right=dfs2(p);
        return root;
    }
    char* Serialize(TreeNode *root) {
        res.clear();
        dfs1(root); //按照前序遍历序列化 结果存入res
        int *s=new int[res.size()];
        for(int i=0;i<(int)res.size();i++) s[i]=res[i];
        return (char*)s;//直接强制转换
    }
    TreeNode* Deserialize(char *str) {
        int *p=(int*)str;
        TreeNode *root=dfs2(p);//得到原二叉树
        return root;
    }
};

二叉搜索树的第k个结点

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

class Solution {
public:
    int s=0;//s记录遍历的节点数
    TreeNode* KthNode(TreeNode* pRoot, int k)
    {
        if(pRoot==NULL) return NULL;
        TreeNode *tmp=KthNode(pRoot->left,k);//正常遍历节点
        if(tmp!=NULL) return tmp;
        s++;//如果s==k 则当前节点为答案
        if(s==k) return pRoot;
        return KthNode(pRoot->right,k);//返回右子树的答案
    }
};

数据流中的中位数

不断输入数据并且不断返回排好序的中位数

利用两个优先队列,一个从大到小,一个从小到大,并动态调整他们的大小差小于等于1,如果两个队列中的数个数相同 返回均分。否则返回大小多1的那个队列头。

class Solution {
public:
    int cnt=0;
    priority_queue<int> que1;
    priority_queue<int,vector<int>,greater<int> > que2;
    void Insert(int num)
    {
        que1.push(num);//推入que1
        while(que1.size()>que2.size()+1) que2.push(que1.top()),que1.pop();//动态调整
        if(!que1.empty()&&!que2.empty()&&que1.top()>que2.top()) que2.push(que1.top()),que1.pop();
    }
    double GetMedian()
    {
        if(que1.size()==que2.size()) return ((que1.top()+que2.top())*1.0/2);
        else if(que1.size()>que2.size()) return que1.top();
        else return que2.top();
        return 0;
    }
};

滑动窗口的最大值

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{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)
    {
        int sz=size,n=num.size();
        deque<int> que;
        vector<int> res;
        for(int i=0;i<n;i++)
        {
            while(!que.empty()&&num[que.back()]<num[i]) que.pop_back();
            while(!que.empty()&&i-que.front()>=sz) que.pop_front();
            que.push_back(i);
            if(sz&&i>=sz-1) res.push_back(num[que.front()]);
        }
        return res;
    }
};

矩阵中的路径

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

class Solution {
public:
    int ls=0;
    bool vis[1005][1005];
    int dx[4]={1,0,-1,0};
    int dy[4]={0,1,0,-1};
    bool findpath(char *matrix,int x,int y,int row,int col,int cnt,char *str)
    {
        if(matrix[x*col+y]!=str[cnt]) return 0;
        if(cnt==ls-1&&matrix[x*col+y]) return 1;
        for(int i=0;i<4;i++)//延伸dfs
        {
            int xx=x+dx[i];
            int yy=y+dy[i];
            if(xx<0||xx>=row||yy<0||yy>col) continue;
            if(vis[xx][yy]) continue;
            vis[xx][yy]=1;
            if(findpath(matrix,xx,yy,row,col,cnt+1,str)) return 1;
            vis[xx][yy]=0;
        }
        return 0;
    }
    bool hasPath(char* matrix, int rows, int cols, char* str)
    {
        memset(vis,0,sizeof vis);
        ls=strlen(str);
        if(rows*cols<ls) return 0;
        if(ls==0) return 1;
        for(int i=0;i<rows;i++)
        {
            for(int j=0;j<cols;j++)
            {
                if(matrix[i*cols+j]==str[0])
                {
                    vis[i][j]=1;
                    if(findpath(matrix,i,j,rows,cols,0,str)) return 1;//从当前点出发是否可行
                    vis[i][j]=0;
                }
            }
        }
        return 0;
    }
};

机器人的运动范围

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

class Solution {
public:
    map< pair<int,int>,bool> mp;//存当前点是否出现过
    int dx[4]={0,1,0,-1};
    int dy[4]={1,0,-1,0};
    int cal(int x)//计算数各位和
    {
        int res=0;
        while(x)
        {
            res+=(x%10);
            x=x/10;
        }
        return res;
    }
    bool check(int xx,int yy,int k)//检查座标的合法性
    {
        if((cal(xx)+cal(yy))>k) return 0;
        return 1;
    }
    int movingCount(int threshold, int rows, int cols)
    {
        mp.clear();
        queue<pair<int,int>> que;
        que.push({0,0});
        int ans=0; mp[{0,0}]=1;
        if(threshold<0) return 0;
        while(!que.empty())//bfs
        {
            pair<int,int> tmp=que.front();
            que.pop(); ans++;
            for(int i=0;i<4;i++)
            {
                int xx=tmp.first+dx[i];
                int yy=tmp.second+dy[i];
                if(xx<0||xx>=rows||yy<0||yy>=cols) continue;
                if(check(xx,yy,threshold)&&mp[{xx,yy}]!=1)
                    que.push({xx,yy}),mp[{xx,yy}]=1;
            }
        }
        return ans;
    }
};

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章