C++ 数据结构算法细节相关

细节

队列

这段代码实现的是二叉树的层序遍历,也就是按照树的层次,一层一层地遍历节点。下面我会为你详细解释这段代码。

  1. queue <TreeNode*> q;

    • 这是一个队列,队列中存放的是指向TreeNode的指针。
    • 队列(queue)是一种先进先出(FIFO)的数据结构。你可以把元素添加到队列的尾部,并从队列的头部移除元素。
    • 在这段代码中,队列q用于暂存每一层的节点,以便按层遍历。
    • 详细用法:
      • q.push(element): 将元素添加到队列尾部。
      • q.front(): 返回队列头部的元素,但不移除。
      • q.pop(): 移除队列头部的元素。
      • q.empty(): 判断队列是否为空,如果为空返回true,否则返回false
      • q.size(): 返回队列中的元素数量

广度优先搜索

所谓广度优先搜索,就是从起点出发,每次都尝试访问同一层的节点,如果同一层都访问完了,再访问下一层,最后广度优先搜索找到的路径就是从起点开始的最短合法路径

lower_bound和upper_bound

lower_bound

  • 定义lower_bound 返回一个指向容器中第一个不小于给定值的元素的迭代器。如果所有元素都小于该值,则返回容器的末尾迭代器。
  • 用法:
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> v = {1, 2, 4, 4, 5, 6};

    // 查找值为 4 的第一个不小于 4 的位置
    auto it = std::lower_bound(v.begin(), v.end(), 4);

    if (it != v.end()) {
        std::cout << "lower_bound: " << *it << " at index " << (it - v.begin()) << std::endl;
    } else {
        std::cout << "No element found." << std::endl;
    }

    return 0;
}

upper_bound

  • 定义upper_bound 返回一个指向容器中第一个大于给定值的元素的迭代器。如果所有元素都小于或等于该值,则返回容器的末尾迭代器。
  • 用法:
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> v = {1, 2, 4, 4, 5, 6};

    // 查找值 4 的第一个大于 4 的位置
    auto it = std::upper_bound(v.begin(), v.end(), 4);

    if (it != v.end()) {
        std::cout << "upper_bound: " << *it << " at index " << (it - v.begin()) << std::endl;
    } else {
        std::cout << "No element found." << std::endl;
    }

    return 0;
}

关键点总结

  • lower_bound 查找值范围的开始(第一个不小于给定值),而 upper_bound 查找值范围的结束(第一个大于给定值)。
  • 这两个函数都使用二分查找,因此时间复杂度为 O(log n)。
  • 返回值是指向容器中的迭代器,可以使用它来得到相应的元素或计算索引。

场景示例

  • 如果你想在一个有序数组中插入一个值并保持数组的有序性,可以使用这两个函数来决定插入的位置。
  • 在处理重复元素时,lower_bound 可以帮助你找到第一个匹配的元素的位置,而 upper_bound 可以帮助你找到最后一个匹配元素之后的位置,从而可以知道有多少重复元素。

希望这能帮助你更好地理解这两个函数的使用!如果你有任何具体的疑问或者案例,欢迎随时询问。

class Solution { public: vector<int> searchRange(vector<int>& nums, int target) {

auto left=lower_bound(nums.begin(),nums.end(),target);

auto right=upper_bound(nums.begin(),nums.end(),target);

if(left==nums.end()||*left!=target) return {-1,-1};

return {(int)(left-nums.begin()),(int)(right-nums.begin()-1)}; } };

深度优先搜索(DFS)

深度优先搜索(Depth-First Search,简称 DFS)是一种用于遍历或搜索树或图的算法。DFS 从根节点开始(在图中可以选择任意节点作为根节点),沿着一条路径尽可能深入地探索,直到无法继续为止,然后回溯到上一个节点,继续探索其他路径。

DFS 的基本步骤

  1. 选择起点:从图中的任意一个节点开始。
  2. 访问节点:访问当前节点,并将其标记为已访问。
  3. 递归探索:从当前节点的未访问邻居中选择一个节点,继续深入探索。如果当前节点没有未访问的邻居,则回溯到上一个节点。
  4. 重复步骤:重复上述步骤,直到所有节点都被访问。

