代码随想录 刷题记录-14 回溯(3)字符串、子集、排列问题

news2024/11/13 8:55:12

字符串

1.131.分割回文串

思路

本题这涉及到两个关键问题:

  1. 切割问题,有不同的切割方式
  2. 判断回文

切割问题,也可以抽象为一棵树形结构,如图:

回溯三部曲

  • 递归函数参数

全局变量数组path存放切割后回文的子串,二维数组result存放结果集。 (这两个参数可以放到函数参数里)

本题递归函数参数还需要startIndex,因为切割过的地方,不能重复切割,和组合问题也是保持一致的。

在回溯算法:求组合总和(二) (opens new window)中我们深入探讨了组合问题什么时候需要startIndex,什么时候不需要startIndex。

代码如下:

vector<vector<string>> result;
vector<string> path; // 放已经回文的子串
void backtracking (const string& s, int startIndex) {
  • 递归函数终止条件

从树形结构的图中可以看出:切割线切到了字符串最后面,说明找到了一种切割方法,此时就是本层递归的终止条件。

那么在代码里什么是切割线呢?

在处理组合问题的时候,递归参数需要传入startIndex,表示下一轮递归遍历的起始位置,这个startIndex就是切割线。

所以终止条件代码如下:

void backtracking (const string& s, int startIndex) {
    // 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
    if (startIndex >= s.size()) {
        result.push_back(path);
        return;
    }
}

个人理解,这里其实也可以把树形结构看成对分割线位置的选择,startIndex就是下一个可选的分割线位置的起始位置。startIndex至多为 s.size() - 1;

那么这里分割问题实际上可以类比到前面的组合问题,使用 “选择”思想的套路解决。

  • 单层搜索的逻辑

来看看在递归循环中如何截取子串呢?

for (int i = startIndex; i < s.size(); i++)循环中,我们 定义了起始位置startIndex,那么 [startIndex, i] 就是要截取的子串。

首先判断这个子串是不是回文,如果是回文,就加入在vector<string> path中,path用来记录切割过的回文子串。

代码如下:

for (int i = startIndex; i < s.size(); i++) {
    if (isPalindrome(s, startIndex, i)) { // 是回文子串
        // 获取[startIndex,i]在s中的子串
        string str = s.substr(startIndex, i - startIndex + 1);
        path.push_back(str);
    } else {                // 如果不是则直接跳过
        continue;
    }
    backtracking(s, i + 1); // 寻找i+1为起始位置的子串
    path.pop_back();        // 回溯过程,弹出本次已经添加的子串
}

注意切割过的位置,不能重复切割,所以,backtracking(s, i + 1); 传入下一层的起始位置为i + 1

判断回文子串

双指针法

public boolean isPanlindrome(String s, int left , int right){
        while(left < right){
            if(s.charAt(left++) != s.charAt(right--)) return false; 
        }
        return true;
    }
class Solution {
    List<List<String>> res = new ArrayList<>();
    List<String> path = new ArrayList<>();

    public List<List<String>> partition(String s) {
        dfs(s,0);
        return res;
    }

    public void dfs(String s, int startIndex){
        if(startIndex >= s.length()) {
            res.add(new ArrayList<>(path));
            return ;
        }
        for(int i=startIndex ; i < s.length() ; i++){
            //左闭右闭
            if(isPanlindrome(s,startIndex,i)){
                path.add(new String(s.substring(startIndex,i + 1))); //substring参数左闭右开
            }else{
                continue;
            }
            dfs(s,i+1);
            path.remove(path.size()-1);
        }
    }
    
    public boolean isPanlindrome(String s, int left , int right){
        while(left < right){
            if(s.charAt(left++) != s.charAt(right--)) return false; 
        }
        return true;
    }
}

优化

上面的代码还存在一定的优化空间, 在于如何更高效的计算一个子字符串是否是回文字串。上述代码isPalindrome函数运用双指针的方法来判定对于一个字符串s, 给定起始下标和终止下标, 截取出的子字符串是否是回文字串。但是其中有一定的重复计算存在:

例如给定字符串"abcde", 在已知"bcd"不是回文字串时, 不再需要去双指针操作"abcde"而可以直接判定它一定不是回文字串。

具体来说, 给定一个字符串s, 长度为n, 它成为回文字串的充分必要条件是s[0] == s[n-1]s[1:n-1]是回文字串。

如果熟悉动态规划这种算法的话, 我们可以高效地事先一次性计算出, 针对一个字符串s, 它的任何子串是否是回文字串, 然后在我们的回溯函数中直接查询即可, 省去了双指针移动判定这一步骤.

 public void isPalindrome(char[] str) {
        for (int i = 0; i <= str.length; ++i) {
            dp[i][i] = true;
        }
        for (int i = 1; i < str.length; ++i) {
            for (int j = i; j >= 0; --j) {
                if (str[j] == str[i]) {
                    if (i - j <= 1) {
                        dp[j][i] = true;
                    } else if (dp[j + 1][i - 1]) {
                        dp[j][i] = true;
                    }
                }
            }
        }
    }

(待动态规划篇解释)

2.93. 复原 IP 地址

1.参数 : startIndex 表示可以选择的分割线的起始位置

2.返回条件 

        startIndex >= s.length

                        if path.size() == 4 ,  res.add(new ArrayList<>(path)) 返回

                        else 返回

3.单层逻辑处理

        遍历从 从startIndex 到 path.size() - 1 的所有分割位置,如果 s.substring(startIndex, i+1)符合ip规范, path.add(s.substring(startIndex, i+1)) ,递归调用下一层。不合法就break结束本层循环。

这道题和上面的分割字符串的问题实际上是同一类型的问题,都可以看做使用回溯对分割位置进行选择,不同的是上面判断的是回文串,这道题要判断的是是否是合法ip。

判断合法ip相对麻烦,要考虑数的大小、是否右前缀0(包括前缀为0值为0,前缀为0值不为0的情况),而且大数处理也比较麻烦。

编写代码如下:

class Solution {
    List<String> res = new ArrayList<>();
    List<String> path = new ArrayList<>();
    String s;
    public List<String> restoreIpAddresses(String s) {
        this.s = s;
        if(s.length() > 12) return res;
        dfs(0);
        return res;
    }
    
    public void dfs(int startIndex){
        if(startIndex == s.length()){
            if(path.size() == 4){
                StringBuilder sb = new StringBuilder();
                for(int i=0 ; i < 3 ; i++){
                    sb.append(path.get(i)).append(".");
                }
                sb.append(path.get(3));
                res.add(new String(sb));
            }
            return ; 
        }
        if(startIndex > s.length()) return ;
        for(int i = startIndex ; i < s.length() ; i++){
            if(isIP(startIndex,i)){
                path.add(new String(s.substring(startIndex,i+1)));
                dfs(i+1);
                path.remove(path.size()-1);
            }else{
                break;
            }
        }
    }
    
    public boolean isIP(int startIndex , int endIndex){
        //传参是左闭右闭
        Long value = Long.valueOf(s.substring(startIndex,endIndex+1));//api设计是左闭右开
        if(s.charAt(startIndex)=='0'&& value != 0) return false;
        if(value == 0 && endIndex!=startIndex) return false;
        if(value >= 0 && value <= 255) return true;
        return false;
    }
}

但这里对isIp的判断不是很好,最好的办法还是在字符串上通过startIndex 和 endIndex进行操作,需要考虑:

  • 段位以0为开头的数字不合法
  • 段位里有非正整数字符不合法
  • 段位如果大于255了不合法

对应代码如下:

// 判断字符串s在左闭又闭区间[start, end]所组成的数字是否合法
bool isValid(const string& s, int start, int end) {
    if (start > end) {
        return false;
    }
    if (s[start] == '0' && start != end) { // 0开头的数字不合法
            return false;
    }
    int num = 0;
    for (int i = start; i <= end; i++) {
        if (s[i] > '9' || s[i] < '0') { // 遇到非数字字符不合法
            return false;
        }
        num = num * 10 + (s[i] - '0');
        if (num > 255) { // 如果大于255了不合法
            return false;
        }
    }
    return true;
}

子集问题

1.78.子集

子集也是一种组合问题,因为它的集合是无序的。那么既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始。

把子集问题的抽象树看做是“选谁”构成的,那么结果应当是树的所有结点。

以示例中nums = [1,2,3]为例把求子集抽象为树型结构,如下:

从图中红线部分,可以看出遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合

回溯三部曲

  • 递归函数参数

全局变量数组path为子集收集元素,二维数组result存放子集组合。(也可以放到递归函数参数里)

递归函数参数在上面讲到了,需要startIndex。

代码如下:

vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex) {
  • 递归终止条件

从图中可以看出:

剩余集合为空的时候,就是叶子节点。

那么什么时候剩余集合为空呢?

就是startIndex已经大于数组的长度了,就终止了,因为没有元素可取了,代码如下:

if (startIndex >= nums.size()) {
    return;
}

其实可以不需要加终止条件,因为startIndex >= nums.size(),本层for循环本来也结束了

  • 单层搜索逻辑

求取子集问题,不需要任何剪枝!因为子集就是要遍历整棵树

那么单层递归逻辑代码如下:

for (int i = startIndex; i < nums.size(); i++) {
    path.push_back(nums[i]);    // 子集收集元素
    backtracking(nums, i + 1);  // 注意从i+1开始,元素不重复取
    path.pop_back();            // 回溯
}

完整代码如下:

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    int[] nums;

    public List<List<Integer>> subsets(int[] nums) {
        this.nums = nums;
        dfs(0);
        return res;
    }
    
    public void dfs(int startIndex){
        res.add(new ArrayList<>(path));
        if(startIndex == nums.length) return ; //条件可以不加,由下面for循环控制 
                                                //i == nums.length()时会不进入for循环返回
        for(int i=startIndex ; i < nums.length ; i++){
            path.add(nums[i]);
            dfs(i+1);
            path.remove(path.size()-1);
        }
    }

}

