第一章 数组
1、二分查找
题目简介:
给定一个 n
个元素有序的(升序)整型数组 nums
和一个目标值 target
,写一个函数搜索 nums
中的 target
,如果目标值存在返回下标,否则返回 -1
。
初见思路:
这个我会嗷,之前学算法的时候就已经过了一遍了。但是实现起来还是有一些不熟练的地方。
class Solution {
public:
int search(vector<int>& nums, int target) {
int n = nums.size();
int i = 0;
int j = n-1;
while(i<=j){
int m = (j + i)/2;
if(nums[m] > target){
j = m -1;
}
else if(nums[m] < target){
i = m + 1 ;
}
else{
return m;
}
}
return -1;
}
};
实现的时候,我对几个地方还是存在犹豫不清的地方:
1、这个while
循环的结束条件到底是什么?
我第一遍写的时候我写成了while(i<j)
的形式,这样子的话如果nums
中只有一个数值的时候就会导致无法进入循环而得到错误的结论。
2、i
和j
这两个变量到底如何变化?
我学的有点混乱,因为有好几种,什么左开右闭,左闭右开的形式,好像就是和这个i
和j
的变化有关系。这个可能得等后面我学成归来再来解决了。
3、m的计算方式?
我这里翻车了,我一开始写了一个m = (i - j)/2
,我把它当作距离长度的一半了,随然二分查找的意思确实是,两个头中间的位置,但是这和索引的计算还是不同的,哎很拉跨的错误。
算法思路:
好好好,代码随想录中完美的解决了我的难题。
1、while
循环的结束条件到底是什么?
这个其实就是和我之前提到的左闭右开和左闭右闭有关系了,如果是左闭右闭的情况,也就是这道题目的情况,那么[ left , right ]
这个区间里,left = right
这个是有意义的,所以再while
循环中,是应该添加一个等于符号的。而如果是左闭右开的情况,也就是[ left , right )
那么其实在每次进行寻找的时候,右边界其实是取不到的,所以while
循环里是不会有一个这个等于号的。
2、i
和j
这两个变量到底如何变化?
这个也是和边界条件息息相关。如果是左闭右闭,那么就是左边界的数和有边界的数都是完全可以取到的,所以判断的时候已经判断过左、右两个边界是不是我们找的目标了,那么我们就应该分别+1
和-1
得到新的区间;而如果是左闭右开的情况,那么右边的边界其实是找不到。那找不到的话,那么right -1
其实就我们下次要找的数中的一个,而不能让他变成我们不可达的右边界。所以就是j=m
。
2、删除元素
题目简介:
给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素。元素的顺序可能发生改变。然后返回 nums
中与 val
不同的元素的数量。
假设 nums
中不等于 val
的元素数量为 k
,要通过此题,您需要执行以下操作:
- 更改
nums
数组,使nums
的前k
个元素包含不等于val
的元素。nums
的其余元素和nums
的大小并不重要。 - 返回
k
。
初见思路:
我一开始看见这个问题,观察到他说不用在乎超过新数组长度的哪些原来的数量,想到的就是把前面的val
全搬到后面去,然后返回前面的数组长度就好了,用了一个标准库的swap
函数完成的。
int removeElement(vector<int> &nums, int val)
{
int n = nums.size();
int j = n - 1;
int i = 0;
if (n == 1)
{
if (nums[0] == val)
{
return 0;
}
else
{
return 1;
}
}
int count = 0;
while (i <= j)
{
if (nums[i] == val)
{
while (nums[j] == val && j > i)
{
j--;
count++;
}
swap(nums[i], nums[j]);
count++;
j--;
}
i++;
}
return n - count;
}
速度也很快,但是就是很繁琐,有一些特殊情况要处理。
算法思路:
还是妙啊,快慢指针法。但是我还是没有完全的理解这个概念的妙处到底是什么
用两个Index
来管理数组,快指针就是一路遍历过去,然后慢指针会在快指针检测到val
的时候,就会让慢指针停止一个次增长。这样子其实快指针和慢指针之间就会逐渐产生插值,这个插值就是检测到的val
的个数。
其实如果只是为了检测到val
的个数,也没必要叫做双指针,不过就是维护了两个变量,让两个变量之间的差值,代表了目标元素的个数。随意遍历一遍都能获得这个值。
真正的核心精髓,我觉得应该是赋值阶段,他让快指针检测到的非val
值赋值给了慢指针的索引处,要知道这个时候,慢指针正在val
处,所以这个赋值其实就是将后面的非val
值移到了前面来,然后覆盖掉了val
。这就让慢指针的值有了新的含义,就是不含有val
的数组的长度。
所以后面才会返回慢指针的值就能代表答案。
int removeElement(vector<int> &nums, int val)
{
int n = nums.size();
int slowIndex=0;
for (int fastIndex = 0; fastIndex < n;fastIndex++){
if(nums[fastIndex] != val){
nums[slowIndex] = nums[fastIndex];
slowIndex++;
}
}
return slowIndex;
}
我现在有一个更好的形容方法,两个指针一起前进,如果发现了目标就让一个指针先停在这里,等待另一个指针发现可以替换的目标,如果发现了马上进行交换。
慢指针的行动逻辑是,在交换完之后,向前移动一格;
快指针的行动逻辑是,每轮都前进一格。没发现目标,就和慢指针交换一次;如果发现了目标就不进行交换。
int myPointFstandSlow(vector<int> &nums, int val)
{
int n = nums.size();
int fast = 0;
int slow = 0;
for (; fast < n;fast++){
if(nums[fast] != val){
nums[slow] = nums[fast];
slow++;
}
else{
}
}
return slow;
}
和答案比其实就是差不多的了。
3、有序平方数组
题目简介:
给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序
初见思路:
忘记我的初见思路是啥了。
算法思路:
还是双指针法。但是思想还是很妙的,由于这是一个递增的数组,或者说是非减的数组。
如果不考虑负数,那么肯定是越到后面的数,平方起来越大,那么直接平方然后copy到新数组就好了;
但如果考虑负数,那么就必须要想到,负数越小他的平方就越大, 那么平方后最大的数只可能在数组的两边出现,最小的数会出现在中间;
所以,我们只需要每次都考虑两头谁大,然后填在新数组的最后就好了。
这里还有一点小巧思,那就是提前预设vector的大小,很显然,我们每次算出来的数是最大的数,但是我们却要把他填充在新数组的最前后头,这可咋办呢?vector只能往数组的后面插入呀,那岂不是成了递减数组了。
但如果我提前设好了vector的大小,我就可以利用下标访问到对应位置的元素了,直接赋值就好了。
vector<int> sortedSquares(vector<int>& nums) {
int n = nums.size();
vector<int> ans(n);
for(int i = 0, j = n-1,pos = n-1; i<=j;){
if(nums[i]* nums[i] < nums[j] * nums[j]){
ans[pos] = nums[j] * nums[j];
j--;
}else{
ans[pos] = nums[i] * nums[i];
i++;
}
pos--;
}
return ans;
}
打卡完成!