DFS 的特点

  • 空间复杂度:DFS 的空间复杂度较低,因为它只需存储当前路径上的节点,通常使用栈来实现。
  • 时间复杂度:DFS 的时间复杂度为 O(V + E),其中 V 是节点数,E 是边数。
  • 适用场景:DFS 适用于解决路径问题、连通性问题、以及寻找图中的环等问题。

DFS 的实现

DFS 可以用递归或栈来实现。

递归实现

def dfs(graph, node, visited):
    if node not in visited:
        print(node)  # 访问节点
        visited.add(node)  # 标记为已访问
        for neighbor in graph[node]:
            dfs(graph, neighbor, visited)  # 递归访问邻居

# 示例图的邻接表表示
graph = {
    'A': ['B', 'C'],
    'B': ['D', 'E'],
    'C': ['F'],
    'D': [],
    'E': ['F'],
    'F': []
}

visited = set()  # 记录已访问的节点
dfs(graph, 'A', visited)

栈实现

def dfs(graph, start):
    stack = [start]
    visited = set()

    while stack:
        node = stack.pop()
        if node not in visited:
            print(node)  # 访问节点
            visited.add(node)  # 标记为已访问
            for neighbor in reversed(graph[node]):
                if neighbor not in visited:
                    stack.append(neighbor)  # 将邻居节点入栈

# 示例图的邻接表表示
graph = {
    'A': ['B', 'C'],
    'B': ['D', 'E'],
    'C': ['F'],
    'D': [],
    'E': ['F'],
    'F': []
}

dfs(graph, 'A')

DFS 的应用

  • 连通性检测:判断图中两个节点是否连通。
  • 拓扑排序:用于有向无环图(DAG)的排序。
  • 迷宫问题:寻找从起点到终点的路径。
  • 回溯算法:如八皇后问题、数独问题等。

DFS 是一种强大且常用的图算法,尤其在解决需要深度探索的问题时非常有用。

实战例子

. - 力扣(LeetCode)

c++ 堆和栈简介,展示下详细用法

栈:先入后出,后入先出。后push的在栈顶

在 C++ 中,堆(Heap)和栈(Stack)是两种重要的内存管理区域,它们的用途、特点和使用方式各不相同。以下是对堆和栈的详细介绍,包括使用示例。

一、栈(Stack)

1. 简介

  • 存储性质: 栈是一个后进先出(LIFO)的数据结构,主要用于存储局部变量、函数参数和返回地址。
  • 内存管理: 栈的内存由编译器自动管理,分配和释放速度较快,通常在程序运行时内存分配较小。
  • 生命周期: 栈中的变量在函数调用时分配内存,函数结束时释放。

2. 使用示例

使用栈来定义局部变量和函数参数。

#include <iostream>

void function() {
    int a = 10; // 局部变量在栈上分配
    int b = 20;
    std::cout << "Sum: " << (a + b) << std::endl; // 输出 Sum: 30
}

int main() {
    function();
    // a 和 b 的内存会在 function() 结束时自动释放
    return 0;
}

3. 栈操作

C++ STL 的 std::stack 来实现栈的数据结构,可以执行入栈、出栈等操作:

#include <iostream>
#include <stack>

int main() {
    std::stack<int> s;

    // 入栈
    s.push(1);
    s.push(2);
    s.push(3);

    std::cout << "栈顶元素: " << s.top() << std::endl; // 输出 3

    // 出栈
    s.pop();
    std::cout << "出栈后栈顶元素: " << s.top() << std::endl; // 输出 2

    return 0;
}

二、堆(Heap)

1. 简介

  • 存储性质: 堆是一个可以动态分配内存的区域,适用于需要动态大小的结构,比如数组或对象。
  • 内存管理: 堆的内存需要程序员手动管理,使用 new 分配内存和 delete 释放内存,操作比较灵活但速度较慢。
  • 生命周期: 在堆上分配的变量不会自动释放,程序员需要手动管理其生命周期。

2. 使用示例