总结

这是一道标准的模板题.

要清楚子集问题和组合问题、分割问题的的区别,子集是收集树形结构中树的所有节点的结果

而组合问题、分割问题是收集树形结构中叶子节点的结果

(前提是把抽象树看做是有“选谁”构成的)

2.90.子集II

本题用“取谁”的思想构造回溯树,取每一个结点存到答案里,但是要注意对树层去重。

由于要对树层去重,数组首先要排序。

回溯三部曲:

(1)参数 :startIndex

(2)返回逻辑:每一层先 res.add(new ArrayList<>(path)) , 因为要把每一个节点代表的答案存储到res里,然后当 startIndex >= nums.length return , 也可以这里不return,由for循环控制。

(3)每一层的逻辑:

        要注意去重

for(int i = startIndex ; i < nums.length ; i++){
            if(i != startIndex && nums[i] == nums[i-1]) continue;
            path.add(nums[i]);
            dfs(i+1);
            path.remove(path.size()-1);
        }

完整代码如下:

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    int[] nums;
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        this.nums = nums;
        dfs(0);
        return res;
    }

    public void dfs(int startIndex){
        res.add(new ArrayList<>(path));
        for(int i = startIndex ; i < nums.length ; i++){
            if(i != startIndex && nums[i] == nums[i-1]) continue;
            path.add(nums[i]);
            dfs(i+1);
            path.remove(path.size()-1);
        }
    }
}

