leetcode 1 两数之和
第一次做题的时候不知道规则,傻乎乎的自己写了个主函数,导致测试用例各种类型不匹配耽误了不少时间,总结是对vector容器不熟悉,vector<int>类型和int的数组类型混淆,以及对vector容器的初始化不了解,对获取数组长度的函数用法不熟
vector容器本质上仍是数组,vector是STL标准库中提供的模板类,里面封装了动态数组(不定长数组),通过参数决定存放的数据类型
vector类的类型定义
template<class T, class Alloc = allocator<T>>
class vector
{
private:
T *start_;
T *finish_;
T *end_;
……
}
模板参数中class T表示存放的数据类型,class Alloc表示分配器的类型,如果省略该模板参数的值,将默认使用allocator<T>,用new和delete分配和释放内存
分配器
在C++中,分配单个对象时,通常将内存分配和对象初始化组合在一起,分配内存后调用类的构造函数初始化对象;
当分配较大内存时,我们可以使用分配器,将内存分配和构造对象分开,分配出来的内存是未构造的,我们可以按需使用其中的某一块内存
而allocator是一个类,allocator对象为数据类型为T的对象分配内存
start_,finish_,end_这三个指针的意义在图中很容易理解,黄色表示已使用的内存大小,黄加蓝表示的是整个分配的内存大小
vector<int>类型和int的数组类型的区别
在于vector<int>是C++标准库中的容器,可以动态分配内存大小,而int数组类型是C++的基本数据类型,其内存大小是预先分配好的,两者的相同点仅在于存放的数据类型是相同的(也是我一开始混淆的点),两种类型之间有很大的差别,不能当作同一种类型
vector容器有构造,赋值,插入删除,比较等操作,涉及到多个函数,下面举例部分:
vector相关函数
构造函数:
vector(); // 创建一个空的vector容器
vector(initializer_list<T> il); // 使用统一初始化列表进行对象初始化 例: vector a({1,2,3})或vector a={1,2,3}(不能加explicit)
vector(const vector<T>& v); // 拷贝构造函数
explicit vector(const size_t n); // 创建vector容器,元素个数为n(容量和实际大小都是n)
赋值函数:
vector &operator=(const vector<T> &v); // 把容器v赋值给当前容器
vector &operator=(initializer_list<T> il); // 用统一初始化列表给当前容器赋值
插入删除:
void push_back(const T& value); // 在容器的尾部追加一个元素
void emplace_back(…); // 在容器的尾部追加一个元素,…用于构造元素
emplace_back和push_back区别在于可以直接构造对象,不用传已构造好的对象进去
iterator insert(iterator pos, const T& value); // 在指定位置插入一个元素,返回指向插入元素的迭代器
iterator emplace (iterator pos, …); // 在指定位置插入一个元素,…用于构造元素,返回指向插入元素的迭代器
void pop_back(); // 从容器尾部删除一个元素
iterator erase(iterator pos); // 删除指定位置的元素,返回下一个有效的迭代器
补充获取数组\字符串长度的四种函数:
strlen()函数:
strlen属于C库函数,用于计算字符串str的长度直到空结束字符(不包括\0)
例:a="hello";strlen(a) 此时返回值为5
sizeof()运算符:
sizeof 是一个关键字,它是一个编译时运算符,用于判断变量或数据类型的字节大小,可用于获取基本数据类型、类、结构、共用体和其他用户自定义数据类型的大小。
例:sizeof(int) 此时返回值为4 a[2]={1,2};sizeof(a) 此时返回值为2*4=8
值得一提的是sizeof计算字符串大小时算上空结束符\0
length()函数:
length函数是string类库函数,用于计算字符串的长度
例:a="hello" a.length() 此时值为5
size()函数:
size函数是vector类库函数,返回vector中存在的元素个数,可能与vector的实际容量不同;size函数也可用于求字符串长度
例:假设有一个大小为5的vector容器v里面存放了3个元素 则v.size()=3
leetcode 4 寻找两个正序数组的中位数
这题试了两种方法都无法通过,也无法满足时间复杂度的要求,只能想到二分查找,但是想不到如何下手,加之身体不舒服,只能选择最简单最暴力的解法...
下面是leetcode的官方题解:
这里将问题转化为查找第k小的数,对数组A,数组B分别进行二分查找,把k看作最终的查找目标,数组0到k之间看作一个小的数组,此时k/2-1是这个小数组的中位数
然后给出查找的规则,两个小数组的中位数的较小值,不可能是第k小的数,因此排除掉一部分数据
当A小数组的中位数小于B小数组的时,则把0到k/2-1的数全部排除,等于大于的情况也是这样处理,这样就完成了第一次二分查找,排除掉了k/2个数
之后继续二分查找,直到k==1(此时总共排除掉了k-1个数),比较两数组第k个数其中较小值就是两个数组的中位数
针对一个数组特别短,一个数组较长导致k/2-1大于数组长度时的情况,这时数组短的中位数直接选取其最后一个元素,更新k的值的时候要根据实际情况
完整代码:
class Solution { public: int getKthElement(const vector<int>& nums1, const vector<int>& nums2, int k) { /* 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较 * 这里的 "/" 表示整除 * nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个 * nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个 * 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个 * 这样 pivot 本身最大也只能是第 k-1 小的元素 * 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组 * 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组 * 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数 */ int m = nums1.size(); int n = nums2.size(); int index1 = 0, index2 = 0; while (true) { // 边界情况 if (index1 == m) { return nums2[index2 + k - 1]; } if (index2 == n) { return nums1[index1 + k - 1]; } if (k == 1) { return min(nums1[index1], nums2[index2]); } // 正常情况 int newIndex1 = min(index1 + k / 2 - 1, m - 1); int newIndex2 = min(index2 + k / 2 - 1, n - 1); int pivot1 = nums1[newIndex1]; int pivot2 = nums2[newIndex2]; if (pivot1 <= pivot2) { k -= newIndex1 - index1 + 1; index1 = newIndex1 + 1; } else { k -= newIndex2 - index2 + 1; index2 = newIndex2 + 1; } } } double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { int totalLength = nums1.size() + nums2.size(); if (totalLength % 2 == 1) { return getKthElement(nums1, nums2, (totalLength + 1) / 2); } else { return (getKthElement(nums1, nums2, totalLength / 2) + getKthElement(nums1, nums2, totalLength / 2 + 1)) / 2.0; } } }; 作者:力扣官方题解 链接:https://leetcode.cn/problems/median-of-two-sorted-arrays/solutions/258842/xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-s-114/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
程序员面试金典 01.01判定字符是否唯一
今天做了两题,这题太简单了不讲
程序员面试金典 01.02 判定是否互为字符重排
看了题解才知道可以用哈希表或者排序来做,思路倒是和题解一样,属于是知识没运用上,自己写的算法只能处理短字符串,还是对数据结构不熟悉
下面分析两种解法,首先是排序:
这里用到了sort()函数(又是一个没见过的知识点)
sort()函数
sort()函数是C++标准模板库STL中的一个模板函数,可以对数据按需求进行排序,默认是从小到大排序
sort()函数有三个参数,begin,end,cmp,其中begin是第一个元素的指针,end是最后一个元素的下一位的指针,cmp是排序准则,可以自己编写cmp函数自定义排序准则
C++ sort()排序详解_晴空๓的博客-CSDN博客 大佬写的sort函数详解,里面写的很详细了
begin()函数和end()函数
string类下的begin()函数,返回一个迭代器,指向字符串的第一个元素;
而end()函数返回一个迭代器,指向字符串的末尾(最后一个字符的下一个位置)
这里涉及到迭代器的概念:
迭代器
迭代器是一种检查容器内元素并遍历元素的数据类型,通常用于对C++中各种容器内元素的访问,不同的容器有不同的迭代器
第二种解法,哈希表:
建立一个长度为128的哈希表,遍历字符串然后在哈希表上进行加减操作,若最后哈希表中数据有小于0的即为出错
这里涉及到一个C++11标准的技巧:简化for循环
简化for循环for(auto c:s)
c是变量名称可自定义 s是容器名称,也就是循环的对象 auto会根据容器存放的数据自动推导数据类型
c前还可以加&引用符合:
当for(auto &c:s)中加了引用符号,就可以对容器中的内容进行赋值,即可通过对c赋值替换s中的数据,不加&则无法改变s中的数据
程序员面试金典 01.03 URL化
今天做了两简单题,这题一开始想直接替换发现行不通,后来用复制的方法在空字符串上操作,利用好数组类型的特性是解题的要点
程序员面试金典 01.04 回文排序
统计数据个数,想到了前几天的哈希表,也用上了C++11的简化for循环
解决问题的关键在于要知道回文是什么,回文的本质是什么,学会观察数据并将其转换为规律(奇数个数不大于1)
程序员面试金典 01.05 一次编辑
一开始进行了分类讨论,长度是否相同,不相同再分差多少字符,相同看字符有多少个相同的,这样我想到了用哈希表去统计字符个数,但是不如直接遍历字符串来的方便,实现难度也高,这是思路上面出现了错误
官方的题解的思路更加清晰,围绕哪些字符串需要一次编辑分类讨论,需要对数据的敏锐观察
程序员面试金典 01.06 字符串压缩
这题思路是创建一个空字符串,遍历原来的字符串将需要的数据扒下来,先判断新字符串是否为空,为空则将第一个字符加入到空串,用last记录前一个字符,num记录相同的字符数量,当c和last不同时将num和c加入字符串中,如此往复直到原来的字符串完全遍历
字符串拼接整型数据
1.使用+=运算符和string类的to_string函数
例:S+=to_string(number)
2.使用std::stringstream(字符串流),stringstream是sstream中的一个类,其可以将多种输入类型并以字符串格式存储,其对象可以将基本数据类型转换(流插入运算符<<)成字符串,也可以将字符串转换(流提取运算符>>)成其他数据类型
例:将app_str和number放入tmp_stream中
stringstream类对象的清空操作:c++ stringstream 的clear()清空误区-菜鸟笔记 (coonote.com)
3.使用append()函数
例:S.append(to_string(number))
程序员面试金典 01.07 旋转矩阵(leetcode 48)
题目要求的不占用额外空间实在做不到,借助辅助数组可以通过,主要还是需要对位置的观察,发现行列之间的转换关系
用翻转的方法也挺有意思的,相当于先转一个90度再转一个180度
程序员面试金典 01.08 零矩阵
要避免在第一次循环里进行操作,否则会出现内存溢出的情况,借助标志数组将目标进行标记再循环赋值
leetcode 283 移动零
经典的快慢指针,想清楚快指针慢指针怎么样动,什么时候两者交换
慢指针是遇到0则停准备比较,快指针则是不停(否则容易进入快指针内容为0的时候才前进,整个循环体用while的误区,因为如果0较少快指针将永远无法到达数组末尾,造成超时)
还有一种思路是遇到非零则交换,后面再补零,类似于创建新数组
同样用到快慢指针的还有leetcode 27 移除元素
这题的思路是和上面的第二种方法是差不多的