使用堆来分配单个变量和数组。

#include <iostream>

int main() {
    // 动态分配一个整数
    int* p = new int(10); // 在堆上分配内存
    std::cout << "动态分配的整数: " << *p << std::endl; // 输出 10

    // 动态分配一个数组
    int* arr = new int[5]; // 在堆上分配一个数组
    for (int i = 0; i < 5; ++i) {
        arr[i] = i + 1; // 给数组赋值
    }
    
    std::cout << "数组内容: ";
    for (int i = 0; i < 5; ++i) {
        std::cout << arr[i] << " "; // 输出 1 2 3 4 5
    }
    std::cout << std::endl;

    // 释放内存
    delete p; // 释放单个变量的内存
    delete[] arr; // 释放数组的内存

    return 0;
}

3. 堆与栈的对比

特性
组织结构后进先出(LIFO)动态大小
内存管理编译器自动管理程序员手动管理
分配时间分配和释放迅速分配和释放相对较慢
生命周期随函数调用自动销毁需要手动释放
大小限制通常比较小可以很大,受限于系统
存储类型基本数据类型和对象动态分配的对象、数组

结论

  •  适合用于存储局部变量和函数调用上下文,具有快速分配和释放的优点,但大小有限。
  •  适合存储不确定大小的数据,提供更多的灵活性,但需要手动管理内存,容易导致内存泄漏。

希望这些示例能帮助你更清晰地理解 C++ 中的堆和栈的用法和特点!如果你还有其他问题,请随时问我

`unordered_map<char, char>` 是 C++ STL 中的一个哈希表实现,它可以存储 `char` 类型的键和值,并且通过哈希函数实现常数时间的查找、插入和删除操作。 以下是使用 `unordered_map<char, char>` 的一些常见操作:

1. 创建一个空的 `unordered_map` 对象: ```c++ unordered_map<char, char> pairs; ```

2. 插入一个键值对: ```c++ pairs.insert(make_pair('a', 'b')); ``` 或者: ```c++ pairs['a'] = 'b'; ```

3. 查找一个键对应的值: ```c++ char value = pairs['a']; ```

4. 检查一个键是否存在: ```c++ if (pairs.find('a') != pairs.end()) { // 'a' exists in the map } ```

5. 删除一个键值对: ```c++ pairs.erase('a'); ```

6. 遍历 `unordered_map` 中的键值对: ```c++ for (auto& pair : pairs) { cout << pair.first << " -> " << pair.second << endl; } ``` 或者使用迭代器: ```c++ for (auto it = pairs.begin(); it != pairs.end(); ++it) { cout << it->first << " -> " << it->second << endl; } ``` 注意,在使用 `unordered_map` 时,键必须是可哈希的类型,因此在使用自定义类型作为键时,需要提供自定义的哈希函数和相等比较函数。

动态规划

动态规划(Dynamic Programming,简称 DP)是一种用于解决最优化问题的算法设计技术,主要用于处理具有重叠子问题和最优子结构性质的问题。动态规划通过将复杂问题分解为更简单的子问题,并存储已解决的子问题的结果,从而避免重复计算,提高效率。

动态规划的基本思想

  1. 重叠子问题:动态规划适用于具有重叠子问题的问题,即将大问题分解为小问题,多个大问题的解可能依赖于同一个小问题。
  2. 最优子结构:一个问题的最优解包含了其子问题的最优解。可以通过子问题的最优解构造出大问题的最优解。

动态规划的步骤

  1. 定义状态:确定问题的状态,也就是用什么变量来描述子问题。
  2. 状态转移:找出状态之间的关系,建立递推关系(转移方程)。
  3. 边界条件:给出递推的边界条件,通常是最小规模问题的答案。
  4. 求解:通过迭代或递归的方式,计算出最终的解。

动态规划的实现方式

动态规划一般有两种实现方式:自顶向下(递归)自底向上(迭代)

  • 自顶向下(Memoization):将未计算的子问题记录下来,避免重复计算。
  • 自底向上(Tabulation):通过从最小子问题开始,逐步计算出更大问题的解。

