剑指offer刷题

18.重建二叉树

输入一棵二叉树前序遍历和中序遍历的结果,请重建该二叉树。

class Solution {
public:
    map<int,int> mp;
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int n=inorder.size();
        for(int i=0;i<n;i++){
            mp[inorder[i]]=i;
        }
        return dfs(preorder,inorder,0,n-1,0,n-1);
    }
    TreeNode* dfs(vector<int>& preorder,vector<int>& inorder,int pl,int pr,int il,int ir){
        if(pl>pr) return NULL;
        TreeNode* root=new TreeNode(preorder[pl]);
        int l=mp[preorder[pl]]-il;
        root->left=dfs(preorder,inorder,pl+1,pl+l,il,l+il-1);//长度是l,但是左右是闭区间,右边区间减去一
        root->right=dfs(preorder,inorder,pl+1+l,pr,l+1+il,ir);
        return root;
    }
};

19.二叉树的下一个节点

中序遍历的下一个节点,如果有右儿子,则找右儿子的最左侧节点,没有右儿子,找这个点的父节点,直到找到某个点是它父节点的左儿子。O(h)

class Solution {
public:
    TreeNode* inorderSuccessor(TreeNode* p) {
        if(p->right){
            p=p->right;
            while(p->left) p=p->left;
            return p;
        }
        while(p->father&&p==p->father->right) p=p->father;
        return p->father;
    }
};

23.矩阵中的路径

就是在矩阵中找字符串,dfs很基础的题目,我竟然也不会写了??

class Solution {
public:
    bool hasPath(vector<vector<char>>& matrix, string str) {
        //if(matrix[0].size()==0||matrix.size()==0) return false;
        for(int i=0;i<matrix.size();i++){
            for(int j=0;j<matrix[i].size();j++){
                if(dfs(matrix,str,i,j,0)) return true;
            }
        }
        return false;
    }
    bool dfs(vector<vector<char>>& matrix,string &str,int i,int j,int id){
        if(matrix[i][j]!=str[id]) return false;
        if(id==str.size()-1) return true;
        char c=matrix[i][j];
        matrix[i][j]='*';
        int dx[5]={0,-1,0,1},dy[5]={1,0,-1,0};
        for(int k=0;k<4;k++){
            int a=i+dx[k],b=j+dy[k];
        //一定要注意先写区间的判断条件,再写是否为标记的,会报段错误            
        if(a>=0&&a<matrix.size()&&b>=0&&b<matrix[a].size()&&matrix[a][b]!='*')
                  if(dfs(matrix,str,a,b,id+1)) return true;
                 //一定要判断是否为真,dfs是逐个返回的,一个套一个,
                 //不能只最后一个返回真,不判断等于白干,还是没学明白!
        }
        matrix[i][j]=c;
        return false;
    }
};

27.数值的整数次方

快速幂也不会写了,负数可以写成unsigned int再取负号,就不会越界。

class Solution {
public:
    double Power(double base, int exponent) {
        unsigned int n=exponent;
        double ans=1;
        //cout<<n<<endl;
        if(exponent<0) n=-exponent; 
        //cout<<n<<endl;
        while(n){
            if(n&1) ans=ans*base;
            n>>=1;
            base=base*base;
        }
        if(exponent<0) ans=1.0/ans;
        return ans;
    }
};

29.删除链表中重复的元素

处理头结点,设置两个指针,p指向上一段的最后一个结点,q用来寻找接下来一段相同数字的后一个结点。如果这段长度等于一,则p可以指向这个段,后移一位;大于一,可以将p->next指向q结点,继续判断下一段,这样就可以直接把中间相同的都删掉,只用next指向即可。

见到这种题,先想指针应该怎么指

p上一段的最后一个节点,因为p所指的都要,p->next是下一段,可能被选,前提是长度等于1;

q一整段相同数字的下一个节点,因为p可以直接指向,上一段已经都相同,直接处理掉,q永远指向新的段。

class Solution {
public:
    ListNode* deleteDuplication(ListNode* head) {
        
        if(head==NULL) return NULL;
        ListNode *pre=new ListNode(-1); 
        pre->next=head;
        
        ListNode *p=pre,*q=head;
        while(p->next){
            while(q&&p->next->val==q->val) q=q->next;
            if(p->next->next==q) p=p->next;
            else p->next=q;
        }
        
        return pre->next;
    }
};

 35.反转链表

