Part 1 数组的一些注意点
第一天学习的内容是数组,基础的内容就按下不表,浅记一下补上的漏洞
乍一看可能比较奇怪,但是仔细思考一下很简单。关注一下数组的本质其实是内存上开辟的一串连续的内存空间。在程序中,只能将内存空间中存储的内容改写,而不能完全去除(即使动态数组也只是释放)。
tip:虽然还没学过Java,但是先浅记一下Java中的二位数组的空间地址并不是连续的(虚拟机处理过)
这道题应该是最基础的二分搜索法(第一次好好写这种题目)
一开始觉得挺简单的,但是一直没有通过。
发现二分法最重要的细节在于区间的开闭性质:
对于左闭右开的区间,一开始设置start,end 指针的时候就需要这样设置:
int start=0;
int end=nums.size();
原因:由于左闭右开的性质,end的位置其实是取不到的,而对于vector容器来说,nums.size()也恰巧是无法取到的。
在完整写一下选择这种区间的写法的代码:
class Solution {
public:
int search(vector& nums, int target) {
int start=0;
int end=nums.size();
while(end>start)
{
int mid=start+(end-start)/2;
if(nums[mid]==target)
{
return mid;
}
else if(nums[mid]
可以注意到还有两个地方要小心:
一是while的条件是end>start而不是end>=start,(当区间左闭右开的时候显然右端点不能与左端点相等)。
二是当target落在左半边的时候,end=mid,原因是这种写法其实右边是开区间,不会取到mid处的位置。
而对于闭合区间,类比一下就知道应该这样设置start,end指针:
int start=0;
int end=nums.size()-1;
再完整写一下代码:
class Solution {
public:
int search(vector& nums, int target) {
int start=0;
int end=nums.size()-1;
while(end>=start)
{
int mid=start+(end-start)/2;
if(nums[mid]==target)
{
return mid;
}
else if(nums[mid]
注意点和上一种写法对比可知。
Tip:其实第一次写二分查找类的代码的时候,如果实在无法理解,可以试着找一个长度比较短的数组模拟一下代码的全过程,更加利于理解,这样记忆也会比较深刻。
再浅记一下二分查找应用的条件:
1.无重复元素(否则返回的元素下表可能不是唯一的)
2.对于本题,需要数组本身有序,其他题目在应用时也有可能出现类似各自的特殊要求
Leetcode 27 移除元素(链接)
通读题意可以发现,本题有两个核心要求:
一是统计出非目标元素的个数(其实和统计目标元素的个数是一个意思,做一下减法就可以)
二是将这些非目标元素放到数组的最前面
第一个要求相对简单,在遍历数组的同时计数就可以完成
对于第二个要求有几种思路:
第一种也是最暴力的方法就是平移
遇到目标元素,就将后面的部分整体向前平移一位(用for循环实现,附上代码:
class Solution {
public:
int removeElement(vector& nums, int val) {
int size=nums.size();
for(int i=0;i
这种方法的时间复杂度在最坏情况下是O(n^2),空间复杂度是O(1)
要注意在if内部需要加上i--,因为数组的后半部分整体向前移了一位。
当然,有经验的朋友应该可以注意到这种寻找目标元素删去的题目,最经典的方法显然是快慢指针,慢指针用于指向新数组的下一个位置,而快指针去寻找可替换的非目标元素。附上代码:
int removeElement(vector& nums, int val) {
int slow = 0;
for (int fast = 0; fast < nums.size(); fast++) {
if (nums[fast] != val) {
nums[slow++] = nums[fast];
}
}
return slow;
}
笔者则使用了一个代码较为繁琐的双指针方法,注意到题目中指明最终输出的元素不关注排序,因此完全可以一个指针从前往后搜索目标元素,而另一个指针从后向前寻找非目标元素并与前一个指针找到的目标元素进行替换,从而实现目标元素往数组后放的目的。附上代码:
class Solution {
public:
int removeElement(vector& nums, int val) {
int p1=0;
int p2=nums.size()-1;
while(p1<=p2)
{
if(nums[p1]!= val) p1++;
else {
if(nums[p2]!= val)
{
nums[p1]=nums[p2];
p2--;
}
else p2--;
}
}
return p1;
}
};
这里需要注意的点在于,最终p1就是非目标元素的个数(也就是最终的返回值),不需要再设置另外的变量进行计数。
Leetcode 977有序数组的平方(链接)
显然,第一步是将原数组的数据更新成平方后的数据
再将新数组进行排序
最简单粗暴的方式当然是使用vector头文件中已经封装的排序函数
sort(nums.begin(),nums.end());
但这样显然不是出题者的初衷,不放让我们思考一下如何排序更加的快捷
回忆一下十大排序算法,满足题目所要求的时间复杂度是O(n)的排序方法,貌似好像没有。
对于这样的题目,只可能是因为题目中的数据有特殊的性质,因此在排序时可以减少部分筛选,从而减少时间复杂度。
注意到题目中的初始数据是递增的,因此可以分成负数和非负数两部分,这样在平方后的两部分各自依然是递增的。
这样只需要对这两部分进行合并排序就可以了。
这里有一个小技巧(个人的感觉会少一点代码),如果按照常规的思路,新数组从小到大进行填充的时候,可能会出现一边到头的情况, 需要另外写代码分类,但是如果反向从大到小进行填充的时候就可以从两边向中间遍历,这样可以减少分类。附上代码:
class Solution {
public:
vector sortedSquares(vector& nums) {
int size=nums.size();
vector num(size,0);
for(int i=0;i0){
while(p1=nums[p2]){
num[i]=nums[p1];
p1++;
}
else{
num[i]=nums[p2];
p2--;
}
i--;
}
}
num[0]=nums[p1];
return num;
}
};
OK,题目和题解的部分到这里结束。
1.常见的一些思路还是不够熟悉,有些方法不知道具体的时间空间复杂度,有些方法只知道大致的思路,但是在具体实现的时候总是会出现各种问题(例如二分查找的区间端点处理)
2.对于题目中的隐藏条件不够敏感,不能因题制宜,寻找适用于具体题目的特殊解法