3.491.非递减子序列

个人解法

答案的范围是有条件的树的节点

1.参数 startIndex

2.返回条件  由startIndex 的范围 以及 非递减 的条件决定,可以由 for循环负责 , continue 或者 i >= nums.length 时返回

3.单层逻辑

        先判断是否把path加入,因为要去重,先加入set,最后遍历set加入res

        for循环遍历所有可能,符合递增就dfs,不符合就 return (continue)(相当于剪枝)

代码如下:

class Solution {
    public List<List<Integer>> res = new ArrayList<>();
    public List<Integer> path = new ArrayList<>();
    public int[] nums;
    Set<List<Integer>> set = new HashSet<>();
    public List<List<Integer>> findSubsequences(int[] nums) {
        this.nums = nums;
        dfs(0);
        for(List<Integer> item : set){
            res.add(new ArrayList<>(item));
        }
        return res;
    }

    public void dfs(int startIndex){
        if(path.size()>=2){
            set.add(new ArrayList<>(path));
        }
        for(int i = startIndex ; i < nums.length ; i++){
            if(path.isEmpty() || nums[i] >= path.get(path.size()-1)){
                path.add(nums[i]);
                dfs(i+1);
                path.remove(path.size()-1);
            }else{
                continue;
            }
        }
    }
}

代码随想录题解:

本题要求返回所有该数组中不同的递增子序列

不能对原数组进行排序的,排完序的数组都是自增子序列了。

所以不能使用之前的去重逻辑!

回溯三部曲

  • 递归函数参数

