数组
1. 二分搜索法
注意三点:
1.区间的闭合,定义的左右区间是要合理[1,1)不对, [1,1]对,[1,2)对
//左闭右闭,初始下标为0 int left = 0; int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right] //左闭右开,初始下标为0 int right = nums.size(); // 定义target在左闭右开的区间里,[left, right)
2. 当left==right,区间[left, right] [left, right)是否依然有效,和1一起思考
while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=
while (left < right) { // 当left==right,区间[left, right)依然有效,所以用 <
3. 当目标值不在中间时,在新区间,核心要更新新区间的边界值,且middele不在新区间内
//左闭右闭
if (nums[middle] > target)
{
right = middle - 1; // target 在左区间,所以[left, middle - 1]
}
else if (nums[middle] < target)
{
left = middle + 1; // target 在右区间,所以[middle + 1, right]
}
else
{ // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
//左闭右开
if (nums[middle] > target)
{
right = middle; // target 在左区间,所以[left, middle),middle不在新区间内
}
else if (nums[middle] < target)
{
left = middle + 1; // target 在右区间,所以[middle + 1, right]
}
else
{ // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
4.计算middle的一些细节
//左闭右闭
int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
//左闭右开
int middle = left + ((right - left) >> 1);
4.1 int middle = left + ((right - left) >> 1);这个>>1是什么意思?
`>> 1` 是一个右移位运算符,它将操作数的二进制表示右移一位。相当于将操作数除以2,并舍弃小数部分。
在二分查找算法中,`middle = left + ((right - left) >> 1);` 这行代码用来计算数组的中间位置。由于 `(right - left)` 表示当前搜索区间的长度,右移一位相当于取中点的偏移量。将这个偏移量加到 `left` 上就得到了 `middle` 的索引。
这个写法的主要目的是避免在计算 `middle` 时出现整数溢出。如果直接使用 `(left + right) / 2`,当 `left` 和 `right` 都很大的时候,`left + right` 可能会超出整数的范围,而 `left + ((right - left) >> 1)` 则可以避免这个问题。
总结:
- `>> 1` 将数除以2,效率比除法运算高。
- 这种写法可以避免整数溢出,是一种比较安全的计算中间索引的方法。
4.2 如果(right - left)是奇数呢
当 `(right - left)` 是奇数时,右移一位 (`>> 1`) 相当于执行整除操作,即只保留整数部分,舍弃小数部分。
具体来说:
- 假设 `(right - left)` 是奇数,比如 `5`,它的二进制表示是 `101`。
- 右移一位 (`>> 1`) 后,得到 `10`,即 `2`。
这种情况下,`middle` 的计算相当于将 `(right - left)` 除以 `2` 并向下取整。所以即使 `(right - left)` 是奇数,`middle` 的计算仍然是有效的,它会返回向下取整后的中间位置。
例如:
- 如果 `left = 0`,`right = 5`,那么 `(right - left) >> 1` 等于 `2`,此时 `middle = 0 + 2 = 2`。
- 如果 `left = 3`,`right = 8`,那么 `(right - left) >> 1` 等于 `2`,此时 `middle = 3 + 2 = 5`。
因此,无论 `(right - left)` 是奇数还是偶数,这种写法都能正确计算出中间位置。
4.2 偶数呢
当 `(right - left)` 是偶数时,右移一位 (`>> 1`) 仍然有效且直观。
具体来说:
- 假设 `(right - left)` 是偶数,比如 `4`,它的二进制表示是 `100`。
- 右移一位 (`>> 1`) 后,得到 `10`,即 `2`。
此时 `(right - left) >> 1` 就等于 `2`,这意味着中间索引距离 `left` 的偏移量是 `2`。
例如:
- 如果 `left = 0`,`right = 4`,那么 `(right - left) >> 1` 等于 `2`,此时 `middle = 0 + 2 = 2`。
- 如果 `left = 1`,`right = 5`,那么 `(right - left) >> 1` 仍然等于 `2`,此时 `middle = 1 + 2 = 3`。
因此,无论 `(right - left)` 是奇数还是偶数,`middle = left + ((right - left) >> 1)` 都能正确计算出 `middle` 索引,指向搜索区间的中间位置。这个写法同样对奇数和偶数情况适用。
4.3
右移一位 (>> 1
) 是一种位操作,将一个数的二进制表示向右移动一位。
具体来说,二进制表示的每一位都向右移动一位,右移后的最低位会被舍弃,而最左边则补上 0
(对于无符号整数)或保持符号位不变(对于有符号整数)。这相当于将数字除以 2
并舍弃小数部分。
对于偶数和奇数的情况,右移一位的效果如下:
- 偶数:偶数的二进制表示最后一位是
0
。右移一位后,相当于这个数字除以2
,并且结果是一个整除的结果。- 例如:
4
的二进制表示是100
,右移一位得到10
,即2
。
- 例如:
- 奇数:奇数的二进制表示最后一位是
1
。右移一位后,同样相当于这个数字除以2
,但结果会向下取整。- 例如:
5
的二进制表示是101
,右移一位得到10
,即2
(相当于5 / 2 = 2.5
,取整为2
)。
- 例如:
总结:
- 对于
(right - left)
是偶数时,右移一位得到准确的中间索引。 - 对于
(right - left)
是奇数时,右移一位相当于向下取整,得到一个偏左的中间索引。
这个操作在二分查找中广泛使用,目的是快速计算数组的中间位置,同时避免直接使用除法运算。
4.4 int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2\
这行代码中的 `middle = left + ((right - left) / 2)` 是一种防止溢出的中间索引计算方法,用于替代传统的 `(left + right) / 2`。
### 防止溢出
在计算数组中间位置时,最直接的方式是使用 `(left + right) / 2`,但这种方法在某些情况下可能会导致溢出问题。如果 `left` 和 `right` 都是非常大的正整数,它们相加的结果可能超过整数的最大表示范围,从而导致溢出。
### 安全的计算方式
`left + ((right - left) / 2)` 这种写法是为了避免溢出。通过先计算 `right - left`,确保中间偏移量不会超出整数范围,然后再加上 `left` 来得到中间位置。具体步骤如下:
1. 计算区间长度:`right - left`,它表示当前搜索区间的长度。
2. 计算中间偏移量:`(right - left) / 2`,它表示中间位置相对于 `left` 的偏移。
3. 加上 `left`:`left + ((right - left) / 2)` 得到中间位置 `middle`。
### 举个例子
假设 `left = 1000`,`right = 2000`:
- `(right - left)` 是 `1000`。
- `(right - left) / 2` 是 `500`。
- `left + 500` 得到 `middle = 1500`。
这种方法即使在 `left` 和 `right` 都很大时也不会出现溢出问题。相比于直接使用 `(left + right) / 2`,这种写法更安全可靠。
\
4.5(right - left)为奇数和偶数的情况
在 `right - left` 为奇数和偶数的情况下,`middle = left + ((right - left) / 2)` 的计算方式和结果会有些不同,具体如下:
### 1. `(right - left)` 为偶数的情况
当 `(right - left)` 为偶数时,`(right - left) / 2` 结果是一个整数,这个整数是搜索区间长度的一半。
**例子:**
- 假设 `left = 2`,`right = 8`,则 `right - left = 6`。
- `(right - left) / 2 = 6 / 2 = 3`。
- `middle = left + 3 = 2 + 3 = 5`。
在这种情况下,`middle` 正好位于区间的中点。
### 2. `(right - left)` 为奇数的情况
当 `(right - left)` 为奇数时,`(right - left) / 2` 的结果是一个整数,但它实际上是向下取整后的结果,即小于 `right - left` 的一半。
**例子:**
- 假设 `left = 2`,`right = 7`,则 `right - left = 5`。
- `(right - left) / 2 = 5 / 2 = 2`(整除运算时舍弃小数部分)。
- `middle = left + 2 = 2 + 2 = 4`。
在这种情况下,`middle` 位于区间偏左的位置,也就是说,它会比真正的中点稍微靠近 `left`。
### 总结
- 当 `(right - left)` 为偶数时,`middle` 正好位于区间的中间。
- 当 `(right - left)` 为奇数时,`middle` 会偏向区间的左侧,因为整除操作舍弃了小数部分。
不论是奇数还是偶数情况,这种计算方法都能确保 `middle` 的值在 `[left, right]` 范围内,并且是二分查找的有效实现。
3.移除元素
暴力解法:两个for循环,覆盖;
双指针:快慢指针(读写指针),写指针=0(初始化),快指针入for循环,慢指针写入需求数组
4. 有序数组的平方
错误:定义函数返回值未写到到函数体内部
这个错误通常是由于代码语法或拼写错误引起的。具体来说,expected member name or ';' after declaration specifiers
错误提示说明编译器期望在某个声明后出现成员名称或分号,但并未找到。这通常发生在以下几种情况下:
- 缺少分号:某个声明之后可能缺少了分号
;
。 - 拼写错误:可能有拼写错误导致编译器无法理解代码的含义。
- 函数或变量定义不完整:可能函数或变量的定义不完整,导致编译器无法解析。
可能的原因与解决方法:
-
检查上一行:错误可能不是出现在
return nums;
这一行,而是出现在前一行或更前面的代码中。请检查上一行代码是否缺少分号或有其他语法错误。
你在代码中的 return nums;
语句位于 sortedSquares
函数之外,导致了语法错误。这个问题的原因是 return nums;
语句不在函数体内,编译器无法理解它是属于哪个函数的。
修正后的代码:
要解决这个问题,你需要确保 return nums;
在 sortedSquares
函数的花括号 {}
内。以下是修正后的代码:
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums)
{
// 对数组中的每个元素进行平方
for(int i = 0; i < nums.size(); i++)
{
nums[i] *= nums[i];
}
// 对平方后的数组进行排序
sort(nums.begin(), nums.end());
// 返回排序后的数组
return nums;
}
};
解释:
-
函数体的完整性:
return nums;
必须在sortedSquares
函数体内,因为它是函数的返回值。修正后的代码中,return nums;
已经正确地位于函数的最后一行。
-
返回类型:
- 这个函数的返回类型是
vector<int>
,因此它必须返回一个vector<int>
类型的值。在修正后的代码中,return nums;
返回了经过平方和排序处理的nums
数组。
- 这个函数的返回类型是
-
操作顺序:
- 先对数组中的每个元素进行平方操作,然后使用
sort
函数对平方后的数组进行升序排序。
- 先对数组中的每个元素进行平方操作,然后使用
这个修正后的代码可以正确地完成对输入数组的平方和排序操作,并返回处理后的数组。
6.螺旋矩阵
我们详细解释一下这行代码的语法和参数定义规则:
vector<vector<int>> res(n, vector<int>(n, 0));
1. vector<vector<int>>
:二维向量的声明
vector
是 C++ 标准库提供的动态数组,可以根据需要自动调整大小。vector<int>
表示一个存储int
类型元素的一维向量。vector<vector<int>>
表示一个存储vector<int>
类型元素的二维向量,实际上可以看作是一个“向量的向量”,即二维数组。
2. res
:二维向量的变量名
res
是声明的这个二维向量的变量名,它用于存储最终生成的矩阵。
3. res(n, vector<int>(n, 0))
:二维向量的初始化
这部分代码用来初始化二维向量 res
,将其构建为 n
行 n
列的矩阵。
3.1 n
(外层 vector
的大小)
- 外层的
vector
(即res
)有n
个元素,对应于矩阵中的n
行。
3.2 vector<int>(n, 0)
(内层 vector
的初始化)
vector<int>(n, 0)
是内层向量的构造函数,表示生成一个大小为n
的一维向量,并将其中的每个元素都初始化为0
。n
表示内层vector<int>
的大小,即每一行的元素个数(列数)。0
是内层vector
的初始值,也就是说每一行的所有元素初始值都是0
。
3.3 res(n, vector<int>(n, 0))
组合起来的含义
res(n, vector<int>(n, 0))
表示创建一个n x n
的二维向量矩阵,其中每个元素初始化为0
。
举个例子:
arr[][] ={ [1, 0, 0],[0, 0, 0],[0, 0, 0]}是3x3矩阵
这个矩阵由三个一维数组(行)组成,每个数组包含三个元素(列)。矩阵的行和列数相等,因此称为 3x3
矩阵。
具体来说,矩阵的表示如下
1 0 0
0 0 0
0 0 0
如果我们用代码表示这个矩阵,可以这样写:
int arr[3][3] = {
{1, 0, 0},
{0, 0, 0},
{0, 0, 0}
};
同理
3.3
res(n, vector<int>(n, 0))
组合起来的含义
res(n, vector<int>(n, 0))
表示创建一个n x n
的二维向量矩阵,其中每个元素初始化为0
。
总结
这一行代码的整体功能是创建了一个 n
行 n
列的二维矩阵,并且矩阵中的所有元素都被初始化为 0
。每一行是一个一维向量(vector<int>
),整个矩阵是一个包含这些一维向量的二维向量(vector<vector<int>>
)。
哈希表
思路:
1.当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。
但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。
2. 去重就要想到set
此时就要使用另一种结构体了,set ,关于set,C++ 给提供了如下三种可用的数据结构:
- std::set
- std::multiset (可以重复)
- std::unordered_set(自动去重)
std::set和std::multiset底层实现都是红黑树,
std::unordered_set的底层实现是哈希表, 使用unordered_set 读写效率是最高的,并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set。
3.349. 两个数组的交集
https://leetcode.cn/problems/intersection-of-two-arrays/
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result;
unordered_set<int> num_set(nums1.begin(),nums1.end());//自动去重
for(int i = 0;i<nums2.size();i++)
{
if(num_set.find(nums2[i]) != num_set.end())//如果在nums_set中找到了nums[i]
{
result.insert(nums2[i]);
}
}
// nums1.insert(nums1.end(),nums2.begin(),nums2.end());
// return result;//错误
return vector<int>(result.begin(),result.end());//这行代码将 unordered_set 中的结果转换为一个 vector 并返回。unordered_set 是无序的,所以返回的 vector 可能是无序的。
}
};
语法讲解:
if(num_set.find(nums2[i]) != num_set.end())
2.
num_set.find(nums2[i])
num_set
是一个unordered_set<int>
类型的集合。.find()
是unordered_set
的一个成员函数,它的作用是查找集合中是否存在某个元素。nums2[i]
是nums2
数组的第i
个元素。
num_set.find(nums2[i])
的意思是,在num_set
集合中查找是否存在nums2[i]
这个元素。
- 如果找到了这个元素,
find()
函数会返回一个指向该元素的迭代器(类似于指针,指向元素在集合中的位置)。- 如果没有找到这个元素,
find()
函数会返回一个特殊的迭代器,指向集合的末尾,这个末尾是用num_set.end()
表示的。
这句代码的意思是:
- 先查找
nums2[i]
这个元素是否在num_set
中。 - 如果找到了(即
find
返回的迭代器不是num_set.end()
),则条件为true
,if
语句块内的代码会被执行。 - 如果没找到(即
find
返回的迭代器是num_set.end()
),则条件为false
,if
语句块内的代码不会被执行。
错误:
vector<int> result_vec(result.begin(),result.end());
出现问题的原因是因为你在代码中声明了一个新的 vector<int> result
,而 result
这个名字已经在之前被定义为一个 unordered_set<int>
类型的变量。这会导致编译器无法区分这两个 result
,进而产生错误。
这里的 result
被多次使用,导致名称冲突。为了解决这个问题,你可以使用不同的变量名来避免冲突。例如,可以将 unordered_set<int> result
改为 unordered_set<int> result_set
,然后用 result_set
初始化新的 vector<int>
。
vector<int> result(result_set.begin(), result_set.end()); // 使用 result_set 进行初始化
sort(result.begin(), result.end());
return result;
注意和这个区别
return vector<int>(result. Begin(), result. end());
这行代码将 unordered_set
中的结果转换为一个 vector
并返回。unordered_set
是无序的,所以返回的 vector
可能是无序的。
!!!不需要重新定义变量名
3.第202题. 快乐数
class Solution {
public:
//先取余,算平方和;n再/取整,sum加起来,想法从个位往更高级去取
//条件是n>0(正整数)
//徐定义sum n
int getsum(int n)
{
int sum = 0;
while(n>0)
{
sum+=(n%10)*(n%10);
// sum+=(n%10)^2; //这个不行,拓展答疑
n = n/10;
}
return sum;
}
//unordered_set一个哈希表,或矩阵向量,存储暂时性的sum不为1的数
// 如果sum==1 返回true
// 否则再set里寻找sum,如果没有相同==没有陷入死循环==》insert进去
//如果有相同,false
bool isHappy(int n)
{
// unordered_set vector<int> result; //拓展答疑语法错误
unordered_set<int> result;
int sum = getsum(n);
while(1)//拓展答疑
{
if(sum==1)
{
return true;
}
else if(result.find(sum) != result.end())
{
return false;
}
else{
result.insert(sum);
}
sum = getsum(sum);
}
}
};
/* 拓展答疑while(1)
while(1) 是一个无限循环的写法。!!!
!!!!
在 C++ 中,while 循环的条件部分要求是一个布尔表达式,
如果表达式为 true,则循环会继续执行;如果为 false,则循环会终止。
为什么使用无限循环?
在这个 isHappy 函数中,使用 while(1) 是为了让代码不断计算数字的平方和,直到满足特定的条件后才跳出循环并返回结果。这些特定的条件包括:
sum == 1:一旦计算出的平方和等于 1,函数就会返回 true,因为 n 是一个快乐数。
result.find(sum) != result.end():如果在 unordered_set 中找到了这个平方和,说明已经陷入了循环,函数返回 false,表示 n 不是一个快乐数。
由于循环条件 while(1) 保证了循环会一直运行,直到遇到上述的返回条件,这种写法可以确保所有可能的情况都被正确处理。
使用无限循环的优势
简洁:通过 while(1),你不需要在循环条件中判断具体的退出条件,只要在合适的时候用 return 语句退出即可。
控制灵活:你可以在循环内部根据不同的条件随时退出循环,而不需要在循环条件中进行复杂的判断。
退出无限循环
在这段代码中,退出无限循环的方式是使用 return 语句:
当 sum == 1 时,直接 return true; 退出循环,表示这是一个快乐数。
当 result.find(sum) != result.end() 时,直接 return false; 退出循环,表示不是一个快乐数。
这种写法有效避免了不必要的条件检查,使得代码更加简洁高效。
*/
/* 拓展答疑sum+=(n%10)^2
在 C++ 中,(n % 10) * (n % 10) 表示计算 n 的最后一位数字的平方。你提到的 ^ 运算符其实是按位异或(bitwise XOR)运算符,而不是用于数学上的幂运算。因此,(n % 10)^2 不能正确地表示计算平方。
具体解释:
^ 运算符:在 C++ 中,^ 是按位异或运算符,它对两个整数的每一位进行异或操作。如果这两个整数在同一位上有相同的二进制位(即都是 0 或都是 1),结果为 0;如果不同,结果为 1。
示例:
假设 n % 10 的结果是 3,那么 (n % 10)^2 实际上计算的是 3^2,按位异或运算的结果不是 9(即 3 * 3),而是 1(因为 3 的二进制表示是 11,2 的二进制表示是 10,11 XOR 10 = 01,结果是 1)。
正确的平方计算:
正确的平方计算应该用乘法运算符 *,例如 (n % 10) * (n % 10),这表示将 n 的最后一位数字的平方相乘。
因此,如果你想计算 n 的最后一位数字的平方,应该使用乘法运算符 *,而不是按位异或运算符 ^。
*/
/*拓展答疑:unordered_set vector<int> result;
语法错误和类型定义不正确。
具体问题!!!
缺少模板参数:unordered_set 是一个模板类,需要指定其中存储的元素类型。你在 unordered_set 后面没有加上模板参数,应该是 unordered_set<int>,表示存储整数的哈希集合。
混合了不同的数据结构:unordered_set 是一个集合类型,而 vector<int> 是一个动态数组类型。你不能将它们直接混合在一起。这里的 vector<int> 作为类型应该独立存在,而不是作为 unordered_set 的模板参数。
正确的写法!!!
如果你想声明一个存储 int 类型的 unordered_set,应该这样写:
cpp
复制代码
unordered_set<int> result;
这段代码声明了一个名为 result 的哈希集合,其中存储的是整数。
如果你还需要一个 vector<int>,可以单独声明它:
cpp
复制代码
vector<int> vec;
综合示例
如果你需要同时使用 unordered_set<int> 和 vector<int>,可以这样写:
cpp
复制代码
unordered_set<int> result; // 用于存储整数的集合
vector<int> vec; // 用于存储整数的向量
这两者是独立的容器,分别用于不同的目的。
总结来说,unordered_set<int> 和 vector<int> 是两种不同的数据结构,不能直接混合使用,必须分别声明,并各自具有自己的模板参数和数据类型
*/
1. 两数之和 - 力扣(LeetCode)
3.map的使用
class Solution{
public:
vector<int> twoSum(vector<int>& nums, int target)
{
//unordered_map<int> result用来存储,键值对,
//搜索的时候查找有没有target-nums[i],没有把该键值对加入,有就返回两个下表
//定义 map iter = result.find(tarfet-nums[i])
//return {iter->second,i}
unordered_map<int,int> result;//定义拓展答疑
for(int i = 0; i<nums.size(); i++)
{
int m = target - nums[i];
// int iter = result.find(m);
auto iter = result.find(m); //拓展答疑auto
if(iter != result.end())
{
return {iter->second,i}; //拓展答疑
}
else
{
result.insert(pair<int, int>(nums[i],i));//pair拓展答疑
}
}
return {};
}
};
1.为什么是auto int不对
在 C++ 中,
auto
关键字用于自动推断变量的类型。它让编译器根据初始化表达式的类型自动确定变量的类型。在你提到的这行代码中:
auto iter = result.find(m);
使用
auto
是正确的,因为result.find(m)
返回的是一个迭代器,auto
可以自动推断出这个迭代器的类型。这个迭代器类型比较复杂,所以使用auto
可以简化代码,不必手动写出具体的类型名称。
1. 为什么不能用
int
?如果你写成:
int iter = result.find(m);
这是错误的,因为
result.find(m)
返回的类型并不是int
。find()
方法返回的是一个迭代器,而不是整数。
unordered_map<int, int>::iterator
类型:result.find(m)
的返回值是一个unordered_map<int, int>::iterator
类型的迭代器,用于指向哈希表中的某个元素。这个迭代器类型很长,所以用auto
来简化代码。
auto
的优势
- 简化代码:对于复杂类型,
auto
可以让代码更加简洁和易读。- 减少错误:手动指定复杂的类型容易出错,
auto
能减少这种错误。结论
auto
是正确且推荐的用法,而使用int
是不对的,因为类型不匹配,无法编译通过。
2.pair 为什么可以直接用,不用提前申明定义
在 C++ 中,
pair
是一个标准模板类,用于存储一对值。你可以直接使用pair
,而不需要提前声明或定义它,因为它已经在 C++ 标准库中定义好了。为什么可以直接使用
pair
标准模板库(STL)的一部分:
pair
是 C++ 标准模板库(STL)的一部分,已经被标准库实现并提供给开发者使用。只要你包含了对应的头文件,你就可以直接使用pair
以及它的相关功能。自动推断类型:C++11 及以后版本支持通过
auto
关键字和make_pair
函数自动推断pair
的类型,避免了繁琐的类型声明。示例代码
在使用
pair
时,通常包含<utility>
头文件,这是pair
定义所在的头文件:#include <utility> // 包含 pair 所在的头文件 pair<int, int> p(1, 2); // 定义一个 int 类型的 pair
使用
pair
的场景
直接使用:
result.insert(pair<int, int>(nums[i], i));
这里直接使用
pair
,它存储两个int
类型的值,分别是nums[i]
和i
。使用
make_pair
(C++11 引入):result.insert(make_pair(nums[i], i));
make_pair
函数可以自动推断类型,使代码更简洁。自动类型推断: 在使用 C++11 及以上版本时,你可以直接使用
auto
来声明一个pair
,让编译器推断类型:auto p = make_pair(1, 2);
总结
- 无需提前声明:因为
pair
是 C++ 标准库的一部分,已经在<utility>
头文件中定义好了,可以直接使用。- 自动推断:使用
auto
和make_pair
可以进一步简化代码,不需要手动指定类型。只要包含了相应的头文件(如
<utility>
),你就可以直接在代码中使用pair
,无需额外的声明或定义。
3. iter->second 这是什么?是m的键值对所在位置的第二位吗
是的,
iter->second
指的是在哈希表unordered_map
中,键值对中对应m
的那个键值对的 "值"。具体解释
在 C++ 中,
unordered_map
存储的是键值对(key-value
pairs),每个元素由一个键(key
)和一个对应的值(value
)组成。每个键值对在哈希表中的存储形式可以表示为:pair<const Key, T>
其中:
Key
是键的类型,在你的例子中是int
。T
是值的类型,在你的例子中也是int
。
iter->second
的含义假设你有一个
unordered_map<int, int>
类型的哈希表,并使用find()
函数查找某个键m
,返回一个迭代器iter
。
iter->first
:指向的是该迭代器所在位置的键,即m
。iter->second
:指向的是该迭代器所在位置的值,即与键m
相关联的值。在你的
twoSum
代码中:if(iter != result.end()) { return {iter->second, i}; }
这里的
iter->second
获取的是与键m
相关联的索引值(即在result
中存储的某个之前遍历过的数字的索引)。i
是当前正在遍历的索引。总结
iter->second
是哈希表中键值对中的 "值",它与m
(即target - nums[i]
)这个键相关联。- 在
twoSum
问题中,iter->second
是之前存储的某个索引,表示数组中第一个符合条件的数的位置,i
是当前数的位置。最终返回这两个索引。
3 15. 三数之和 - 力扣(LeetCode)
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
//三数和,同一个数组,左右指针,一层for
//定义矩阵,
vector<vector<int>> result;
// vector<vector<int>> sum;
sort(nums.begin(),nums.end());
for(int i = 0; i<nums.size(); i++)
{
int left = i+1;
int right = nums.size()-1;
if(nums[i] > 0)
{
return result;
}
//去重的操作
if(i>0 && nums[i] == nums[i-1]){
continue;
}
while(left < right)
{
if(nums[i]+nums[left]+nums[right] < 0){
left++;
}
else if(nums[i]+nums[left]+nums[right] > 0){
right--;
}
else if(nums[i]+nums[left]+nums[right] == 0){
/*********************一******************************/
// result.push_back(vector<int>{nums[i],nums[left],nums[right]});
/**********************二*******************************/
// vector<int> sum = {nums[i],nums[left],nums[right]};
// result.push_back(sum);
/***********************三*****************************/
// vector<int> sum;
// sum.insert(sum.end(),nums[i]);
// sum.insert(sum.end(),nums[left]);
// sum.insert(sum.end(),nums[right]);
// result.push_back(sum);
/***********************四*****************************/
vector<int> sum;
sum.push_back(nums[i]); // 将 nums[i] 插入到 sum 的末尾
sum.push_back(nums[left]); // 将 nums[left] 插入到 sum 的末尾
sum.push_back(nums[right]); // 将 nums[right] 插入到 sum 的末尾
result.push_back(sum); // 将 sum 作为一个整体添加到 result 中
/***********************错误*****************************/
// vector<int> sum;
// sum.insert(nums[i]);
// sum.insert(nums[left]);
// sum.insert(nums[right]);
// result.push_back(sum);
/*错误
// result.insert(result.end(),sum.begin(),sum.end());//错误
// sum.push_back(sum.end(),nums[i]); //错误
// sum.insert(nums[left]);
// sum.insert(nums[right]);
// result.insert(sum);
*/
left++;
right--;//没有双指针收缩操作,会超出时间限制
}
}
}
return result;
}
};
push_back 和 insert的区别详解
push_back
和 insert
是 C++ 中 vector
类的两个常用方法,用于向 vector
中添加元素。它们的使用方式和作用略有不同。
1.
push_back
push_back
用于在 vector
的末尾添加元素。它是一个简单的操作,只需将一个元素追加到 vector
的末尾。
用法:
vector<int> vec;
vec.push_back(10); // 将10添加到vec的末尾
vec.push_back(20); // 将20添加到vec的末尾
特点:
- 只在
vector
的末尾添加元素。 - 适合用于需要动态扩展
vector
的情况。 - 时间复杂度为均摊 O(1)。
2.
insert
insert
用于在 vector
的指定位置插入一个或多个元素。它可以在 vector
的任意位置插入元素,不局限于末尾。
用法:
vector<int> vec = {10, 20, 30};
vec.insert(vec.begin() + 1, 15); // 在vec的第二个位置插入15,vec变为{10, 15, 20, 30}
vector<int> vec2 = {1, 2, 3};
vec.insert(vec.end(), vec2.begin(), vec2.end()); // 在vec的末尾插入vec2的所有元素
特点:
- 可以在
vector
的任意位置插入元素。 - 可以插入一个元素或一个范围内的多个元素。
- 插入操作可能涉及到元素的移动,因此时间复杂度为 O(n),其中
n
是vector
中的元素数量。
总结对比
-
用法范围:
push_back
只能在末尾添加单个元素。insert
可以在任意位置插入单个或多个元素。
-
效率:
push_back
更高效,特别是当vector
需要频繁添加元素时。insert
灵活性更高,但效率较低,尤其是在vector
的中间插入时。
-
使用场景:
- 当只需要向
vector
添加元素到末尾时,使用push_back
。 - 当需要在
vector
中间插入元素,或者插入多个元素时,使用insert
。
- 当只需要向
错误
insert(sum)在undered_set中可以直接用,其他地方vector<int>不可以,必须有位置和元素
vector<int>不可以
/***********************错误*****************************/
// vector<int> sum;
// sum.insert(nums[i]);
// sum.insert(nums[left]);
// sum.insert(nums[right]);
// result.push_back(sum);
你的意图是将 nums[i]
, nums[left]
, 和 nums[right]
依次插入到 sum
中,然后将 sum
添加到 result
中。然而,sum.insert()
的使用方式在这里是错误的。这个是在一个
insert
方法的正确用法
insert
方法用于在vector
的指定位置插入元素或范围。- 正确的
insert
用法需要两个参数:插入的位置和要插入的元素。
在这种情况下,应该使用 push_back
而不是 insert
。
push_back
是用于在 vector
的末尾添加一个元素的简洁方法。
修正后的代码
你可以将代码修改如下:
vector<int> sum; // 初始化一个空的 vector<int>
sum.push_back(nums[i]); // 插入 nums[i]
sum.push_back(nums[left]); // 插入 nums[left]
sum.push_back(nums[right]); // 插入 nums[right]
result.push_back(sum); // 将 sum 作为一个整体添加到 result 中
解释
sum.push_back(nums[i]);
: 将nums[i]
添加到sum
的末尾。sum.push_back(nums[left]);
: 将nums[left]
添加到sum
的末尾。sum.push_back(nums[right]);
: 将nums[right]
添加到sum
的末尾。result.push_back(sum);
: 将sum
整体添加到result
中。
总结
对于你的需求,push_back
更适合。insert
一般用于在特定位置插入元素,或者插入一个范围内的多个元素。而在这种场景下,使用 push_back
更直观和简洁。
undered_set中可以直接用
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result_set; // 存放结果,之所以用set是为了给结果集去重
unordered_set<int> nums_set(nums1.begin(), nums1.end());
for (int num : nums2) {
// 发现nums2的元素 在nums_set里又出现过
if (nums_set.find(num) != nums_set.end()) {
result_set.insert(num);
}
}
return vector<int>(result_set.begin(), result_set.end());
}
};
//这个里面为什么可以直接result_set.insert(num);
在这段代码中,result_set.insert(num);
是一个有效的操作,因为 result_set
是一个 unordered_set<int>
类型的集合,insert
方法是 unordered_set
的成员函数。
unordered_set
的 insert
方法
- 功能:
insert
方法将一个元素插入到unordered_set
中。 - 特性:
unordered_set
是一个不允许重复元素的数据结构。如果你试图插入一个已经存在的元素,unordered_set
会自动忽略这个重复的插入操作,不会存储重复的值。
为什么可以直接使用 insert
?
-
数据类型匹配:
result_set
是一个unordered_set<int>
类型,而num
是一个int
类型,数据类型匹配,因此可以直接插入。 -
去重功能:
unordered_set
自动处理重复元素。当num
插入result_set
时,如果该元素已经存在,unordered_set
会自动忽略这次插入。这就是为什么你可以直接使用insert
方法,而不用担心会有重复的元素。
result.push_back(vector<int>{nums[i],nums[left],nums[right]});
使用 result.push_back(vector<int>{nums[i], nums[left], nums[right]});
是一种更简洁且常用的方法来将三个元素 nums[i]
、nums[left]
和 nums[right]
组合成一个 vector<int>
,并直接插入到 result
中。这个语法是完全正确的,并且比之前的方式更加简洁明了。
解释
-
vector<int>{nums[i], nums[left], nums[right]}
:- 这是一个临时的
vector<int>
对象,包含三个元素:nums[i]
、nums[left]
和nums[right]
。 {}
初始化列表被用来直接构造这个临时对象。
- 这是一个临时的
-
result.push_back(...)
:push_back
方法会将上述临时构造的vector<int>
对象插入到result
中。