代码随想录Day 22| 回溯算法开篇,77.组合、216组合总和Ⅲ、17.电话号码的字母组合

news2024/11/17 15:31:05

提示:DDU,供自己复习使用。欢迎大家前来讨论~

文章目录

  • 回溯算法理论基础
  • 一、理论基础
      • 1.1 什么是回溯法
      • 1.2 回溯法的效率
      • 1.3 回溯法解决的问题
      • 1.4 如何理解回溯法
      • 1.5 回溯法模板
  • 二、题目
    • 题目一:77.组合
      • 解题思路:
      • 回溯法三部曲
      • 组合问题中的剪枝操作
    • 题目二:216.组合总和III
      • 解题思路:
      • 回溯三部曲
      • 剪枝操作:
    • 题目三: 17.电话号码的字母组合
      • 解题思路
        • 数字和字母的映射
        • 回溯法来解决n个for循环的问题
  • 总结


回溯算法理论基础

回溯算法开始

一、理论基础

1.1 什么是回溯法

回溯法也可以叫做回溯搜索法,它是一种搜索的方式。

​ 回溯是递归的副产品,只要有递归就会有回溯。

1.2 回溯法的效率

因为回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质。

那么既然回溯法并不高效为什么还要用它呢?

因为没得选,一些问题能暴力搜出来就不错了,撑死了再剪枝一下,还没有更高效的解法。

1.3 回溯法解决的问题

回溯法,一般可以解决如下几种问题:

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 棋盘问题:N皇后,解数独等等

组合是不强调元素顺序的,排列是强调元素顺序

例如:{1, 2} 和 {2, 1} 在组合上,就是一个集合,因为不强调顺序,而要是排列的话,{1, 2} 和 {2, 1} 就是两个集合了。

记住组合无序,排列有序,就可以了。

1.4 如何理解回溯法

回溯法解决的问题都可以抽象为树形结构

因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度就构成了树的深度

递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。

1.5 回溯法模板

回溯三部曲。

  • 回溯函数模板返回值以及参数

回溯算法中函数返回值一般为void。

再来看一下参数,因为回溯算法需要的参数可不像二叉树递归的时候那么容易一次性确定下来**,所以一般是先写逻辑,然后需要什么参数,就填什么参数。**

void backtracking(参数)
  • 回溯函数终止条件

​ 遍历树形结构一定要有终止条件,所以回溯也有要终止条件。

​ 什么时候达到了终止条件,树中就可以看出,一般来说搜到叶子节点了,也就找到了满足条件的一条答案,把这个答案存放起来,并结束本层递归。

if (终止条件) {
 存放结果;
 return;
}
  • 回溯搜索的遍历过程

回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成的树的深度。

回溯算法理论基础

注意图中,举例集合大小和孩子的数量是相等的!

回溯函数遍历过程伪代码如下:

for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
    处理节点;
    backtracking(路径,选择列表); // 递归
    回溯,撤销处理结果
}

for循环就是遍历集合区间,可以理解一个节点有多少个孩子,这个for循环就执行多少次。

backtracking这里自己调用自己,实现递归。

可以从图中看出for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历,这样就把这棵树全遍历完了,一般来说,搜索叶子节点就是找的其中一个结果了。

分析完过程,回溯算法模板框架如下:

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

这份模板很重要

小结:

  • 回溯和递归是相辅相成的。

  • 回溯法其实就是暴力查找,并不是什么高效的算法。

  • 回溯法解决的问题都可以抽象为树形结构(N叉树),并给出了回溯法的模板。

二、题目

题目一:77.组合

77. 组合

解题思路:

本题是回溯法的经典题目。

直接的想法就是两层for循环,依次输出两个元素。

代码日下:

int n = 4;
for (int i = 1; i <= n; i++) {
    for (int j = i + 1; j <= n; j++) {
        cout << i << " " << j << endl;
    }
}

输入:n = 100, k = 3 那么就三层for循环,代码如下:

代码如下:

int n = 100;
for (int i = 1; i <= n; i++) {
    for (int j = i + 1; j <= n; j++) {
        for (int u = j + 1; u <= n; n++) {
            cout << i << " " << j << " " << u << endl;
        }
    }
}

如果n为100,k为50呢,那就50层for循环,就太多for循环了

此时就会发现虽然想暴力搜索,但是用for循环嵌套连暴力都写不出来!

此时,回溯搜索法来了,虽然回溯法也是暴力,但至少能写出来,不像for循环嵌套k层让人绝望。

Q:那么回溯法怎么暴力搜呢?

要解决 n为100,k为50的情况,暴力写法需要嵌套50层for循环,那么回溯法就用递归来解决嵌套层数的问题

递归来做层叠嵌套(可以理解是开k层for循环),每一次的递归中嵌套一个for循环,那么递归就可以用于解决多层嵌套循环的问题了

回溯法解决的问题都可以抽象为树形结构(N叉树),用树形结构来理解回溯就容易多了

组合问题抽象为如下树形结构:

77.组合
  • 每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围

  • 图中可以发现n相当于树的宽度,k相当于树的深度

  • 图中每次搜索到了叶子节点,我们就找到了一个结果

回溯法三部曲

  • 递归函数的返回值以及参数

在这里要定义两个全局变量,一个用来存放符合条件单一结果,一个用来存放符合条件结果的集合。

vector<vector<int>> result; // 存放符合条件结果的集合
vector<int> path; // 用来存放符合条件结果

整体代码如下:

startIndex 就是防止出现重复的组合。需要startIndex来记录下一层递归,搜索的起始位置。

vector<vector<int>> result; // 存放符合条件结果的集合
vector<int> path; // 用来存放符合条件单一结果
void backtracking(int n, int k, int startIndex)
  • 回溯函数终止条件
if (path.size() == k) {
    result.push_back(path);
    return;
}
  • 单层搜索的过程

回溯法的搜索过程就是一个树型结构的遍历过程,在如下图中,可以看出for循环用来横向遍历,递归的过程是纵向遍历。

77.组合1

for循环每次从startIndex开始遍历,然后用path保存取到的节点i。

for (int i = startIndex; i <= n; i++) { // 控制树的横向遍历
    path.push_back(i); // 处理节点
    backtracking(n, k, i + 1); // 递归:控制树的纵向遍历,注意下一层搜索要从i+1开始
    path.pop_back(); // 回溯,撤销处理的节点
}

完整的代码:

class Solution {
private:
    vector<vector<int>> result; // 存放符合条件结果的集合
    vector<int> path; // 用来存放符合条件结果
    void backtracking(int n, int k, int startIndex) {
        if (path.size() == k) {
            result.push_back(path); //这里是以列表的格式加入进去[[X,X],[X,X],...]
            return;
        }
        for (int i = startIndex; i <= n; i++) {
            path.push_back(i); // 处理节点
            backtracking(n, k, i + 1); // 递归
            path.pop_back(); // 回溯,撤销处理的节点
        }
    }
public:
    vector<vector<int>> combine(int n, int k) {
        result.clear(); // 可以不写
        path.clear();   // 可以不写
        backtracking(n, k, 1);
        return result;
    }
};
  • 时间复杂度: O( n ∗ 2 n n * 2^n n2n)
  • 空间复杂度: O(n)

组合问题中的剪枝操作

剪枝优化:

回溯法虽然是暴力搜索,但也有时候可以有点剪枝优化一下的。

for (int i = startIndex; i <= n; i++) {
    path.push_back(i);
    backtracking(n, k, i + 1);
    path.pop_back();
}

来举一个例子,n = 4,k = 4的话,那么第一层for循环的时候,从元素2开始的遍历都没有意义了。 在第二层for循环,从元素3开始的遍历都没有意义了。

77.组合4

可以剪枝的地方就在递归中每一层的for循环所选择的起始位置

如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了

注意代码中i,就是for循环里选择的起始位置。

