leetcode刷题记录23(2023-09-17)【x的平方根(二分法、防止临时变量int超出范围 | 解码方法(一维dp) | 杨辉三角(模拟、数组优化) | 单词拆分II(回溯、记忆化搜索))】

69. x 的平方根

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

示例 1:

输入:x = 4
输出:2
示例 2:

输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842…, 由于返回类型是整数,小数部分将被舍去。

提示:

0 < = x < = 2 31 − 1 0 <= x <= 2^{31} - 1 0<=x<=2311

假设初始二分区间从1到x,然后进行二分;

需要注意计算mid的时候越界的问题,防止临时变量超出int范围,代码如下:

#include <iostream>

using namespace std;

// 二分查找
class Solution
{
public:
    int mySqrt(int x)
    {
        int left = 1, right = x;
        int ans = 0;
        while (left <= right)
        {
            // int mid = (left + right) / 2; // 会 overflow(1 + 2147483647 cannot be represented in type 'int')
            int mid = left + (right - left) / 2; // 不会 overflow
            if (mid <= x / mid)
            {
                ans = mid;
                left = mid + 1;
            }
            else
            {
                right = mid - 1;
            }
        }
        return ans;
    }
};

int main()
{
    Solution sol;
    int res = sol.mySqrt(5);
    cout << res;
    return 0;
}

91. 解码方法

一条包含字母 A-Z 的消息通过以下映射进行了 编码 :

‘A’ -> “1”
‘B’ -> “2”

‘Z’ -> “26”
要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,“11106” 可以映射为:

“AAJF” ,将消息分组为 (1 1 10 6)
“KJF” ,将消息分组为 (11 10 6)
注意,消息不能分组为 (1 11 06) ,因为 “06” 不能映射为 “F” ,这是由于 “6” 和 “06” 在映射中并不等价。

给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。

题目数据保证答案肯定是一个 32 位 的整数。

示例 1:

输入:s = “12”
输出:2
解释:它可以解码为 “AB”(1 2)或者 “L”(12)。
示例 2:

输入:s = “226”
输出:3
解释:它可以解码为 “BZ” (2 26), “VF” (22 6), 或者 “BBF” (2 2 6) 。
示例 3:

输入:s = “06”
输出:0
解释:“06” 无法映射到 “F” ,因为存在前导零(“6” 和 “06” 并不等价)。

提示:

1 <= s.length <= 100
s 只包含数字,并且可能包含前导零。

这道题目可以利用动态规划进行解决,dp[i] 表示长度为 i 的前缀子串的可能情况;

状态转移方程为:

d p [ i ] = 第 i 位是否满足 ? d p [ i − 1 ] : 0 + 第 i − 1 位和第 i 位组合到一起是否满足 ? d p [ i − 1 ] : 0 ; dp[i] = 第i位是否满足?dp[i-1]:0+第i-1位和第i位组合到一起是否满足?dp[i-1]:0; dp[i]=i位是否满足?dp[i1]:0+i1位和第i位组合到一起是否满足?dp[i1]:0;

初始化: d p [ 0 ] dp[0] dp[0] 表示长度为 0 的子串。解码也为空,所以只有一种可能,所以dp[0] = 1。

代码如下:

class Solution
{
public:
    int numDecodings(string s)
    {
        int length = s.size();
        vector<int> dp(length + 1);
        dp[0] = 1;
        dp[1] = s[0] >= '1' && s[0] <= '9' ? 1 : 0;
        for (int i = 2; i <= length; i++)
        {
            int first = s[i - 2] - '0';
            int second = s[i - 1] - '0';
            if (second >= 1 && second <= 9)
            {
                dp[i] += dp[i - 1];
            }
            if (first > 0 && second + first * 10 >= 1 && second + first * 10 <= 26)
            {
                dp[i] += dp[i - 2];
            }
        }
        return dp[length];
    }
};

由于动态规划数组只和上一位和上两位有关系,所以可以将数组优化为3个变量,进一步节约空间:

class Solution
{
public:
    int numDecodings(string s)
    {
        int length = s.size();
        int dp0 = 1;
        int dp1 = s[0] >= '1' && s[0] <= '9' ? 1 : 0;
        for (int i = 2; i <= length; i++)
        {
            int first = s[i - 2] - '0';
            int second = s[i - 1] - '0';
            int tmp = 0;
            if (second >= 1 && second <= 9)
            {
                tmp += dp1;
            }
            if (first > 0 && second + first * 10 >= 1 && second + first * 10 <= 26)
            {
                tmp += dp0;
            }
            dp0 = dp1;
            dp1 = tmp;
        }
        return dp1;
    }
};