算法示例

让我们用斐波那契数列作为动态规划的一个简单示例来说明。

斐波那契数列问题

斐波那契数列的定义如下:

  • F(0) = 0
  • F(1) = 1
  • F(n) = F(n-1) + F(n-2) (n >= 2)
自顶向下实现
def fib(n, memo={}):
    if n in memo:
        return memo[n]  # 如果已经计算过,直接返回
    if n <= 1:
        return n
    memo[n] = fib(n-1, memo) + fib(n-2, memo)  # 计算并存储结果
    return memo[n]

# 示例
print(fib(10))  # 输出 55

自底向上实现
def fib(n):
    if n <= 1:
        return n
    dp = [0] * (n + 1)  # 创建一个数组来存储计算结果
    dp[0], dp[1] = 0, 1
    for i in range(2, n + 1):
        dp[i] = dp[i-1] + dp[i-2]  # 动态规划计算
    return dp[n]

# 示例
print(fib(10))  # 输出 55

常见动态规划问题

动态规划可以用于解决很多经典问题,以下是一些常见的例子:

  1. 背包问题(01背包问题、完全背包问题等)
  2. 最长公共子序列(LCS)
  3. 编辑距离(Levenshtein 距离)
  4. 股票买卖问题
  5. 最短路径问题(Dijkstra算法、Floyd-Warshall 算法)

总结

动态规划是一种强大的算法设计工具,能够有效地解决很多复杂问题,其关键在于找到适当的状态、状态转移方程和合理的边界条件。通过掌握动态规划,可以更高效地解决一类具有特定结构的问题。

其他细节点

INT_MAX

class MinStack { stack<int> x_stack; stack<int> min_stack; public: MinStack() { min_stack.push(INT_MAX); } min_stack.push(INT_MAX);这句啥意思 为什么用INT_MAX

行min_stack.push(INT_MAX)初始化min_stack,最大值为INT_MAX,表示int可以容纳的最大值。 这样做是为了确保“min_stack”中总是有一个最小值。当一个新值被推到“x_stack”上时,它将与“min_stack”的顶部元素进行比较。如果新值较小,它将被推到“x_stack”和“min_stack”上。如果它不小,只更新“x_stack”。 通过初始化值为INT_MAX的min_stack,我们确保推送到min_stack上的第一个元素总是比推送到它上的任何其他值都小。这保证了“min_stack”总是至少有一个元素,即使“x_stack”是空的。

resize

ret[i].resize(i + 1); 的意思是调整 ret[i] 这一行的大小,将其设为 i+1,即第 i 行有 i+1 个元素。

杨辉三角

. - 力扣(LeetCode)

,定义了一个二维 vector,名字叫 ret。 - 然后,通过一个循环,对每一行进行处理,循环的次数为 numRows。 - 在每行中,先使用 resize 函数,将当前行的长度设为 i + 1,也就是说,第一行长度为 1,第二行长度为 2,第三行长度为 3,以此类推。 - 接下来,将每一行的第一个和最后一个元素设为 1。这是因为杨辉三角的性质,每行的两端元素都是 1。 - 最后,对于每一行的中间元素,都是前一行对应位置元素的和。这是因为杨辉三角的性质,中间元素等于上一行左右相邻两个元素之和。

vector<int> dp(amount+1, max)

vector<int> dp(amount+1, max)这个为什么 为什么要amount+1

因为dp从dp[0]开始   所以amount+1

这里的 `vector<int> dp(amount+1, max)` 表示创建一个大小为 `amount+1` 的数组 `dp`,并将其所有元素初始化为 `max`。这里的 `max` 是一个变量,表示 `amount+1` 的值。

s.substr(j, i - j)啥意思


`string::substr()` 是 C++ 中的一个函数,用于提取字符串中从第 `j` 个位置开始,长度为 `i-j` 的子串。在这个代码中,`s.substr(j, i - j)` 是用来提取从字符串 `s` 的第 `j` 个位置开始,长度为 `i-j` 的子串。

 vector<long> fmax(nums.begin(), nums.end())

vector<int>& nums)

 vector<long> fmax(nums.begin(), nums.end())和 fmax(nums.size())有什么区别