for (int i = startIndex; i <= n; i++) {

接下来看一下优化过程如下:

  1. 已经选择的元素个数:path.size();
  2. 还需要的元素个数为: k - path.size();
  3. 在集合n中至多(小于等于这个下标)要从该起始位置 : n - (k - path.size()) + 1,开始遍历

为什么有个+1呢,因为包括起始位置,我们要是一个左闭的集合。

举个例子,n = 4,k = 3, 目前已经选取的元素为0(path.size为0),n - (k - 0) + 1 即 4 - ( 3 - 0) + 1 = 2。

借鉴剪枝找边界的方法 :

  • path.size() : 已经找的个数

  • k-path.size() :还需找的个数

  • 【x, n】的数组长度起码应该是k-path.size()才有继续搜索的可能, 那么就有 n-x+1 = k-path.size() , 解方程得 x = n+1 - (k-path.size()), 而且这个x是可以作为起点往下搜的 也就是for(i = s; i<=x; i++) 这里的x是可以取到的

所以优化之后的for循环是:

for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) // i为本次搜索的起始位置

优化后整体代码如下:

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(int n, int k, int startIndex) {
        if (path.size() == k) {
            result.push_back(path);
            return;
        }
        for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) { // 优化的地方
            path.push_back(i); // 处理节点
            backtracking(n, k, i + 1);
            path.pop_back(); // 回溯,撤销处理的节点
        }
    }
public:

    vector<vector<int>> combine(int n, int k) {
        backtracking(n, k, 1);
        return result;
    }
};

题目二:216.组合总和III

216. 组合总和 III

解题思路:

这个题目和组合题目类似,但是有一个新的条件,得到的符合个数的元素的和为一个值。

回溯三部曲

  • 确定递归函数参数

依然需要一维数组path来存放符合条件的结果,二维数组result来存放结果集。

vector<vector<int>> result; // 存放结果集
vector<int> path; // 符合条件的结果

接下来还需要如下参数:

  • targetSum(int)目标和,也就是题目中的n。
  • k(int)就是题目中要求k个数的集合。
  • sum(int)为已经收集的元素的总和,也就是path里元素的总和。
  • startIndex(int)为下一层for循环搜索的起始位置。避免元素重复
vector<vector<int>> result;
vector<int> path;
void backtracking(int targetSum, int k, int sum, int startIndex)

强调一下,回溯法中递归函数参数很难一次性确定下来,一般先写逻辑,需要啥参数了,填什么参数。

  • 确定终止条件
if (path.size() == k) {
    if (sum == targetSum) result.push_back(path); //这里就是和组合题不一致的地方,判断元素和
    return; // 如果path.size() == k 但sum != targetSum 直接返回
}
  • 单层搜索过程

区别之一就是集合固定的就是9个数[1,…,9],所以for循环固定i<=9

for (int i = startIndex; i <= 9; i++) {
    sum += i;
    path.push_back(i);
    backtracking(targetSum, k, sum, i + 1); // 注意i+1调整startIndex
    sum -= i; // 回溯
    path.pop_back(); // 回溯
}

别忘了处理过程 和 回溯过程是一一对应的,处理有加,回溯就要有减!

完整的代码如下:

class Solution {
private:
    vector<vector<int>> result; // 存放结果集
    vector<int> path; // 符合条件的结果
    // targetSum:目标和,也就是题目中的n。
    // k:题目中要求k个数的集合。
    // sum:已经收集的元素的总和,也就是path里元素的总和。
    // startIndex:下一层for循环搜索的起始位置。
    void backtracking(int targetSum, int k, int sum, int startIndex) {
        if (path.size() == k) {
            if (sum == targetSum) result.push_back(path);
            return; // 如果path.size() == k 但sum != targetSum 直接返回
        }
        for (int i = startIndex; i <= 9; i++) {
            sum += i; // 处理
            path.push_back(i); // 处理
            backtracking(targetSum, k, sum, i + 1); // 注意i+1调整startIndex
            sum -= i; // 回溯
            path.pop_back(); // 回溯
        }
    }

public:
    vector<vector<int>> combinationSum3(int k, int n) {
        result.clear(); // 可以不加
        path.clear();   // 可以不加
        backtracking(n, k, 0, 1);
        return result;
    }
};