可以头插法,指针全指向前面,递归res函数一点和后面一段

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head==NULL) return NULL;
        ListNode *pre=new ListNode(-1);
        pre->next=head;
        ListNode *p=pre->next,*r=pre->next;
        while(p){
            if(r->next) r=r->next;
            else r=NULL;
            if(p==pre->next) p->next=NULL;
            else p->next=pre->next;
            pre->next=p;
            p=r;
        }
        return pre->next;
    }
};

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *prev = nullptr;
        ListNode *cur = head;
        while (cur)
        {
            ListNode *next = cur->next;
            cur->next = prev;
            prev = cur, cur = next;
        }
        return prev;
    }
};

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if (!head || !head->next) return head;
        ListNode *tail = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return tail;
    }
};

37.树的子结构

我们同时从根节点开始遍历两棵子树:

    如果树B中的节点为空,则表示当前分支是匹配的,返回true;
    如果树A中的节点为空,但树B中的节点不为空,则说明不匹配,返回false;
    如果两个节点都不为空,但数值不同,则说明不匹配,返回false;
    否则说明当前这个点是匹配的,然后递归判断左子树和右子树是否分别匹配即可;

//贴个y总的代码
class Solution {
public:
    bool hasSubtree(TreeNode* pRoot1, TreeNode* pRoot2) {
        if (!pRoot1 || !pRoot2) return false;
        if (isSame(pRoot1, pRoot2)) return true;
        return hasSubtree(pRoot1->left, pRoot2) || hasSubtree(pRoot1->right, pRoot2);
    }

    bool isSame(TreeNode* pRoot1, TreeNode* pRoot2) {
        if (!pRoot2) return true;
        if (!pRoot1 || pRoot1->val != pRoot2->val) return false;
        return isSame(pRoot1->left, pRoot2->left) && isSame(pRoot1->right, pRoot2->right);
    }
};

38.二叉树的镜像

swap可以交换二叉树的两个指针哦

class Solution {
public:
    void mirror(TreeNode* root) {
        if(!root) return ;
        swap(root->left,root->right);
        mirror(root->left);
        mirror(root->right);
    }
};

39.对称的二叉树

用栈模拟递归,对根节点的左子树,我们用中序遍历;对根节点的右子树,我们用反中序遍历。
则两个子树互为镜像,当且仅当同时遍历两课子树时,对应节点的值相等。

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if (!root) return true;
        stack<TreeNode*> left, right;
        TreeNode *lc = root->left;
        TreeNode *rc = root->right;
        while(lc || rc || left.size()){
            while (lc && rc){
                left.push(lc), right.push(rc);
                lc = lc->left, rc = rc->right;
            }
            if (lc || rc) return false;
            lc = left.top(), rc = right.top();
            left.pop(), right.pop();
            if (lc->val != rc->val) return false;
            lc = lc->right, rc = rc->left;
        }
        return true;
    }
};

41.分行从上到下打印二叉树

每行的结尾多设置一个null记录。

class Solution {
public:
    vector<vector<int>> printFromTopToBottom(TreeNode* root) {
        vector<vector<int>> res;
        vector<int> cur;
        if (!root) return res;
        queue<TreeNode*> q;
        q.push(root);
        q.push(NULL);
        while (q.size()) {
            auto t = q.front();
            q.pop();
            if(t){
                cur.push_back(t->val);
                if (t->left) q.push(t->left);
                if (t->right) q.push(t->right);   
            }
            else{
                //一定要注意这里需要判断q是否为空,如果不判断的话,就一直在加null,会报内存超限的错。
                if(q.size()) q.push(NULL);
                res.push_back(cur);
                cur.clear();
            }
        }

        return res;
    }
};

53.最小的k个数

优先队列默认是大顶堆,小顶堆用greater。

