剑指offer刷题笔记--Num61-68

目录

1--扑克牌中的顺子(61) 

2--圆圈中最后剩下的数字(62)

3--股票的最大利润(63)

4--求1+2+...+n(64)

5--不用加减乘除做加法(65)

6--构建乘积数组(66)

7--把字符串转换成整数(67)

8--二叉搜索树的最近公共祖先(68-I)

9--二叉搜索树的最近公共祖先(68-II)


1--扑克牌中的顺子(61) 

主要思路:

        五个数是顺子的充要条件:① 最大值 - 最小值 < 5(大小王除外);② 没有出现重复的值(大小王除外);

        判断是否出现重复的值可以借助 set 容器;

#include <iostream>
#include <vector>
#include <set>

class Solution {
public:
    bool isStraight(std::vector<int>& nums) {
        int max = -1, min = 14; // 初始化最大值和最小值
        for(int num : nums){
            if (num == 0) continue; // 跳过大小王
            if(S.find(num) != S.end()) return false;
            else S.insert(num);
            // 更新最大值和最小值
            max = std::max(max, num); 
            min = std::min(min, num);
            if ((max - min) >= 5) return false; 
        }
        return true;
    }
private:
    std::set<int> S;
};

int main(int argc, char *argv[]){
    Solution S1;
    std::vector<int> test = {1, 2, 3, 4, 5};
    bool res = S1.isStraight(test);
    if(res) std::cout << "true" << std::endl;
    else std::cout << "false" << std::endl;
    return 0;
}

2--圆圈中最后剩下的数字(62)

主要思路:

     直观思路是借助于环形队列或者是环形链表,但需要遍历每一个数,会出现超时的现象;   

// 超时
#include <iostream>
#include <queue>

class Solution {
public:
    int lastRemaining(int n, int m) {
        std::queue<int> q;
        for(int i = 0; i < n; i++) q.push(i);
        while(q.size() != 1){
            for(int j = 1; j < m; j++){
                int tmp = q.front();
                q.pop();
                q.push(tmp);
            }
            q.pop();
        }
        return q.front();
    }
};

int main(int argc, char *argv[]){
    Solution S1;
    int n = 10, m = 17;
    int res = S1.lastRemaining(n, m);
    std::cout << res << " ";
    return 0;
}

        本题是经典的约瑟夫环问题,可通过动态规划解决;

        需要抓住的核心思想是,f(n, m) 和 f(n-1, m) 实质上是同一个数,只不过这个数在 n 序列 和 n - 1 序列中对应的编号不一样,因此需要求出两个编号的对应关系; 

// 超时
#include <iostream>

class Solution {
public:
    int lastRemaining(int n, int m) {
        int dp = 0; // dp[1] = 0;
        for(int i = 2; i <= n; i++){
            dp = (dp + m) % i; // f[i] = (f[i-1] + m) % i;
        }
        return dp;
    }
};

int main(int argc, char *argv[]){
    Solution S1;
    int n = 10, m = 17;
    int res = S1.lastRemaining(n, m);
    std::cout << res << " ";
    return 0;
}

3--股票的最大利润(63)

主要思路:

        本题只能一次买入和买出,因此直观地只需要计算最大利润,即卖出的价格高,买入的价格低,且买入要在卖出之前,可以采用双指针来遍历,也可以采用动态规划来计算;

        定义 dp[i] 表示第 i 天的最大利润,则 dp[i] 与当天卖出得到的利润,以及前一天 dp[i-1]的最大利润有关 (前i - 1天已完成买入和卖出的操作);

#include <iostream>
#include <vector>

class Solution {
public:
    int maxProfit(std::vector<int>& prices) {
        if(prices.size() <= 1) return 0;

        int dp = 0; // 初始化 dp 为 0, 表示第一天完成买入和卖出,利润为0
        int buy = prices[0]; // 初始化买入的时间
        for(int i = 1; i < prices.size(); i++){    
            // 计算在第 i 天卖出的利润
            int sell = prices[i]; 
            int profit = sell - buy;
            // 更新最大利润
            if (profit > dp) dp = profit;

            // 更新买入的时间
            if(prices[i] < buy) buy = prices[i]; 
        }
        return dp;
    }
};