剪枝操作:

216.组合总和III1

已选元素总和如果已经大于n(图中数值为4)了,那么往后遍历就没有意义了,直接剪掉。

那么剪枝的地方可以放在递归函数开始的地方,剪枝代码如下:

if (sum > targetSum) { // 剪枝操作
    return;
}

剪枝也可以放在 调用递归之前,即放在这里,只不过要记得 要回溯操作给做了。

for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) { // 剪枝
    sum += i; // 处理
    path.push_back(i); // 处理
    if (sum > targetSum) { // 剪枝操作
        sum -= i; // 剪枝之前先把回溯做了
        path.pop_back(); // 剪枝之前先把回溯做了
        return;
    }
    backtracking(targetSum, k, sum, i + 1); // 注意i+1调整startIndex
    sum -= i; // 回溯
    path.pop_back(); // 回溯
}

for循环的范围也可以剪枝,==i <= 9 - (k - path.size()) + 1==就可以了。

最后C++代码如下:

note:

  • path.push_back(i)和path.pop_back(), 一个需要写值,一个不需要直接pop掉最后一个元素。
  • backtracking(targetSum, k, sum, i + 1); // 注意i+1调整startIndex,并不是startIndex+1
class Solution {
private:
    vector<vector<int>> result; // 存放结果集
    vector<int> path; // 符合条件的结果
    void backtracking(int targetSum, int k, int sum, int startIndex) {
        if (sum > targetSum) { // 剪枝操作
            return; 
        }
        if (path.size() == k) {
            if (sum == targetSum) result.push_back(path);
            return; // 如果path.size() == k 但sum != targetSum 直接返回
        }
        for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) { // 剪枝
            sum += i; // 处理
            path.push_back(i); // 处理
            backtracking(targetSum, k, sum, i + 1); // 注意i+1调整startIndex
            sum -= i; // 回溯
            path.pop_back(); // 回溯
        }
    }

public:
    vector<vector<int>> combinationSum3(int k, int n) {
        result.clear(); // 可以不加
        path.clear();   // 可以不加
        backtracking(n, k, 0, 1);
        return result;
    }
};
  • 时间复杂度: O(n * 2^n)
  • 空间复杂度: O(n)

题目三: 17.电话号码的字母组合

17. 电话号码的字母组合

解题思路

直接思路,也是使用嵌套的for循环,但是层数多了,就会很头大。

此时使用回溯算法来解决这类问题。

理解本题后,要解决如下三个问题:

  1. 数字和字母如何映射
  2. 两个字母就两个for循环,三个字符我就三个for循环,以此类推,然后发现代码根本写不出来
  3. 输入1 * #按键等等异常情况
数字和字母的映射

可以使用map或者定义一个二维数组,例如:string letterMap[10],来做映射,这里定义一个二维数组,代码如下:

const string letterMap[10] = {
    "", // 0
    "", // 1
    "abc", // 2
    "def", // 3
    "ghi", // 4
    "jkl", // 5
    "mno", // 6
    "pqrs", // 7
    "tuv", // 8
    "wxyz", // 9
};
回溯法来解决n个for循环的问题

例如:输入:“23”,抽象为树形结构,如图所示:

17. 电话号码的字母组合

图中可以看出遍历的深度,就是输入"23"的长度,而叶子节点就是我们要收集的结果,输出[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”]。

回溯三部曲:

  • 确定回溯函数参数

参数指定是有题目中给的string digits,一个新的参数就是int型的index。这个index是记录遍历第几个数字了,就是用来遍历digits的(题目中给出数字字符串),同时index也表示树的深度。

vector<string> result;
string s;
void backtracking(const string& digits, int index)
  • 确定终止条件

