leetcode——剑指offer1

1、先序中序重建二叉树——构建中序哈希加快搜索,先序找根,中序划分左右子树

2、树的子结构——遍历树,看当前节点是否为子树结构,子树结构要求根左右均相等直至匹配完毕

3、生成二叉树的镜像——交换节点的左右子树,递归处理左右子树

4、对称的二叉树——递归判断左右子树交叉是否相等,注意递归结构true and false 的条件

5、二叉搜索树与双向链表——中序遍历逻辑,cur->right=root,cur->right->left=cur,cur=cur->right

6、序列化二叉树——序列化:先序遍历逻辑转string,反序列化:string转queue,先序遍历逻辑构建二叉树

7、 I. 从上到下打印二叉树——队列实现基础bfs

8、II. 从上到下打印二叉树 II——ans.emplace_back({}),ans.back().emplace_back()

9、III. 从上到下打印二叉树 III——dequedq,dq.emplace_back(),dq.emplace_front(),ans.emplace_back(vector(dq.begin(),dq.end())

10、二叉搜索树的后序遍历序列——指针p小于根j滑过,指针p大于根j滑过,看指针是否到达p=j,根据m划分左右子树递归判断

11、二叉树中和为某一值的路径——常规dfs,递归终止条件,更新路径,递归左右子树,路径还原

12、I. 二叉树的深度——递归结束条件root==nullptr return 0,返回max(dfs(left),dfs(right))+1

13、二叉搜索树的第k大节点——右根左,使用&k不断k–,当k==0时返回节点

14、II. 平衡二叉树——递归判断树节点是否左右子树深度绝对值之差不超过1

15、 I. 二叉搜索树的最近公共祖先——二叉搜索树直接root与p q对比判断是左子树,右子树,还是当前节点

16、II. 二叉树的最近公共祖先——递归终止条件root==nullptr||root->val==p->val||root->val==q->val,判断左右子树结果,根据左右子树的返回结果判断最近公共祖先`

链表

1、从尾到头打印链表——栈、反转、双端队列头插

2、合并两个排序的链表——不创建头结点完成,cur=head,cur->next=l1/l2…

3、链表中倒数第k个节点——双指针,在一个循环内完成

4、反转链表——原地反转,pre=nullptr,cur->next=pre,pre=cur,cur=next

5、删除链表的节点——注意删除头节点的处理,使用一个cur完成,cur->next->val==val?

6、复杂链表的复制——哈希表,创建节点,建立next和random指针

7、二叉搜索树与双向链表——head和cur,结束head->left=cur,cur->right=head

8、两个链表的第一个公共节点——两个指针交叉走两个链表,while(p1!=p2)

1、用两个栈实现队列——输入栈与输出栈,当输出栈为空才去从输入栈取数

2、从尾到头打印链表——将链表节点压入,再弹出放入向量

3、包含min函数的栈——遇到更小的min压入栈,即min.top()>=x,min.push(x)

4、二叉搜索树与双向链表——右根左入栈,栈弹出创建双向链表

5、栈的压入、弹出序列——模拟栈,每压入一个元素便循环判断是否栈顶等于弹出序列,是则一直弹出

6、二叉搜索树的后序遍历序列——倒序遍历数组,保持递增则压入,出现递减则弹出,栈底为新的root

class Solution {
public:
    bool verifyPostorder(vector<int>& postorder) {
        stack<int> s;
        int root=INT_MAX;
        for(int i=postorder.size()-1;i>=0;i--){
            if(postorder[i]>root)   return false;
            while(!s.empty()&&s.top()>postorder[i]){
                root=s.top();
                s.pop();
            }
            s.push(postorder[i]);
        }
        return true;
    }
};

队列

1、用两个栈实现队列——输入栈与输出栈,当输出栈为空才去从输入栈取数

2、I. 滑动窗口的最大值——双指针+双端队列维护固定窗口大小的非单调递减序列

3、II.队列的最大值——双端队列维护非单调递减序列

1、最小的k个数——维护size为k的大根堆,如果小于堆顶进行交换,最后堆剩下最小的k个数

2、数据流的中位数——大根堆,小根堆,维持平衡,取大根堆与小根堆顶点作平均

3、丑数——取最小因子,判断是否重复,不重复放入堆标记访问过,重复则不作处理

4、I. 滑动窗口的最大值——将val与对应的下班压入大根堆,弹出时看下标是否在窗口范围,不是则连续弹出

二分查找

1、二维数组中的查找——从下往上,从左往右找

2、旋转数组的最小数字——判断mid与right的关系,注意mid==right时,right–

3、数字序列中某一位的数字——确定在几位数,确定在哪个数,确定在数的哪一位

4、数组中的逆序对——归并排序,当左数组大于右数组时,res+=m-i+1

5、和为s的两个数字——有序数组,双指针,小于target移动左指针,大于target移动右指针

6、I. 在排序数组中查找数字——二分查找+中心扩展

7、II. 0~n-1中缺失的数字——标准二分,left=mid+1,right=mid-1(因为right出现了,未出现应在right左边)

位运算

1、二进制中1的个数——n&n-1的次数

class Solution {
public:
    int hammingWeight(uint32_t n) {
        int count = 0;
        while (n) {
            n &= n - 1;
            count++;
        }
        return count;
    }
};

2、I. 数组中数字出现的次数——求所有异或即ab异或,获取ab第一个不同的位数,根据位数进行分组,两个结果即ab

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        //计算所有数字的异或结果  得到a与b的差异
        int ret = 0;
        for (int n : nums)
            ret ^= n;
        //a与b的差异  为1代表某位上两数不同
        int div = 1;
        while ((div & ret) == 0)
            div <<= 1;
        //根据最低位的1 进行异或分组 a与b分到两组  其他数字相同的被分为一组
        int a = 0, b = 0;
        for (int n : nums)
            if (div & n)
                a ^= n;
            else
                b ^= n;
        //输出结果形式
        return vector<int>{a, b};
    }
};

3、II. 数组中数字出现的次数 II——遍历32位作计算,将所有项指定位作加法sum+=(item>>i)&1,%3==1证明是出现一次数字的数位,进行还原ans|=i<<1

class Solution{
public:
    int singleNumber(vector<int>&nums){
        int ans=0;
        //遍历每位数
        for(int i=0;i<32;++i){
            int sum=0;
            //每位数相加
            for(int item:nums)
                sum+=(item>>i)&1;
            //余数是否为1 是则还原1
            if(sum%3==1)
                ans|=1<<i;
        }
        return ans;
    }
};

4、II. 0~n-1中缺失的数字——1~n异或数组的值,得到的结果即缺失的数字

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int sum1=0;
        int sum2=0;
        for(int i=0;i<nums.size();i++){
            sum1^=nums[i];
            sum2^=i+1;
        }
        return sum1^sum2;
    }
};

5、不用加减乘除做加法——a^b作不进位加法,a&b<<1作进位加法

class Solution {
public:
    int add(int a, int b) {
        return b?add(a^b,(unsigned int)(a&b)<<1):a;
    }
};

6、求1+2+…+n——利用&&实现递归代替for while等

class Solution {
    int ans=0;
public:
    int sumNums(int n) {
        bool x=n>1&&sumNums(n-1)>0;
        ans+=n;
        return ans;
    }
};

动态规划

1、I. 斐波那契数列——dp[i]=dp[i-1]+dp[i-2]

2、II. 青蛙跳台阶问题——dp[i]=dp[i-1]+dp[i-2]

3、机器人的运动范围——dp[i][j]=dp[i-1][j] | dp[i][j-1]

4、I. 剪绳子——尽量分成3*3,当余数为1 *4,当余数为2 *2,当余数为0 …

class Solution {
public:
    int cuttingRope(int n) {
        if(n<=3) return n-1;
        int yushu=n%3,zheng=n/3;
        if(yushu==1) return pow(3,zheng-1)*4;
        if(yushu==2) return pow(3,zheng)*2;
        return pow(3,zheng);
    }
};

5、II. 剪绳子 ——可以造成越界,所以一边减一边乘一边取余,终止条件为余数是4 3 2 1…

class Solution {
public:
    int cuttingRope(int n) {
        if(n<=3) return n-1;
        long res=1;
        while(n>4){
            n-=3;
            res*=3;
            res%=1000000007;
        }
        return int(n*res%1000000007);
    }
};

6、正则表达式匹配——字母相等或遇到‘.’,dp[i][j]=dp[i-1][j-1];遇到‘’不匹配,dp[i][j]=dp[i][j-2];遇到’'匹配,dp[i][j]=dp[i-1][j]

class Solution {
public:
    //动态规划 分三种情况递推公式 1.单个字符匹配 2.匹配0个 3.匹配1~n个
    bool isMatch(string s, string p) {
        //1、申请dp数组  dp[i][j]  i为匹配串 j为模式串
        int m = s.size()+1, n = p.size()+1;
        vector<vector<bool>> dp(m, vector<bool>(n, 0));
        //2、dp数组初始化 从字符串的第二个位置找*  每次跳2  只有 a*b*c*r*...类型符合
        dp[0][0] = 1;
        for (int i = 2; i <= n; i+=2)
            if (p[i-1] == '*')
                dp[0][i] = dp[0][i-2];
        //3、根据递推公式计算dp数组
        for (int i = 1; i < m; ++i) {
            for (int j = 1; j < n; ++j) {
                //1.单个字符匹配
                if (s[i - 1] == p[j - 1] || p[j - 1] == '.')
                    dp[i][j] = dp[i - 1][j - 1];
                //遇到* ————匹配0个  或 匹配1~n个
                else if (p[j - 1] == '*') {
                    //2.匹配0个 —— 若不匹配为true 则不匹配
                    if(j > 1&& dp[i][j - 2]==1)
                        dp[i][j] = dp[i][j - 2];
                    //3.匹配1~n个
                    else if(s[i - 1] == p[j - 2] || p[j - 2] == '.')
                        dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[m-1][n-1];
    }
};

7、连续子数组的最大和——dp[i]=max(dp[i-1]+nums[i],nums[i])

8、1~n 整数中 1 出现的次数——想成密码锁,确定cur=0,cur=1,cur>1的递推公式

class Solution {
public:
    //想成密码锁固定一个位置 三种情况的递推公式  cur=0  cur=1  cur=2
    int countDigitOne(int n) {
        int ans = 0;
        //1、初始化四位参数low cur high digit 
        int  low = 0, cur = n % 10, high = n / 10, digit = 1;
        //2、确定循环的终止条件———— high和cur同时为0才终止
        while (high!=0 ||cur != 0) {
            //3、根据三种情况的递推方程进行计算
            if (cur == 0)  ans += high * digit;
            else if (cur == 1) ans += high * digit + low + 1;
            else    ans += (high + 1) * digit;
            //4、更新四位参数 low cur high digit 
            low += cur * digit;
            cur = high % 10;
            high /= 10;
            //5、小心出界 作一个判断
            if(digit<INT_MAX/10)digit *= 10;
        }
        return ans;
    }
};

9、把数字翻译成字符串——dp[i]=dp[i-1]+<dp[i-2]凑成10~25>

10、礼物的最大价值——dp[i][j]=max(dp[i-1][j],dp[i][j-1])+grid[i][j]

11、丑数——dp[i]=min(dp1,min(dp2,dp3))

12、n个骰子的点数——dp[i-1][j]->dp[i][j+1],dp[i][j+2],dp[i][j+3]…

class Solution {
public:
    vector<double> dicesProbability(int n) {
        //初始化
        vector<double> dp(6,1.0/6.0);
        //开始递推
        for(int i=2;i<=n;++i){
            vector<double>ans(5*i+1,0);
            for(int j=0;j<dp.size();++j)
                for(int k=0;k<6;++k)
                    ans[j+k]+=dp[j]*1/6;
            dp=ans;
        }
        return dp;
    }
};

13、股票的最大利润——max(dp[i]=price[i]-minprice)

其他

1、数组中重复的数字——两数之和变种,利用哈希集合查重

2、替换空格——遇到空格,new_string.append("%20")

3、调整数组顺序使奇数位于偶数前面——双指针,一个指针维护处理好的,一个指针维护待处理的

4、顺时针打印矩阵——left,right,top,bottom,注意不要重复输出,对于单行单列不要左转和上转

5、打印从1到最大的n位数——s.resize(n,‘0’),dfs单层逻辑0123456789,最后去除前面的0以及000…

6、II. 和为s的连续正数序列——滑动窗口,当小于target移动右指针,当大于target移动左指针,当等于target保存结果并移动左指针

7、I. 翻转单词顺序——结尾加上空格,遇到空格则分割,最后将向量反转,拼接成字符串

8、左旋转字符串——局部反转,局部反转,整体反转

9、扑克牌中的顺子——排序,计算0的个数,判断扑克牌是否连续,若相等则返回false,否则尝试使用0进行填补

10、圆圈中最后剩下的数字(约瑟夫环)—— dp[i]=(dp[i-1]+m)%i

11、矩阵中的路径——dfs,单层搜索逻辑 bool ans=dfs(i+1,j)||dfs(i,j+1)||dfs(i-1,j)||dfs(i,j-1) return ans;

12、表示数值的字符串——按照规则进行指针移动,特定出现需要使用else return false;

13、数值的整数次方——x=xx,n=n>>1,if(n&1==1) ans=x

class Solution {
public:
    //快速幂
    double myPow(double x, int n) {
        double ans=1.0;
        long t=n;
        //负数问题转正数问题
        if(n<0){
            t=-t;
            x=1/x;
        }
        //一边移位,一边累乘
        while(t!=0){
            if((t&1)==1)
                ans=ans*x;
            x=x*x;//x x2 x4...
            t=t>>1;//1 2 4...
        }
        return ans;
    }
};

14、字符串的排列——标准dfs,单层搜索逻辑0~n,使用vis标记使用过的元素,排序+禁止横向扩展去重

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值