本题求子序列,很明显一个元素不能重复使用,所以需要startIndex,调整下一层递归的起始位置。

  • 终止条件

本题其实类似求子集问题,也是要遍历树形结构找每一个节点,所以和回溯算法:求子集问题! (opens new window)一样,可以不加终止条件,startIndex每次都会加1,并不会无限递归。

但本题收集结果有所不同,题目要求递增子序列大小至少为2,所以代码如下:

if (path.size() > 1) {
    result.push_back(path);
    // 注意这里不要加return,因为要取树上的所有节点
}

(个人认为存放答案这个操作是放在递归函数一开始要写的,但是这属于单层处理逻辑,可以放在单层处理里)

  • 单层搜索逻辑

在图中可以看出,同一父节点下的同层上使用过的元素就不能再使用了。

解释一下这里仍然从树层上去重,并且方法是同一父节点下的同层上使用过的元素就不能再使用了的原因:(1)从图中可以看出,选择该层其那面使用过的元素,必然会造成答案重复 (2)选择该元素后续所可能组成的所有答案,都可以由前面那个使用过的元素的子树节点所对应的答案得到。

那么单层搜索代码如下:

unordered_set<int> uset; // 使用set来对本层元素进行去重
for (int i = startIndex; i < nums.size(); i++) {
    if ((!path.empty() && nums[i] < path.back())
            || uset.find(nums[i]) != uset.end()) {
            continue;
    }
    uset.insert(nums[i]); // 记录这个元素在本层用过了,本层后面不能再用了
    path.push_back(nums[i]);
    backtracking(nums, i + 1);
    path.pop_back();
}

对于已经习惯写回溯的同学,看到递归函数上面的uset.insert(nums[i]);,下面却没有对应的pop之类的操作,应该很不习惯吧

这也是需要注意的点,unordered_set<int> uset; 是记录本层元素是否重复使用,新的一层uset都会重新定义(清空),所以要知道uset只负责本层!

题目中说"-100 <= nums[i] <= 100" , 所以也可以用数组做哈希。

所以正如在哈希表:总结篇!(每逢总结必经典) (opens new window)中说的那样,数组,set,map都可以做哈希表,而且数组干的活,map和set都能干,但如果数值范围小的话能用数组尽量用数组。

完整代码如下:

class Solution {
    public List<List<Integer>> res = new ArrayList<>();
    public List<Integer> path = new ArrayList<>();
    public int[] nums;

    public List<List<Integer>> findSubsequences(int[] nums) {
        this.nums = nums;
        dfs(0);
        
        return res;
    }

    public void dfs(int startIndex){
        if(path.size()>=2){
            res.add(new ArrayList<>(path));
        }
        Set<Integer> set = new HashSet<>();
        for(int i = startIndex ; i < nums.length ; i++){
            if((path.isEmpty() || nums[i] >= path.get(path.size()-1)) && !set.contains(nums[i])){
                set.add(nums[i]);
                path.add(nums[i]);
                dfs(i+1);
                path.remove(path.size()-1);
            }else{
                continue;
            }
        }
    }
}

排列问题

1.46.全排列

  • 递归函数参数

首先排列是有序的,也就是说 [1,2] 和 [2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方

可以看出元素1在[1,2]中已经使用过了,但是在[2,1]中还要在使用一次1,所以处理排列问题就不用使用startIndex了。

但排列问题需要一个used数组,标记已经选择的元素,如图橘黄色部分所示:

  • 递归终止条件

叶子节点就是收割结果的地方。

当收集元素的数组path的大小达到和nums数组一样大的时候,说明找到了一个全排列,也表示到达了叶子节点。

  • 单层搜索的逻辑

这里和77.组合问题 、131.切割问题 和78.子集问题最大的不同就是for循环里不用startIndex了。

因为排列问题,每次都要从头开始搜索,例如元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。

而used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次。这里就是防止树枝出现重复元素。

这里used不做参数,做全局变量也可以,做好现场恢复即可。

代码如下:

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    int[] nums;
    Set<Integer> set = new HashSet<>();
    public List<List<Integer>> permute(int[] nums) {
        this.nums = nums;
        dfs();
        return res;
    }
    
    public void dfs(){
        if(path.size() == nums.length){
            res.add(new ArrayList<>(path));
            return ; 
        }
        for(int i = 0 ; i < nums.length ; i++){
            if(!set.contains(nums[i])){
                set.add(nums[i]);
                path.add(nums[i]);
                dfs();
                path.remove(path.size()-1);
                set.remove(nums[i]);
            }else{
                continue;
            }
        }
    }
}

