先附上地址方便对照着看:代码随想录
一、数组
1、数组元素不能删,只能覆盖,数据结构有提到存储空间连续问题。
2、二分查找:这一块其实可以看题目要求,如果题目仅仅要求找到该元素而不返回下标之类的话,可以使用bsearch,如果不是有序数组需要用qsort。关于这两个函数的具体参数可以在dev c++中ctrl+鼠标左键点击头文件进入,然后ctrl+F键搜索查看具体参数。
但是如果需要返回数组下标之类的就比较麻烦一些,首先需要看数组是否是有序数组,而且要注意是否存在重复元素,不然返回的下标不唯一,但通常这种题最多让你计数。
手撕代码如下:个人喜欢闭区间,写起来对称
int left = 0;
int right = numsSize-1; //这个numsSize是数组大小,是sizeof(nums)/sizeof(nums[0])
int middle = 0;
while(left <= right){
middle = (left + right)/2;
if(nums[middle] > target)
right = middle-1;
else if(nums[middle] < target)
left = middle+1;
else if(nums[middle] == target)
return middle;
}
3、移除元素:暴力解法基本都能想到,快慢指针法确实想不到,还是思路没打开
快慢指针重点在于如何出现快慢并满足题目要求,分步骤分析:
step 1:设置双指针,一起向数组尾部移动
step 2:找到需要删除的元素,快指针后移,慢指针不动
step 3:快指针指向的元素覆盖慢指针元素,直至到达尾部
作者在这里用了很巧妙的思路:
1、快指针一直覆盖慢指针的元素:没有找到待删除元素时,两指针指向元素相同,覆盖也不会有影响。
2、当找到该元素时,慢指针停住如何实现?观察可得:快指针不受影响一直移动,而慢指针只需要在待删除元素那里停住,用if语句即可。
于是核心就出来了:注意if判断就好
for(int fastIndex=0;fastIndex<numsSize;fastIndex++){
if(nums[fastIndex]!= val) //写slowIndex不行,因为slowIndex会一直停住,只有fastIndex才行
nums[slowIndex++] = nums[fastIndex];
}
4、有序数组的平方:确实想到的是暴力解法,没好好看题
题目中有个很重要的条件:非递减顺序,这个就隐含了重要性质:数组两端的元素平方之后的值是最大值或次大值,也就是说只有数组两端的元素才有资格争夺最大值。
那么可以想到另一种双指针法:两端指针,一个指针在头,从前往后;另一个在尾,从后往前。
核心思路:把两个指针所指元素的平方拿出来比较,大的存进新数组然后移动指针,直至两个指针相同,存入两个指针所指的同一元素即可。
while(i!=j){ //i在头,j在尾
int a = nums[i]*nums[i];
int b = nums[j]*nums[j];
if(a>=b){
res[cnt--] = a;
i++;
}
else{
res[cnt--] = b;
j--;
}
}
res[cnt] = nums[i]*nums[i];
5、长度最小的子数组:经过前两题之后能想到双指针了,但不知道怎么判定指针移动
还是双指针法:滑动窗口,通常用于寻找连续子数组,分步骤分析
step 1:定义双指针都在数组头部,其中一个指针 j 一直向后走
step 2: j 每走一次都要加上当前 j 的下标所指元素的值,并将总和与题目给定值比较
step 3:如果小了,那么没事,j 继续走;如果大了,必须调整另一个指针 i 了。
注意,这里的 i 不能简单用 i++ 调整,否则找出来的可能不是最小子数组!(虽然我当时是这么想的)
具体如何调整呢,上代码,感觉一下思路就清晰了。
for (int j = 0; j < nums.size(); j++) {
sum += nums[j];
// 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
while (sum >= s) {
subLength = (j - i + 1); // 取子序列的长度
result = result < subLength ? result : subLength;
sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
}
}
6、螺旋矩阵:没啥好说的,单纯我自己菜,CPU干烧了也不知道怎么下手
但其实对于这类题目吧,如果考试遇到了,我更愿意去背模板,现场模拟效率低还容易错
//设置每次循环的起始位置
int startX = 0;
int startY = 0;
//设置二维数组的中间值,若n为奇数。需要最后在中间填入数字
int mid = n / 2;
//循环圈数
int loop = n / 2;
//偏移数
int offset = 1;
//当前要添加的元素
int count = 1;
循环圈数我能想到,但偏移数还真想不到,n为奇数的话矩阵还要填数字也想不到,orz
while(loop) {
int i = startX;
int j = startY;
//模拟上侧从左到右
for(; j < startY + n - offset; j++) {
ans[i][j] = count++;
}
//模拟右侧从上到下
for(; i < startX + n - offset; i++) {
ans[i][j] = count++;
}
//模拟下侧从右到左
for(; j > startY; j--) {
ans[i][j] = count++;
}
//模拟左侧从下到上
for(; i > startX; i--) {
ans[i][j] = count++;
}
//偏移值每次加2
offset+=2;
//遍历起始位置每次+1
startX++;
startY++;
loop--;
}
//若n为奇数需要单独给矩阵中间赋值
if(n%2)
ans[mid][mid] = count;
看起来很复杂但其实很好背,四次for循环里面的都是一样的赋值流程,要记住的只有三个点:
1、前两次的for循环内需要加n并减去偏移值
为啥只有前两次循环要这么操作,因为只有前两次循环需要找到右边界和下边界
2、偏移值每次加2,起始位置在对角线上,所以x,y都要加1,而loop控制圈数,正常减1就行。
至于偏移值为什么加2,因为你循环一圈最左边,最右边一圈元素遍历过了,最上面,最下面一圈元素遍历过了,而且startX加进来也意味着左边已经遍历过的也进来了,在找边界的时候不得减掉两边。(n-offset)
3、n为奇数还要给中间元素赋值
然后就可以背下来了
7、总结一下,双指针yyds,但是如果暴力能过,其实就不用把自己绕晕了,但并不是说双指针没用,而是面对机试的时候,暴力是很好的节约时间的办法,这也是在规定时间内没办法的事,谁管你代码好不好看,能AC你就牛逼。
然后在写代码的时候,把一些步骤或者思路先用注释写在开头是非常好的整理思路的方法,避免你写到一半忘了题目条件或者算法思路之类的。
目前一刷就刷刷对应推荐题了,二刷的时候再刷类似题