这两个语句的区别在于vector的初始化方式。 第一个语句将一个名为fmax的vector初始化为nums的副本,其长度与nums相同。这意味着fmax中包含与nums相同的元素,并且可以直接对fmax进行修改而不会影响nums。 第二个语句将一个名为fmax的vector初始化为一个长度为nums.size()的空向量。这意味着fmax中不包含任何元素,需要使用push_back()等方法来添加元素。

位运算

位运算是指对二进制数进行操作的运算。常用的位运算符包括:左移(<<)、右移(>>)、按位与(&)、按位或(|)、按位异或(^)和取反(~)。

- 左移(<<):将一个数的二进制码向左移动若干位,右侧补0。例如,3 << 2,表示将3的二进制码向左移动2位,变成1100,转换为十进制数为12。

- 右移(>>):将一个数的二进制码向右移动若干位,左侧补0或1。例如,3 >> 1,表示将3的二进制码向右移动1位,变成1,转换为十进制数为1。

- 按位与(&):对两个二进制数的每一位进行与运算,结果中每一位都是两个数中对应位的与运算结果。例如,3 & 5,表示将3和5的二进制码进行与运算,结果为1。

- 按位或(|):对两个二进制数的每一位进行或运算,结果中每一位都是两个数中对应位的或运算结果。例如,3 | 5,表示将3和5的二进制码进行或运算,结果为7。 -

按位异或(^):对两个二进制数的每一位进行异或运算,结果中每一位都是两个数中对应位的异或运算结果。例如,3 ^ 5,表示将3和5的二进制码进行异或运算,结果为6。

(按位异或是一种二进制运算符,用符号 "^" 表示,它对两个二进制位进行比较,当两个二进制位不同时返回 1,否则返回 0。例如,对于两个二进制数 1010 和 1100,按位异或的结果为 0110。在计算机编程中,按位异或通常用于加密和校验和的计算。)

- 取反(~):将一个二进制数的每一位取反,即0变成1,1变成0。例如,~3,表示将3的二进制码进行取反操作,结果为-4。 位运算常用于位掩码,数据压缩等方面。

字典序

字典序_百度百科

设想一本英语字典里的单词,何者在前何者在后?

显然的做法是先按照第一个字母、以 a、b、c……z 的顺序排列;如果第一个字母一样,那么比较第二个、第三个乃至后面的字母。如果比到最后两个单词不一样长(比如,sigh 和 sight),那么把短者排在前。

通过这种方法,我们可以给本来不相关的单词强行规定出一个顺序。“单词”可以看作是“字母”的字符串,而把这一点推而广之就可以认为是给对应位置元素所属集合分别相同的各个有序多元组规定顺序:下面用形式化的语言说明。

以排列 [4,5,2,6,3,1] 为例:

我们需要将一个左边的「较小数」与一个右边的「较大数」交换,以能够让当前排列变大,从而得到下一个排列。

同时我们要让这个「较小数」尽量靠右,而「较大数」尽可能小。当交换完成后,「较大数」右边的数需要按照升序重新排列。这样可以在保证新排列大于原来排列的情况下,使变大的幅度尽可能小

我们能找到的符合条件的一对「较小数」与「较大数」的组合为 2 与 3,满足「较小数」尽量靠右,而「较大数」尽可能小。

当我们完成交换后排列变为 [4,5,3,6,2,1],此时我们可以重排「较小数」右边的序列,序列变为 [4,5,3,1,2,6]。

这么说吧  6,3,1]已经是倒叙   不能再排了  2,6,3,1]还能排成 3,1,2,6]

把6换成4,换成 2431你更好理解,后面依次是  3-124  3-142 3-214 3-241  

1-234   1-243 

1-324    1-342

1-423    1-432

明白了不 ,太绕了,哪个神经病想的

跳跃游戏 II

. - 力扣(LeetCode)

这段代码是用来解决跳跃游戏问题的。给定一个非负整数数组,你从数组起始位置开始跳跃,每次可以跳跃的步数为该位置数组值。目标是到达数组最后一个位置,求最少需要跳跃的步数。