例如输入用例"23",两个数字,那么根节点往下递归两层就可以了,叶子节点就是要收集的结果集。

那么终止条件就是如果index 等于 输入的数字个数(digits.size)了(本来index就是用来遍历digits的)。

然后收集结果,结束本层递归。

if (index == digits.size()) {
    result.push_back(s);
    return;
}
  • 确定单层遍历逻辑

首先要取index指向的数字,并找到对应的字符集(手机键盘的字符集)。

然后for循环来处理这个字符集,代码如下:

int digit = digits[index] - '0';        // 将index指向的数字转为int,,例如表达式 ('5' - '0') 的结果就是int 5
string letters = letterMap[digit];      // 取数字对应的字符集
for (int i = 0; i < letters.size(); i++) {
    s.push_back(letters[i]);            // 处理
    backtracking(digits, index + 1);    // 递归,注意index+1,一下层要处理下一个数字了
    s.pop_back();                       // 回溯
}

**注意:**输入1 * #按键等等异常情况

代码中最好考虑这些异常情况,但题目的测试数据中应该没有异常情况的数据,所以我就没有加了。

但是要知道会有这些异常,如果是现场面试中,一定要考虑到!

完整C++代码:

// 版本一
class Solution {
private:
    const string letterMap[10] = {
        "", // 0
        "", // 1
        "abc", // 2
        "def", // 3
        "ghi", // 4
        "jkl", // 5
        "mno", // 6
        "pqrs", // 7
        "tuv", // 8
        "wxyz", // 9
    };
public:
    vector<string> result;
    string s;
    void backtracking(const string& digits, int index) {
        if (index == digits.size()) {
            result.push_back(s);
            return;
        }
        int digit = digits[index] - '0';        // 将index指向的数字转为int
        string letters = letterMap[digit];      // 取数字对应的字符集
        for (int i = 0; i < letters.size(); i++) {
            s.push_back(letters[i]);            // 处理
            backtracking(digits, index + 1);    // 递归,注意index+1,一下层要处理下一个数字了
            s.pop_back();                       // 回溯
        }
    }
    vector<string> letterCombinations(string digits) {
        s.clear();
        result.clear();
        if (digits.size() == 0) {
            return result;
        }
        backtracking(digits, 0);
        return result;
    }
};
  • 时间复杂度: O( 3 m ∗ 4 n ) 3^m * 4^n) 3m4n),其中 m 是对应三个字母的数字个数,n 是对应四个字母的数字个数
  • 空间复杂度: O( 3 m ∗ 4 n 3^m * 4^n 3m4n)

一些写法,是把回溯的过程放在递归函数里了,例如如下代码,可以写成这样:(注意注释中不一样的地方)(不推荐)

// 版本二
class Solution {
private:
        const string letterMap[10] = {
            "", // 0
            "", // 1
            "abc", // 2
            "def", // 3
            "ghi", // 4
            "jkl", // 5
            "mno", // 6
            "pqrs", // 7
            "tuv", // 8
            "wxyz", // 9
        };
public:
    vector<string> result;
    void getCombinations(const string& digits, int index, const string& s) { // 注意参数的不同
        if (index == digits.size()) {
            result.push_back(s);
            return;
        }
        int digit = digits[index] - '0';
        string letters = letterMap[digit];
        for (int i = 0; i < letters.size(); i++) {
            getCombinations(digits, index + 1, s + letters[i]);  // 注意这里的不同
        }
    }
    vector<string> letterCombinations(string digits) {
        result.clear();
        if (digits.size() == 0) {
            return result;
        }
        getCombinations(digits, 0, "");
        return result;

    }
};

小结:

本题是多个集合求组合,所以在回溯的搜索过程中,都有一些细节需要注意的。


总结

  • 回溯算法的基本理论
  • 回溯法的组合问题

回溯算法也是暴力搜索,他解决的是for循环层数过多,无法实现的问题。
要自己多敲一下代码,注意细节的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2066453.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