2.47.全排列 II

使用上一题的解题思路,并结合Set去除,代码如下:

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    int[] nums;
    int[] used;
    Set<List<Integer>> resultSet = new HashSet<>();
    public List<List<Integer>> permuteUnique(int[] nums) {
        this.nums = nums;
        used = new int[21];//  used[i+10] 为 i 的 可用次数
        for(int i : nums){
            used[i+10]++;
        }
        dfs();
        res.addAll(resultSet);
        return res;
    }

    public void dfs(){
        if(path.size() == nums.length){
            resultSet.add(new ArrayList<>(path));
            return ;
        }
        for(int i = 0 ; i < nums.length ; i++){
            if(used[nums[i]+10]>0){
                used[nums[i]+10]--;
                path.add(nums[i]);
                dfs();
                path.remove(path.size()-1);
                used[nums[i]+10]++;
            }else{
                continue;
            }
        }
    }
}

需要注意的是,这里不能够再次使用set来判断树枝上是否使用过该元素来排除结果了,因为题目中给出的数字包括重复数字,应当用哈希思想记录每个元素的可用次数,在用过之后--,回溯回来以后恢复现场++。

也可以先排序后去重:

(注意这里必须要用used数组,因为这是全排列,每次都是从0开始的,不是从startIndex开始的)

List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    int[] nums;
    boolean[] used;


    public List<List<Integer>> permuteUnique(int[] nums) {
        Arrays.sort(nums);
        this.nums = nums;
        used = new boolean[nums.length];
        dfs();
        return res;
    }

    public void dfs(){
        if(path.size() == nums.length){
            res.add(new ArrayList<>(path));
            return ;
        }
        for(int i = 0 ; i < nums.length ; i++){
            if( i != 0 && nums[i]==nums[i-1] && used[i-1] == false) continue;
            //同时还不能重复选择自己
            if(used[i] == false){
                used[i] = true;
                path.add(nums[i]);
                dfs();
                path.remove(path.size()-1);
                used[i] = false;

            }
        }
    }

这种解法的思想是:先排序,再利用used数组再每个树层上去重,采集每个节点上的结果。同时还因为是全排列,还需要在每个树枝上防止重复使用已经用过的元素。

性能分析

之前并没有分析各个问题的时间复杂度和空间复杂度,这次来说一说。

这块网上的资料鱼龙混杂,一些所谓的经典面试书籍根本不讲回溯算法,算法书籍对这块也避而不谈,感觉就像是算法里模糊的边界。

所以这块就说一说我个人理解,对内容持开放态度,集思广益,欢迎大家来讨论!

子集问题分析:

  • 时间复杂度:$O(n × 2^n)$,因为每一个元素的状态无外乎取与不取,所以时间复杂度为$O(2^n)$,构造每一组子集都需要填进数组,又有需要$O(n)$,最终时间复杂度:$O(n × 2^n)$。
  • 空间复杂度:$O(n)$,递归深度为n,所以系统栈所用空间为$O(n)$,每一层递归所用的空间都是常数级别,注意代码里的result和path都是全局变量,就算是放在参数里,传的也是引用,并不会新申请内存空间,最终空间复杂度为$O(n)$。

排列问题分析:

  • 时间复杂度:$O(n!)$,这个可以从排列的树形图中很明显发现,每一层节点为n,第二层每一个分支都延伸了n-1个分支,再往下又是n-2个分支,所以一直到叶子节点一共就是 n * n-1 * n-2 * ..... 1 = n!。每个叶子节点都会有一个构造全排列填进数组的操作(对应的代码:result.push_back(path)),该操作的复杂度为$O(n)$。所以,最终时间复杂度为:n * n!,简化为$O(n!)$。
  • 空间复杂度:$O(n)$,和子集问题同理。

组合问题分析:

  • 时间复杂度:$O(n × 2^n)$,组合问题其实就是一种子集的问题,所以组合问题最坏的情况,也不会超过子集问题的时间复杂度。
  • 空间复杂度:$O(n)$,和子集问题同理。

去重总结参考:回溯算法去重问题的另一种写法

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

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

相关文章

《计算机操作系统》(第4版)第5章 虚拟存储器 复习笔记