这里采用了贪心算法,维护了三个变量:maxPos表示当前能够到达的最远位置,end表示目前跳跃步数所能到达的最远位置,step表示跳跃次数。

从数组起始位置开始循环,如果当前位置能够到达的最远位置大于等于目前最远位置end,则将end更新为maxPos,并将步数step加1。i == end 表示已经跳跃了step次,需要更新下一次跳跃所能到达的最远位置。如果当前位置i无法到达end,则说明需要再跳一次才能到达end,此时步数不需要加1。

最后返回的是最少需要跳跃的步数。

子集

. - 力扣(LeetCode)

这是一段 C++ 代码,用于求一个整数数组的所有子集。下面是代码的解释:

- 变量 n 存储了数组 nums 的长度。 - 变量 mask 是一个二进制数,用于表示 nums 中某些元素是否被选中。因为 nums 中每个元素都只有选或者不选两种情况,所以 mask 的范围是 0 到 2^n-1,共有 2^n 种情况

- 位运算符 << 表示左移,例如 1 << 2 就是将二进制数 1 左移 2 位,得到二进制数 100,也就是十进制数 4。

- 表达式 (1 << n) 表示将二进制数 1 左移 n 位,得到的二进制数就是 2^n。因为 mask 的范围是从 0 到 2^n-1,所以当 mask 小于 2^n 时,就代表还有未考虑的情况。

- 表达式 mask & (1 << i) 表示将二进制数 mask 和二进制数 1 << i 进行按位与运算。因为 1 << i 只有第 i 位是 1,其他位都是 0,所以这个表达式的结果就是 mask 的第 i 位是否为 1。如果为 1,就代表 nums[i] 被选中了

cand.clear();和cand.pop_back()

. - 力扣(LeetCode)

vector<int> cand;//子集 cand.clear();和cand.pop_back();有什么区别

`cand.clear()`会清空`vector`中的所有元素,使其成为空集。而`cand.pop_back()`则会删除`vector`中的最后一个元素,如果`vector`原本只有一个元素,则调用`pop_back()`后,`vector`将为空。 所以二者的区别在于`cand.clear()`清空整个`vector`,而`cand.pop_back()`只删除一个元素。

state.pop_back()理解不了   先采用吧

`state.pop_back()` 的作用是撤销选择,把状态列表(子集)中最后一个元素删除,恢复到之前的状态。

`state.clear()` 可以清空状态列表,但并不会撤销选择。在回溯算法中,撤销选择是非常重要的一步,因为每次递归调用都会对状态进行修改,如果不撤销选择,会影响后续的搜索。因此,使用 `state.pop_back()` 是必要的。

单双引号

. - 力扣(LeetCode)

这是因为在C++中,空字符串应该用双引号""来表示,而不是单引号''。因此,将''改为""应该就可以解决这个问题了。

"("  和 ')'   单双可不一样   前面是字符串   后面是字符

电话号码的字母组合

. - 力扣(LeetCode)

哈哈哈哈   自己写了个递归 太牛了

这是一个 C++ 实现的电话号码的字母组合问题,使用了深度优先搜索(DFS)的思想。

首先,定义了一个长度为 10 的字符串数组 `MAPPING`,存储了数字和字母的映射关系,例如,`MAPPING[2]` 存储的是数字 2 对应的字母 "abc"。

然后,定义了一个函数 `letterCombinations`,接收一个字符串 `digits`,表示电话号码。首先判断 `digits` 的长度是否为 0,如果是,直接返回一个空的 vector。

接下来,定义了一个 vector `ans`,用于存储所有可能的字母组合。然后,定义了一个字符串 `path`,长度为 `n`(即 `digits` 的长度),用于存储当前的字母组合。

接着,定义了一个 lambda 函数 `dfs`,接收两个参数:一个是 `dfs` 函数本身,另一个是当前处理的位置 `i`。在 `dfs` 函数中,首先判断当前位置 `i` 是否等于 `n`,如果是,说明已经处理完了所有数字,此时将 `path` 添加到 `ans` 中,并返回。