碎碎念:关于小模型或者端侧大模型

今年有个有趣的现象&#xff0c;大厂分别推出能够在端侧运行的小模型 Microsoft&#xff1a;Phi-3 Vision 4.2b&#xff0c;支持 文本、图像输入&#xff0c;可以运行在 surface 上 Google&#xff1a;Gemini Nano 1.8b/3.2b&#xff0c;支持文本&#xff0c;可以运行在手机上…

SwiftUI 如何恣意定制和管理系统中的窗口(Window)

概览 在苹果大屏设备上,我们往往需要借助多窗口(Multiwindow)来充分利用海量的显示空间,比如 Mac,iPad 以及 AppleTV 系统 等等。 所幸的是,SwiftUI 对多窗口管理提供了很好的支持。利用 SwiftUI 我们可以非常轻松的设置窗口在屏幕上的位置,大小以及拖动反馈。 在本篇…

新版干货|互联网算法备案办理指南

一、什么是互联网算法备案 根据《互联网信息服务算法推荐管理规定》《互联网信息服务深度合成管理规定》和《生成式人工只能服务管理暂行办法》相关规定&#xff0c;需要进行互联网算法备案的主体包含具有舆论属性或者社会动员能力的算法推荐服务提供者、深度合成服务提供者、…

第五节:Nodify 节点位置设置

引言 如果你尝试过前几节的代码&#xff0c;会发现节点都是出现在0,0 位置&#xff0c;及编辑器左上角。编辑器作为最外层的交互控件&#xff0c;内部封装了节点容器ItemContrainer&#xff0c;我们通过样式属性对Loaction做绑定。本节将介绍如何配置节点位置。 1、节点位置 …

FPGA实现TMDS编码

1.TMDS编码 TMDS&#xff08;Transition Minimized Differential Signaling&#xff09;&#xff0c;即最小化差分传输信号&#xff0c;在DVI&#xff08;数字视频接口&#xff0c;只能传输视频&#xff09;和HDMI&#xff08;音视频均可传输&#xff09;协议中用于传输音视频…

适用于 Windows 的典型 PDF 编辑器程序

尽管 PDF 文件已经存在了很长时间&#xff0c;但我们仍然希望使用此类文件完成一些任务。其中一项任务是在我们的计算机上编辑它们&#xff0c;尤其是 Windows。但是&#xff0c;考虑到 PDF 文件是复杂数据的集合&#xff0c;它不会那么简单。因此&#xff0c;您将需要第三方应…

5分钟就能搭建 AI 聊天室场景!内含源代码,码住了!

近期奥运会的赛事观看火爆全网&#xff0c;大家在赛事直播间的聊天更是异常活跃&#xff0c;小编作为一个非足球爱好者&#xff0c;经常对直播解说中的「专有名词」充满迷惑。这时候特别想有一个 AI 数字人帮忙解惑。 这里&#xff0c;我们就利用云信的 AI 数字人&#xff0c;…

GraphRAG + Ollama 本地部署全攻略:避坑实战指南

—1— 为什么要对 GraphRAG 本地部署&#xff1f; 微软开源 GraphRAG 后&#xff0c;热度越来越高&#xff0c;目前 GraphRAG 只支持 OpenAI 的闭源大模型&#xff0c;导致部署后使用范围大大受限&#xff0c;本文通过 GraphRAG 源码的修改&#xff0c;来支持更广泛的 Embedd…

springboot之项目搭建并say hi

写在前面 本文看下如何搭建一个最简单的支持http接口的hello程序。 1&#xff1a;正文 接着引入springboot依赖&#xff1a; <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><v…

4.7重复的子字符串(LC_459-E)

给定一个非空的字符串 s &#xff0c;检查是否可以通过由它的一个子串重复多次构成。 示例 1: 输入: s "abab" 输出: true 解释: 可由子串 "ab" 重复两次构成。示例 2: 输入: s "aba" 输出: false示例 3: 输入: s "abcabcabcabc"…