118. 杨辉三角

已解答
简单
相关标签
相关企业
给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。

在「杨辉三角」中,每个数是它左上方和右上方的数的和。

在这里插入图片描述

示例 1:

输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]

示例 2:

输入: numRows = 1
输出: [[1]]

提示:

1 < = n u m R o w s < = 30 1 <= numRows <= 30 1<=numRows<=30

自己的代码,中间vector空间大小处理的不好,存在内存的多次拷贝情况。

class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<int> vec = {1};
        vector<vector<int>> res;
        res.push_back(vec);

        for(int i=1; i< numRows;i++){
            vec = vector<int>(i+1);
            vec[0] = 1;
            for(int j=1; j<i;j++){
                vec[j] = res[i-1][j-1] + res[i-1][j];
            }
            vec[vec.size()-1] = 1;
            res.push_back(vec);
        }
        return res;
    }
};

答案的做法在内存处理上一步到位,利用resize去除掉了预留的空间,更加合理。

// 参考题解,进行了一点代码优化
class Solution
{
public:
    vector<vector<int>> generate(int numRows)
    {
        vector<vector<int>> res(numRows);
        for (int i = 0; i < numRows; i++)
        {
            res[i].resize(i + 1);
            res[i][0] = res[i][i] = 1;
            for (int j = 1; j < i; j++)
            {
                res[i][j] = res[i - 1][j - 1] + res[i - 1][j];
            }
        }
        return res;
    }
};

116. 填充每个节点的下一个右侧节点指针

给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:

struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。

初始状态下,所有 next 指针都被设置为 NULL。

示例 1:

在这里插入图片描述

输入:root = [1,2,3,4,5,6,7]
输出:[1,#,2,3,#,4,5,6,7,#]
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化的输出按层序遍历排列,同一层节点由 next 指针连接,‘#’ 标志着每一层的结束。

示例 2:

输入:root = []
输出:[]

提示:

树中节点的数量在 [ 0 , 2 12 − 1 ] [0, 2^{12} - 1] [0,2121] 范围内
-1000 <= node.val <= 1000

进阶:

你只能使用常量级额外空间。
使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。

自己写的有点乱,利用nullptr来进行每一层节点的区分,其实没必要。题解利用for循环的做法更加巧妙。

#include <queue>

using namespace std;

// Definition for a Node.
class Node
{
public:
    int val;
    Node *left;
    Node *right;
    Node *next;

    Node() : val(0), left(nullptr), right(nullptr), next(nullptr) {}

    Node(int _val) : val(_val), left(nullptr), right(nullptr), next(nullptr) {}

    Node(int _val, Node *_left, Node *_right, Node *_next)
        : val(_val), left(_left), right(_right), next(_next) {}
};

class Solution
{
public:
    Node *connect(Node *root)
    {
        queue<Node *> que;
        if (root == nullptr)
        {
            return root;
        }
        que.push(root);
        que.push(nullptr);
        while (!que.empty())
        {
            Node *tmp = que.front();
            // 如果遇到了一个 nullptr,那么说明一层已结束,加入一个 nullptr
            if (tmp == nullptr)
            {
                que.pop();
                if (!que.empty())
                    que.push(nullptr);
                continue;
            }
            // 如果 tmp 不是 nullptr,那么把它的左右指针加入进来
            if (tmp->left != nullptr)
                que.push(tmp->left);
            if (tmp->right != nullptr)
                que.push(tmp->right);
            que.pop();
            if (!que.empty())
            {
                tmp->next = que.front();
            }
        }
        return root;
    }
};

用Java来写的:

// Definition for a Node.
class Node {
    public int val;
    public Node left;
    public Node right;
    public Node next;

    public Node() {
    }

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val, Node _left, Node _right, Node _next) {
        val = _val;
        left = _left;
        right = _right;
        next = _next;
    }
};

class Solution {
    public Node connect(Node root) {
        if (root == null) {
            return null;
        }

        Queue<Node> queue = new LinkedList<>();
        // List<Node> queue = new LinkedList<>();
        queue.add(root);

        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                // 取出队列头部的元素
                Node curr = queue.poll();
                if (i < size - 1) {
                    // 如果不是当前层的最后一个元素,那么将其next指向队列头部的元素
                    curr.next = queue.peek();
                }
                // 将当前节点的左右子节点加入队列
                if (curr.left != null) {
                    queue.add(curr.left);
                }
                if (curr.right != null) {
                    queue.add(curr.right);
                }
            }
        }
        // 返回根节点
        return root;
    }
}