第5章 虚拟存储器 一 、虚拟存储器概述 1. 常规存储管理方式的特征和局部性原理 (1)特征 ①一次性。 ②驻留性。 (2)局部性原理 局部性原理表现在时间局部性和空间局部性两方面。 2.虚拟存储器的定义和特征 (1)虚拟存储器的定义 虚拟存储器是指具有请求调入功能和置换功能&…

java之表格数据存储

java之表格数据存储 摘要表格数据存储javabean 介绍javabean 设计类表格数据存储 摘要 本博客主要讲述java如何存储表格数据。 表格数据存储 在解决实际问题的时候&#xff0c;需要涉及到如何存储表格的数据&#xff0c;这里讲述了一种使用javabean的方法存储表格 javabea…

STM中的I2C

常见的几种通信接口 I2C总线定义 定义 I2C - Inter-Integrated Circuit&#xff1a;两线式 串行总线&#xff1a;说明处理器和外设之间只需两根信号线&#xff0c;分别是SCL时钟控制信号线和SDA数据线 SCL&#xff08;serial clock line&#xff09; 时钟控制信号线&#xff…

makefile文件基本语法

一、makefile文件基本介绍 Makefile 文件是 make 工具使用的配置文件&#xff0c;它定义了如何自动化构建项目的规则和命令。Makefile 文件的主要作用是指定如何编译和链接程序&#xff0c;以及管理文件之间的依赖关系&#xff0c;从而实现高效的构建过程。 1.1 Makefile 的基…

【FreeRTOS】队列实验-分发数据给多个任务(赛车游戏)

目录 0 前言1 队列实验_分发数据给多个任务(赛车游戏)2 赛车游戏2.1 game.c2.2 注册队列2.3显示汽车2.4隐藏汽车2.5 CarTask2.6 car_game2.7 MX_FREERTOS_Init 3 总结 0 前言 学习视频&#xff1a; 【FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS&#xff08;FreeRTOS教…

如何用Python实现山东省旅游数据爬虫与K-means满意度分析

&#x1f393; 作者&#xff1a;计算机毕设小月哥 | 软件开发专家 &#x1f5a5;️ 简介&#xff1a;8年计算机软件程序开发经验。精通Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等技术栈。 &#x1f6e0;️ 专业服务 &#x1f6e0;️ 需求定制化开发源码提…

AI一键视频多语言配音/翻译工具:打造无缝多语言视频体验

在全球化的今天,视频内容的传播不再受限于地域和语言。然而,如何高效地将视频内容翻译成多种语言并保持其自然度和流畅性,一直是业界面临的挑战。为了解决这一难题,我们推出了一款智能视频多语言AI配音和翻译工具——Linly Dubbing。该工具基于YouDub-webui的灵感进行了创新…

开源:cuda studio云原生一站机器学习、深度学习、大模型AI平台

文章目录 1、 cuda studio云原生一站机器学习、深度学习、大模型AI平台2、网址 1、 cuda studio云原生一站机器学习、深度学习、大模型AI平台 cube studio开源云原生一站式机器学习/深度学习/大模型AI平台&#xff0c;支持sso登录&#xff0c;多租户&#xff0c;大数据平台对接…

3级线性反馈移位寄存器在C3=1时可有4种线性反馈函数,设其初始状态为(a1,a2,a3)=(1,0,1),求各线性反馈函数的输出序列及周期

标题是题目 题解 1.补充知识 2.分析四种情况&#xff1a; 结合我所给的反馈数公式以及a31&#xff0c;可以得到反馈函数为: fC1*a3⊕C2*a2⊕C3*a1C1*a3⊕C2*a2⊕a1 附&#xff1a;别把初始状态为&#xff08;a1,a2,a3&#xff09;(1,0,1)带入&#xff0c;因为a1,a2,a3的值…

小程序学习day11-生命周期函数、组件所在页面的生命周期、自定义组件的插槽、自定义组件的父子通信

40、自定义组件&#xff08;续&#xff09;&#xff08;续&#xff09; &#xff08;10&#xff09;生命周期函数 1&#xff09;小程序里的全部生命周期函数 ①created&#xff08;在组件刚被创建时执行&#xff09;&#xff08;被创建&#xff0c;但未被放入页面&#xff09…

【AD9361 数字基带】多片基带内FPGA补偿 I/Q Rotation