无线终端ZWS云应用(一)—1分钟快速接入CATCOM-100 DTU上云

环境监测设备&#xff08;如温湿度传感器&#xff09;可以通过DTU终端CATCOM-100接入ZWS云平台&#xff0c;实现远程监控和管理。 准备工作 准备一个温湿度传感器和一个致远电子的DTU终端CATCOM-100。准备一张SIM卡&#xff0c;用于4G联网。 操作步骤 1. 云平台设备创建 1.1 …

PCIe563XD系列多功能异步数据采集卡64路AD信号采集500K采样频率

阿尔泰科技 型号&#xff1a;PCIe5630D/5631D/5632D/5633Dhttps://item.taobao.com/item.htm?spma1z10.3-c-s.w4002-265216876.12.84513350msbilC&id589158158140&piskf6qstfsYFCA6dK09z-BERdlfDjobG5szWKMYE-KwHcntcqeoOlla3juYGWce0OmNomNjOScZ7chwcmwbiSuY0jrXIkN…

nodejs发送邮件给多个收件人如何实现群发?

node.js发送邮件的方法&#xff1f;如何用nodejs自动发送邮件&#xff1f; Node.js发送邮件是一种高效而灵活的解决方案&#xff0c;尤其是在需要群发邮件时。AokSend将探讨如何使用Node.js发送邮件给多个收件人&#xff0c;帮您实现邮件的批量发送。 nodejs发送邮件&#xf…

2024年3款精选工具+谷歌翻译:发现那些你不知道的高级功能!

现在这世界变得越来越像一个村了&#xff0c;语言不应该是我们聊天的绊脚石。但是在工作的时候&#xff0c;碰到不同语言的文件、邮件和会议&#xff0c;翻译还是挺考验人的。好在有谷歌翻译这个牛气的工具&#xff0c;还有其他几个好用的软件帮忙&#xff0c;让我们在工作上翻…

前端常见**MS题 [3]

css部分 1、简单说明一下盒模型 CSS盒模型定义了盒的每个部分包含&#xff1a; margin, border, padding, content 。根据盒子大小的计算方式不同盒模型分成了两种&#xff0c;标准盒模型和怪异盒模型。 标准模型&#xff0c;给盒设置 width 和 height&#xff0c;实际设置的是…

【吊打面试官系列-Memcached面试题】memcached 的多线程是什么?如何使用它们?

大家好&#xff0c;我是锋哥。今天分享关于 【memcached 的多线程是什么&#xff1f;如何使用它们&#xff1f; 】面试题&#xff0c;希望对大家有帮助&#xff1b; memcached 的多线程是什么&#xff1f;如何使用它们&#xff1f; 线程就是定律&#xff08;threads rule&#…

linux 第一个命令的编写

1. 命令的概念 命令就是可执行程序。 比如说输入“ls -al”命令&#xff0c;ls 就是可执行程序的的名字。-al 就是要传递进去的参数。 ps 命令&#xff1a; 功能&#xff1a;显示进程的动态。 输入 ps 命令 当 shell 接收到命令以后&#xff0c;会根据输入的字符到环境变量和默…

UDP/TCP --- Socket编程

本篇将使用 Linux 中的系统调用来实现模拟 TCP 和 UDP 的通信过程&#xff0c;其中只对 UDP 和 TCP 进行了简单的介绍&#xff0c;本篇主要实现的是代码&#xff0c;至于 UDP 和 TCP 的详细讲解将会在之后的文章中给出。 本篇给出的 tcp 和 udp 的代码中的 echo 都是测试连接是…

电脑死机之后强制关机重启,只能进入到Bios,不能进入到系统?

前言 最近遇到好几件比较有意思的事情&#xff0c;粉丝过来求助咨询&#xff1a;电脑不知怎的就黑屏死机了&#xff0c;重启之后&#xff0c;电脑只能进入到Bios&#xff0c;无论怎么重启都没用。 把电脑拆出来看了看&#xff0c;线路一切正常。感觉上可能是内存条的问题&…