接着,遍历数字 `digits[i]` 对应的所有字母 `c`,将 `c` 赋值给 `path[i]`,然后递归调用 `dfs(dfs, i + 1)` 处理下一个数字。

最后,在 `letterCombinations` 函数中,调用 `dfs(dfs, 0)` 开始搜索,搜索完毕后,返回所有可能的字母组合 `ans`。

Lambda

Lambda函数 - 简书

auto dfs = [&](auto&& dfs, int i)这lambda函数有什么规则啊 乱起八糟的不会写啊

这是一个 C++11 中使用 lambda 表达式来实现递归的技巧。下面是这个 lambda 表达式的解释:

- auto 是关键字,用来推导 lambda 表达式的返回类型。

- dfs 是指这个 lambda 表达式的名字,可以自定义。

- && 表示按右值引用传参,用于优化传递参数的效率。

- auto&& dfs 表示这个 lambda 表达式接受一个自己的类型作为参数,用于递归调用。

- int i 是 dfs 函数的参数,表示当前需要处理的节点编号。

关于 auto&& 的使用,它是万能引用(forwarding reference)的一种形式,可以接受任何类型的参数,包括左值和右值,同时也可以接受 const 和 volatile 限定符。

至于为什么要使用这个技巧,主要是为了避免写递归函数时需要繁琐地传递参数,同时也可以减少代码量。但需要注意的是,这种写法可能会让代码难以阅读和理解,因此需要谨慎使用。

那[&]用来干嘛的

[&]是一个lambda表达式的捕获列表,用来捕获外部变量,方便在lambda函数内部使用。在这个例子中,&表示按引用捕获,捕获的是当前作用域中的所有变量,包括函数参数和局部变量。这样,在dfs函数体内部可以直接使用当前作用域中的所有变量,而不需要额外传递参数。

Lambda函数是一种匿名函数,它可以在Python中使用。它是一种特殊的函数,它不需要使用def关键字来定义函数,而是通过lambda关键字来创建。Lambda函数通常被用来在代码中定义一个简单的函数,而不需要定义一个完整的函数。Lambda函数通常只包含一个表达式,这个表达式的结果就是函数的返回值。Lambda函数可以作为参数传递给其他函数,也可以作为返回值返回。Lambda函数的语法如下: lambda 参数列表: 表达式 其中,参数列表包含函数的参数,多个参数用逗号隔开,表达式是函数的返回值。例如,下面是一个简单的Lambda函数的例子: ``` f = lambda x: x**2 print(f(2)) ``` 这个Lambda函数的功能是计算一个数的平方。在这个例子中,我们将函数赋值给变量f,并且通过调用f(2)来计算2的平方。

string path(n, 0);

这行代码定义了一个名为 "path" 的字符串类型变量,长度为 n,初始值全部为 0。在C++中,字符串是由字符组成的数组,因此这里的 "path" 是一个长度为 n 的空字符串。

int n = digits.length(); int n = digits.size();有什么区别

两者作用相同,都是获取容器(如字符串、数组等)的元素个数,但是使用方法稍有不同。

1. `digits.length()`:适用于字符串类型,返回值类型为 `size_t`,表示字符串的字符数(不包括结尾的空字符'\0')。

2. `digits.size()`:适用于 STL 容器类型(如 vector、array、deque 等),返回值类型为 `size_type`,表示容器中元素的个数。

对于数组而言,两种方式都可以使用,但是 `digits.size()` 更符合 STL 的设计思想,建议使用 `digits.size()` 获取数组的长度。

分割回文串

分割回文串是指将一个字符串分割成若干个子串,使得每个子串都是回文串。回文串是指正读和反读都一样的字符串。例如,对于字符串 "aab", 可以将其分割成 ["a", "a", "b"] 或 ["aa", "b"],都是分割回文串的结果。分割回文串是一个经典的算法问题,可以采用动态规划等算法进行求解。