int main(int argc, char *argv[]){
    Solution S1;
    std::vector<int> test = {7, 1, 5, 3, 6, 4};
    int res = S1.maxProfit(test);
    std::cout << res << " ";
    return 0;
}

4--求1+2+...+n(64)

主要思路:

        利用逻辑运算符的短路性质来确定递归出口;

#include <iostream>

class Solution {
public:
    int sumNums(int n) {
        // 利用逻辑运算符的短路性质来确定递归出口
        // 当 n <= 0 时,逻辑运算符将 n += sumNums(n - 1) 短路,从而结束递归
        bool x = (n > 1) && (n += sumNums(n - 1));
        return n;; 
    }
};

int main(int argc, char *argv[]){
    Solution S1;
    int test = 3;
    int res = S1.sumNums(test);
    std::cout << res << std::endl;

    return 0;
}

5--不用加减乘除做加法(65)

主要思路:

        将整数 a 和 b 的和,拆分为 a 和 b 的无进位加法结果进位结果的和;

        当进位结果为0时,结束加法;

#include <iostream>

class Solution {
public:
    int add(int a, int b) {
        while(b != 0){
            int c = ((a & b) << 1); // 进位
            a = a ^ b; // 无进位和
            b = c; // 更新b,相当于不断执行 (无进位和 + 进位)
        }
        return a; 
    }
};

int main(int argc, char *argv[]){
    int a = 1, b = 1;
    Solution S1;
    int res = S1.add(a, b);
    std::cout << res << std::endl;

    return 0;
}

6--构建乘积数组(66)

主要思路:

        对于位置 i 的元素,使用 L 存储其左边元素([0, i - 1])的乘积,使用 R 存储其右边元素的乘积([i + 1, len - 1]);

        最后将 L 和 R 进行乘积并返回即可;

#include <iostream>
#include <vector>
 
class Solution {
public:
    std::vector<int> constructArr(std::vector<int>& a) {
        if(a.size() <= 1) return a;

        std::vector<int> L = {1};
        for(int i = 1; i < a.size(); i++){ 
            L.push_back(L[i - 1] * a[i - 1]); // 存储左边的乘积
        }
        int R = 1;
        for(int j = a.size() - 2; j >= 0; j--){
            R = R * a[j+1]; // 存储右边的乘积
            L[j] = L[j] * R; // 更新最终的结果
        }
        return L;
    }
}; 
 
int main(){
    std::vector<int> test = {1, 2, 3, 4, 5};
    Solution S1;
    std::vector<int> res = S1.constructArr(test);
    for(int num : res){
        std::cout << num << " ";
    }
 
    return 0;
}

7--把字符串转换成整数(67)

主要思路:

        主要难点在于确定边界条件,防止越界;

#include <iostream>
#include <string>

#define INT_MAX 2147483647
#define INT_MIN -2147483648

class Solution {
public:
    int strToInt(std::string str) {
        if(str.length() <= 0) return 0;

        // 去除前面的空格
        int i = 0;
        while(i < str.length() && str[i] == ' ') i++;

        // 判断第一个字符
        int sign = 1;
        int res = 0;
        if(str[i] == '-'){
            sign = -1; // 负数
            i++;
        }
        else if(str[i] == '+'){
            sign = 1; // 正数
            i++;
        }
        else if(str[i] < '0' || str[i] > '9') return 0; // 非数

        for(; i < str.length(); i++){
            if(str[i] < '0' || str[i] > '9') break;
            if(res > (INT_MAX / 10) || res == (INT_MAX / 10) && str[i] > '7'){
                return sign == 1 ? INT_MAX : INT_MIN;
            }
            int tmp = (str[i] - '0');
            res = res * 10 + tmp;
        }
        return sign * res;
    }
};
 
int main(){
    std::string test = "   -42";
    Solution S1;
    int res = S1.strToInt(test);
    std::cout << res << std::endl;
    return 0;
}

8--二叉搜索树的最近公共祖先(68-I)

主要思路:

        题目提供的是二叉搜索树,即左子树的值比根节点小,右子树的值比根节点大;

        假设 p 和 q 分别在某个根节点的左右子树,则其最近公共祖先为当前根节点;

        假设 p 和 q 处在某个根节点的同一颗子树上,需要遍历下一层来判断,直到 p 和 q 不满足在同一颗子树上;