//大顶堆
priority_queue<int,vector<int>,less<int> >que;
//小顶堆
priority_queue<int,vector<int>,greater<int> >que;
class Solution {
public:
    vector<int> getLeastNumbers_Solution(vector<int> input, int k) {
        vector<int> res;
        priority_queue<int> heap;
        for(auto x : input)
        {
            heap.push(x);
            if(heap.size() > k) heap.pop(); 
        }
        while(heap.size())
        {
            //cout<<heap.top()<<endl;
            res.push_back(heap.top());
            heap.pop();
        }
        reverse(res.begin(),res.end());
        return res;
    }
};

55.连续子数组的最大和

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int sum = 0, maxv = 0xcfcfcfcf;             //求最大值,max初始化为负无穷
        for (int i = 0; i < nums.size(); i ++ )     //遍历数组
        {
            sum += nums[i];
            maxv = max(maxv, sum);                  //一边累加sum一边更新最大值
            if (sum < 0) sum = 0;                   //当sum小于0,则丢弃重开
            //这里你可能会问为什么sum小于0就丢弃,如果数组里所有的数都是负数呢?
            //答:若数组中全是负数,那么最大值就是最小的那一个负数,因为负数越累加越小。
        }
        return maxv;
    }
};

75.和为s的两个数

最后return要用大括号扩住

class Solution {
public:
    vector<int> findNumbersWithSum(vector<int>& nums, int target) 
    {
        unordered_map<int, int> hash;//创建哈希表
        for (int i = 0; i < nums.size(); ++i) {
            if(hash[target - nums[i]] == 0)//如果哈希表中没有target - nums[i]
                hash[nums[i]]++;//nums[i]出现次数加1
            else//如果哈希表中有target - nums[i]
                return {nums[i], target - nums[i]};//返回答案
        }
        return {};

    }
};

79.滑动窗口的最大值

单调队列,双端队列,维护队列单调递减,把队列后面小于当前值的元素都删掉。

多画图想想,如果不是删末尾的将导致不知道中间的最大值是啥。

class Solution {
public:
    vector<int> maxInWindows(vector<int>& nums, int k) {
        deque<int> q;
        vector<int> ans;
        for(int i=0;i<nums.size();i++){
            while(q.size()&&(i-q.front()>=k)) q.pop_front();
            while(q.size()&&nums[q.back()]<=nums[i]) q.pop_back();
            q.push_back(i);
            if(i>=k-1) ans.push_back(nums[q.front()]);
        }
        return ans;
    }
};

66.两个链表的公共结点

很神奇的思想,一定要记住,a+c+b和b+c+a。

class Solution {
public:
    ListNode *findFirstCommonNode(ListNode *headA, ListNode *headB) {
        ListNode *p=headA,*q=headB;
        while(q!=p){
            if(p) p=p->next;
            else p=headB;
            if(q) q=q->next;
            else q=headA;
        }
        return p;
    }
};

46.二叉搜索树的后序遍历

直接判断该序列能否构成一个二叉搜索树即可,最后一个节点为根节点,然后找左边的都比它小,右边的都比它大,符合条件即可。

class Solution {
public:
    bool verifySequenceOfBST(vector<int> sequence) {
        if(sequence.size()==0) return true;
        return dfs(0,sequence.size()-1,sequence);
    }
    bool dfs(int l,int r,vector<int> sequence){
        if(l>=r) return true;
        int x=sequence[r],k=l;
        while(k<r&&sequence[k]<x) k++;
        for(int i=k;i<r;i++){
            if(sequence[i]<x) return false;
        }
        return dfs(l,k-1,sequence)&&dfs(k,r-1,sequence);
    }
};

51.数字排列

先把数字按照大小顺序排列,dfs枚举每个数字一次,填到空白的位置上,记录一个pre,如果两个数字相同,则是上个数的位置下标+1,如果不相同,则为0。

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    vector<bool> st;
    vector<vector<int>> permutation(vector<int>& nums) {
        //path.resize(nums.size());
        //st.resize(nums.size());
        
        path=vector<int>(nums.size(),0);
        st=vector<bool>(nums.size(),false);
        
        sort(nums.begin(),nums.end());
        dfs(nums,0,0);//pre上个相同数字的位置,确保不重复
        return ans;
    }
    void dfs(vector<int>& nums,int u,int pre){
        if(u==nums.size()){
            ans.push_back(path);
            return ;
        }
        for(int i=pre;i<nums.size();i++){
            if(!st[i]){
                st[i]=true;
                path[i]=nums[u];
                if(u+1<nums.size()&&nums[u+1]!=nums[u]){
                    dfs(nums,u+1,0);
                }
                else{
                    dfs(nums,u+1,i+1);
                }
                st[i]=false;
            }
        }
    }
};