假设我们当前搜索到字符串的第 i 个字符,且 s[0..i−1] 位置的所有字符已经被分割成若干个回文串,并且分割结果被放入了答案数组 ans 中,那么我们就需要枚举下一个回文串的右边界 j,使得 s[i..j] 是一个回文串。

因此,我们可以从 i 开始,从小到大依次枚举 j。对于当前枚举的 j 值,我们使用双指针的方法判断 s[i..j] 是否为回文串:如果 s[i..j] 是回文串,那么就将其加入答案数组 ans 中,并以 j+1 作为新的 i 进行下一层搜索,并在未来的回溯时将 s[i..j] 从 ans 中移除。

作者:力扣官方题解
链接:https://leetcode.cn/problems/palindrome-partitioning/solutions/639633/fen-ge-hui-wen-chuan-by-leetcode-solutio-6jkv/ 

. - 力扣(LeetCode)

拓扑排序

拓扑排序是指将有向无环图(DAG)中的节点按照一定的顺序进行排序的算法。在拓扑排序中,如果存在一条从节点 A 到节点 B 的有向边,那么在排序结果中节点 A 出现在节点 B 的前面。拓扑排序可以用来解决一些问题,比如编译器中的依赖关系分析,任务调度等。 拓扑排序的具体实现方法一般有两种,分别是 Kahn 算法和 DFS 算法。 Kahn 算法的基本思想是维护图中所有节点的入度,每次选择一个入度为 0 的节点加入排序结果,并将其所有邻接节点的入度减 1。不断执行上述操作,直到所有节点都已加入排序结果或者发现图中存在环。 DFS 算法的基本思想是从图中某个节点开始进行深度优先搜索(DFS),并在搜索过程中记录已访问节点的顺序。当所有邻接节点都已访问并标记后,将该节点加入排序结果中。不断执行上述操作,直到所有节点都已加入排序结果。 两种方法各有优缺点,Kahn 算法的时间复杂度为 O(V+E),DFS 算法的时间复杂度为 O(V+E),但实际运行时间与图的结构有关。在实际应用中,可以根据具体问题的特点选择合适的方法。

push   push_back

stack<int> nums; nums.push()和nums.push_back()
stack<int>是一个后进先出的数据结构,只能在栈顶插入元素,因此只能使用nums.push()来插入元素。nums.push_back()是vector和deque的成员函数,用于在容器的尾部插入元素,与栈无关。

vector<int> res;和vector<int> res(n, 0)

`vector<int>` 是一个动态数组,用于存储整数类型的数据。它可以在需要时自动调整大小,因此可以方便地添加或删除元素。你可以使用 `res.push_back()` 向其末尾添加元素,使用 `res.pop_back()` 删除末尾元素,使用 `res.size()` 获取当前元素数量等等。

vector<int> res(n, 0)这是一个长度为n的vector,每个元素都是int类型,初始值都为0。vector是一种动态数组,可以在运行时动态地调整大小,类似于数组但更加灵活。

最长公共子序列

. - 力扣(LeetCode)

vector<vector<int>> dp(m + 1, vector<int>(n + 1));

vector<vector<int>> dp(m + 1, (n + 1)); 那这俩是不是一个意思

char c1 = text1.at(i - 1);

char c1 = text1[i - 1];这两是不是一个意思

是的,这两行代码的意思是一样的,都是用来获取字符串 `text1` 中第 `i-1` 个字符。

编辑距离

. - 力扣(LeetCode)

这是一个动态规划的算法,用于计算将一个字符串转化为另一个字符串的最小编辑距离。

首先定义一个二维数组 D,D[i][j] 表示将 word1 的前 i 个字符转化为 word2 的前 j 个字符的最小编辑距离。

接下来考虑状态转移方程。假设 word1 的第 i 个字符为 c1,word2 的第 j 个字符为 c2。

情况一:如果将 c1 替换为 c2,则需要操作一次。因此最小编辑距离为 D[i-1][j-1]+1。

情况二:如果将 c1 删除,则需要操作一次。因此最小编辑距离为 D[i-1][j]+1。

情况三:如果将 c2 插入到 c1 的后面,则需要操作一次。因此最小编辑距离为 D[i][j-1]+1。

其他

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值