#include <iostream>
#include <vector>

struct TreeNode {
     int val;
     TreeNode *left;
     TreeNode *right;
     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        while(root){
            // p 和 q 都处在右子树
            if(p->val > root->val && q->val > root->val) root = root->right;
            // p 和 q 都处在左子树
            else if(p->val < root->val && q->val < root->val) root = root->left;
            // 结束遍历
            else break;
        }
        return root;
    }
};

int main(){
    // root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
    TreeNode *Node1 = new TreeNode(6);
    TreeNode *Node2 = new TreeNode(2);
    TreeNode *Node3 = new TreeNode(8);
    TreeNode *Node4 = new TreeNode(0);
    TreeNode *Node5 = new TreeNode(4);
    TreeNode *Node6 = new TreeNode(7);
    TreeNode *Node7 = new TreeNode(9);
    TreeNode *Node8 = new TreeNode(3);
    TreeNode *Node9 = new TreeNode(5);

    Node1->left = Node2;
    Node1->right = Node3;
    Node2->left = Node4;
    Node2->right = Node5;
    Node3->left = Node6;
    Node3->right = Node7;
    Node5->left = Node8;
    Node5->right = Node9;

    Solution S1;
    TreeNode *res = S1.lowestCommonAncestor(Node1, Node2, Node3);
    std::cout << res->val << std::endl;

    return 0;
}

9--二叉搜索树的最近公共祖先(68-II)

主要思路:

        与上题不同,本题提供的是普通二叉树;核心思路是使用深度递归搜索节点 p 和 节点 q,返回其对应的节点;当节点 p 和 节点 q 分属某个根节点的左右子树时,这个根节点是其最近祖先(因为回溯是自底向上,首先遇到的肯定是最近祖先);

        需要注意的是,当在一颗子树找到某一个节点时,是直接返回该节点,无需在同一个子树搜索另一个节点;因为只需要回溯判断上一层,看看另一个节点是否在另一颗子树上即可;如果另一个节点在另一颗子树,则上一层的根节点是最近祖先;如果另一个节点不在另一颗字树上,则另一个节点肯定在刚刚直接返回节点的那颗子树上(即没有继续搜索的那一颗),此时返回节点必定是最近祖先(即节点和最近祖先是同一个节点);(写得有点乱,细想一下就理解了);

#include <iostream>
#include <vector>

struct TreeNode {
    int val;
     TreeNode *left;
     TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == NULL || root->val == p->val || root->val == q->val) return root;
        TreeNode* left = lowestCommonAncestor(root->left, p, q); // 从左子树找节点
        TreeNode* right = lowestCommonAncestor(root->right, p, q); // 从右子树节点
        if(left != NULL && right != NULL){ // p和q分别在root的左右子树上
            return root;
        }
        else if(left != NULL && right == NULL){ // p和q不在root的右子树上
            return left; // 返回找到的节点位置
        }
        else if(left == NULL && right != NULL){ // p和q不在root的左子树上
            return right; // 返回找到的节点位置
        }
        else return NULL;
    }
};

int main(){
    // root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
    TreeNode *Node1 = new TreeNode(3);
    TreeNode *Node2 = new TreeNode(5);
    TreeNode *Node3 = new TreeNode(1);
    TreeNode *Node4 = new TreeNode(6);
    TreeNode *Node5 = new TreeNode(2);
    TreeNode *Node6 = new TreeNode(0);
    TreeNode *Node7 = new TreeNode(8);
    TreeNode *Node8 = new TreeNode(7);
    TreeNode *Node9 = new TreeNode(4);

    Node1->left = Node2;
    Node1->right = Node3;
    Node2->left = Node4;
    Node2->right = Node5;
    Node3->left = Node6;
    Node3->right = Node7;
    Node5->left = Node8;
    Node5->right = Node9;

    Solution S1;
    TreeNode *res = S1.lowestCommonAncestor(Node1, Node2, Node3);
    std::cout << res->val << std::endl;

    return 0;
}

剑1 end in 2023.08.07. (~v~)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值