88. 树中两个结点的最低公共祖先

分别在某结点的左右子树上找p,q,如果p,q分别在左右子树上,则root就是最近公共祖先;如果两个都在同一侧继续遍历这一侧。时间复杂度是o(n)

递归:

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(!root)
            return NULL;
        if(root==p||root==q)
            return root;
        TreeNode* left = lowestCommonAncestor(root->left, p, q);
        TreeNode* right = lowestCommonAncestor(root->right, p, q);
        if(left&&right)
            return root;
        if(left==NULL)
            return right;
        else
            return left;
    }
};

非递归:

class Solution {
public:
    int findPath(TreeNode*root, TreeNode* p, vector<TreeNode*>&path){
        if(!root)
            return 0;
        if(root->val==p->val){
            path.push_back(root);
            return 1;
        }
        int l = findPath(root->left,p,path);
        int r = findPath(root->right,p,path);
        if(l==1||r==1)
            path.push_back(root);
        return l==1||r==1;
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        vector<TreeNode*>path1,path2;
        findPath(root,p,path1);
        findPath(root,q,path2);
        if(path1.empty()||path2.empty())
            return NULL;
        TreeNode* res =NULL;
        for(int i = 0;i<path1.size();i++){
            if(i>=path1.size()||i>=path2.size())
                break;
            if(path1[path1.size()-1-i]==path2[path2.size()-1-i])
                res = path1[path1.size()-1-i];
            else
                break;
        }
        return res;
    }
};

72. 平衡二叉树

递归左右高度,然后用一个ans记录正确与否

class Solution {
public:
    bool ans=true;

    int h(TreeNode* root){
        if(!root) return 0;
        int l=h(root->left);
        int r=h(root->right);
        if(abs(l-r)>1) ans=false;
        return max(l,r)+1;
    }

    bool isBalanced(TreeNode* root) {
        h(root);
        return ans;
    }
};

64. 字符流中第一个只出现一次的字符

很巧妙的一个题,利用队列维护位置的单调性;

队列和双指针都是经常用到的优化时间的工具;用map记录出现次数,用队列维护位置的单调性。

当进入一个字符,出现字符次数大于一了,就从队头开始删除重复出现元素

查找时,直接返回队头。

class Solution{
public:
    unordered_map<char,int> mp;
    queue<int> q;

    //Insert one char from stringstream
    void insert(char ch){
        if(++mp[ch]>1){
            while(!q.empty()&&mp[q.front()]>1) q.pop();
        }
        else{
            q.push(ch);
        }
    }
    //return the first appearence once char in current stringstream
    char firstAppearingOnce(){
        if(q.empty()) return '#';
        else return q.front();
    }
};

65.数组中的逆序对

递归里面运行完两个mergeSort后,start~mid 和 mid+1~end都是有序的,且两个内部的逆序对都已经算过了,只需要算当前状态的逆序对就行了,不会重复的。

利用归并排序计算,两个数组合并,如果出现第二个数组里的某一个数字大于第一个数组里的当前数字,res+=第一个数组i以后的全部。

class Solution {
public:

    vector<int> temp;
    int inversePairs(vector<int>& nums) {
        return merge(nums,0,nums.size()-1);
    }
    int merge(vector<int>& nums,int l,int r){
        if(l>=r) return 0;
        int i=l,mid=(l+r)/2;
        int j=mid+1;
        int res=merge(nums,l,mid)+merge(nums,mid+1,r);
        temp.clear();
        while(i<=mid&&j<=r){
            if(nums[i]<=nums[j]){
                temp.push_back(nums[i++]);
            }
            else{
                temp.push_back(nums[j++]);
                res+=mid-i+1;
            }
        }
        while(i<=mid) temp.push_back(nums[i++]);
        while(j<=r) temp.push_back(nums[j++]);
        j=0;
        for(int i=l;i<=r;i++){
            nums[i]=temp[j++];
            //cout<<nums[i]<<' ';
        }
        //cout<<endl;
        
        return res;
    }
};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值