第二种做法,也很简洁,没有利用队列,利用上一行的链表,生成下一行的链表来实现的。

// Definition for a Node.
class Node {
    public int val;
    public Node left;
    public Node right;
    public Node next;

    public Node() {
    }

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val, Node _left, Node _right, Node _next) {
        val = _val;
        left = _left;
        right = _right;
        next = _next;
    }
};

class Solution {
    public Node connect(Node root) {
        if (root == null) {
            return null;
        }
        // 从根节点开始,每一行最左边的节点
        Node leftMost = root;
        while (leftMost != null) {
            // 遍历一行的头指针
            Node head = leftMost;
            while (head != null) {
                if (head.left != null) {
                    head.left.next = head.right;
                }
                if (head.right != null && head.next != null) {
                    head.right.next = head.next.left;
                }
                head = head.next;
            }
            leftMost = leftMost.left;
        }
        return root;
    }
}

140. 单词拆分 II

给定一个字符串 s 和一个字符串字典 wordDict ,在字符串 s 中增加空格来构建一个句子,使得句子中所有的单词都在词典中。以任意顺序 返回所有这些可能的句子。

注意:词典中的同一个单词可能在分段中被重复使用多次。

示例 1:

输入:s = “catsanddog”, wordDict = [“cat”,“cats”,“and”,“sand”,“dog”]
输出:[“cats and dog”,“cat sand dog”]

示例 2:

输入:s = “pineapplepenapple”, wordDict = [“apple”,“pen”,“applepen”,“pine”,“pineapple”]
输出:[“pine apple pen apple”,“pineapple pen apple”,“pine applepen apple”]
解释: 注意你可以重复使用字典中的单词。

示例 3:

输入:s = “catsandog”, wordDict = [“cats”,“dog”,“sand”,“and”,“cat”]
输出:[]

提示:

1 <= s.length <= 20
1 <= wordDict.length <= 1000
1 <= wordDict[i].length <= 10
s 和 wordDict[i] 仅有小写英文字母组成
wordDict 中所有字符串都 不同

思路较为清晰,主要就是利用了dfs的思想:

// dfs 思路
class Solution
{
    void dfs(string s, vector<string> &res, int idx, string &curStr, unordered_set<string> &st)
    {
        if (idx == s.size())
        {
            res.push_back(curStr);
            return;
        }
        for (int i = idx; i < s.size(); i++)
        {
            string str = s.substr(idx, i - idx + 1);
            if (st.find(str) != st.end())
            {
                int pos = curStr.size();
                if (pos > 0)
                {
                    curStr.append(" ");
                }
                curStr.append(str);
                dfs(s, res, i + 1, curStr, st);
                curStr.erase(curStr.begin() + pos, curStr.end());
            }
        }
    }

public:
    vector<string> wordBreak(string s, vector<string> &wordDict)
    {
        unordered_set<string> st(wordDict.begin(), wordDict.end());
        string curStr = "";
        vector<string> res;
        dfs(s, res, 0, curStr, st);
        return res;
    }
};

由于中间进行了大量的重复计算,可以采用记忆化搜索进行优化,如下:

// dfs + 记忆化搜搜
class Solution
{
    unordered_set<string> wordSet;
    unordered_map<int, vector<string>> res;
    void backtrace(string &s, int index)
    {
        if (res.find(index) == res.end())
        {
            if (index == s.size())
            {
                res[index] = {""};
                return;
            }
            res[index] = {}; // 将记录表置为空
            for (int i = index + 1; i <= s.size(); i++)
            {
                string word = s.substr(index, i - index);
                if (wordSet.find(word) != wordSet.end())
                {
                    backtrace(s, i);
                    // 如果res[i]为空,就不会进入for循环
                    for (auto succ : res[i])
                    {
                        res[index].push_back(succ.empty() ? word : word + " " + succ);
                    }
                }
            }
        }
    }

public:
    vector<string> wordBreak(string s, vector<string> &wordDict)
    {
        wordSet = unordered_set(wordDict.begin(), wordDict.end());
        backtrace(s, 0);
        return res[0];
    }
};
  • 17
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cherries Man

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值