I/Q 旋转 Rotation 在许多多通道射频系统中&#xff0c;如 AD-FMCOMMS5&#xff0c;甚至在 AD-FMCOMMS2、AD-FMCOMMS3 上&#xff0c;都需要测量或校正两个复数 &#xff08;I/Q&#xff09; RF 信号之间的相位差。 从纯粹的数学描述来看&#xff0c;单个正弦波没有相位&…

NNG简介和使用总结

先认识下ZeroMQ 参考&#xff1a;ZeroMQ详解 - 南哥的天下 - 博客园 (cnblogs.com) ZeroMQ&#xff08;简称ZMQ&#xff09;是一个基于消息队列的多线程网络库&#xff0c;其对套接字类型、连接处理、帧、甚至路由的底层细节进行抽象&#xff0c;提供跨越多种传输协议的套接字。…

RK3568开发笔记-buildroot系统scp拷贝文件报错dbclient no such file or directory

目录 ​​​​​​​ 前言 一、问题分析 什么是 Dropbear Dropbear 的优点 二、解决办法 总结 前言 在使用RK3588开发板进行系统开发时,很多开发者会选择使用Buildroot来构建自己的定制化系统。在开发过程中,通常需要通过scp(Secure Copy Protocol)命令将文件从本地计…

IDEA工具设置默认使用maven的settings.xml文件

第一步&#xff1a;打开idea工具&#xff0c;选中 File ——> New Projects Setup ——> Settings for New Projects 第二步&#xff1a;先设置下自动构建项目这个选项 第三步&#xff1a;选中 Build Tools ——> Maven&#xff0c;让后就可以设置自己安转的maven和se…

xlsx表格-A列的值需要从C列中匹配到然后输出C列旁边D列的值,怎么写公式?

公式&#xff1a; IFERROR(VLOOKUP(A1, C:D, 2, FALSE), "") 解释&#xff1a; 在VLOOKUP函数中&#xff0c;2表示要返回的列的索引。具体来说&#xff0c;VLOOKUP函数的语法如下&#xff1a; VLOOKUP(lookup_value, table_array, col_index_num, [range_lookup])…

功能测试和性能测试区别简析,软件测试公司如何开展有效测试?

软件功能测试旨在验证软件是否按照需求和设计规范正常运行&#xff0c;软件性能测试则是用来评估软件在特定负载条件下的行为和响应时间&#xff0c;确保软件在高并发和高需求的环境中能够稳定运行。 虽然两者都属于软件测试的重要组成部分&#xff0c;但它们的目的和重点却有…

零基础5分钟上手谷歌云GCP - 服务器自动扩展

简介 欢迎来到小李哥谷歌云GCP云计算知识学习系列&#xff0c;适用于任何无云计算或者谷歌云技术背景的开发者&#xff0c;让大家零基础5分钟通过这篇文章就能完全学会谷歌云一个经典的服务开发架构方案。 我将每天介绍一个基于全球三大云计算平台&#xff08;AWS, Azure, GC…

改编版猜数字小游戏,猜错了就黑屏(整蛊版本)

1. 前情提要 在前一篇博客中&#xff0c;我们了解到了如何获得随机数&#xff0c;并且通过运算可以规定所获得的这个随机数的范围在多少数值之间 那么接下来我们就需要去具体去实现猜数字游戏的各种布置 2. 布置主菜单 玩一个游戏&#xff0c;最开始的界面都会是一个主菜单…

iPhone13手机照片被误删,有什么方法可以恢复吗?

在日常使用手机时&#xff0c;我们可能因为误操作、手机崩溃、或者其他原因&#xff0c;导致iPhone13手机中的照片丢失。遇到这种情况&#xff0c;手机误删照片如何恢复&#xff1f;在本文中&#xff0c;我们将分享3个妙招&#xff0c;帮助您恢复iPhone13上误删的照片。 一、通…

2024年第二季度SSD出货量下滑18.4%,降至6750万部,但容量增长4.1%至90.6EB

2024年第二季度SSD Exabytes实现连续季度增长 仅企业级PCIe SSD有所增长&#xff1a;尽管所有其他类别均出现下滑&#xff0c;但企业级PCIe SSD的增长是由其所有终端市场需求增加所驱动的。总体SSD出货量&#xff1a;总体SSD出货量环比下降18.4%&#xff0c;降至6750万部&…