代码随想录day2-移除元素与有序数组的平方
1、LeetCode 27 移除元素
题目描述:
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
题目分析:
这个题目考察的是移除数组中的目标元素,我们一般的思路就是找到并将其删除即可,但是对于数组而言,没有删除这个操作,只能说遇到了目标值,就把它用后面的东西进行覆盖,这也就是暴力法的思路。
题目解答:
方法1:暴力法
用两层循环就可以实现,外层循环遍历数组,内层循环将开始出现目标值的时候的后面所有元素往前移动。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int len = nums.size(); // 数组的长度
for (int i = 0; i < len; i++) {
if (nums[i] == val) { // 一旦发现出现了目标值
for (int j = i + 1; j < len; j++) {
nums[j - 1] = nums[j]; // 将后面的位置逐个移动
}
i--; // 由于之后的都往前移动了一位,所以i也要减1
len--; // 数组的长度减小1
}
}
return len;
}
};
暴力算法的时间复杂度是O(n^2),其实,对于这种移除元素的题目,可以采用双指针(快慢指针)法。
方法二:快慢指针法
快慢指针法就是定义一个快指针和慢指针
其中快指针指向的是新数组(这个不是真正意义上开辟一个新数组,而是挑选非目标值出来构成一个新数组),慢指针指向的是新数组的位置,也就是满足条件的存放位置。具体的实现代码如下:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int fast = 0;
int slow = 0;
for (int i = 0; i < nums.size(); i++) {
if (nums[fast] != val) { // 如果快指针指向的元素不是目标元素,那么就将其保留下来,存到慢指针的位置去
nums[slow] = nums[fast];
slow++; // 慢指针更新位置,以便存放下一个数据
}
}
return slow; // 慢指针存放的就是元素的个数
}
};
使用双指针法的时间复杂度是O(n),提升了代码的效率。
双指针不仅有快慢指针,还有相向指针,从中间向两边走的指针等等。
2、LeetCode 26 删除有序数组中的重复项
记录于2023.2.21晚,明日继续记录day2的内容。
2023.2.23日继续记录22日的内容
题目分析:
本题也是一个删除重复元素的题目,删除重复的,只保留一个就可以了,因此也可以借鉴快慢指针(双指针)的思路。此外,本题是升序的,也就是说出现过的数字在以后不会再出现了,这也大大降低了题目难度。
题目解答:
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
// 使用快慢指针法
int fast = 1; // 由于第一个元素肯定不是重复的,所以快指针直接指向1
int slow = 0; // 慢指针还是指向0
while (fast < nums.size()) {
// 如果快指针搜索到的不是目标值
if (nums[fast] != nums[slow]) {
// 慢指针先更新,然后赋值
nums[++slow] = nums[fast];
}
fast++;
}
return slow + 1; // 注意返回的值
}
};
具体的注释已经写好了。
3、LeetCode 283 移动零
题目分析:
本题实际上可以看作删除0元素,然后再后面补充0即可,就与上面的题一样了。
题目解答:
class Solution {
public:
void moveZeroes(vector<int>& nums) {
// 可以使用双指针法,快慢指针法进行操作
int fast = 0; // 快指针
int slow = 0; // 慢指针
int cnt = 0; // 数组中0的个数
int len = nums.size();
while (fast < len) {
if (nums[fast] != 0) nums[slow++] = nums[fast];
else cnt++; // 统计出现的0的次数
fast++; // 快指针更新
}
// 将后面几个都填充为0即可
if (cnt != 0) {
for (int i = nums.size() - 1; i > nums.size() - 1 - cnt; i--) {
nums[i] = 0;
}
}
}
};
4、LeetCode 844 比较含退格的字符串
本题还未有比较清晰的思路,等做好了再来更新,之前想错了。(2023.2.22)
5、LeetCode 977 有序数组的平方
题目分析:
很明显,这个题目最简单的方法是遍历,各自平方,然后排序。不过这样做的话时间复杂度比较高,效率比较低。本质上这个题也是遍历了数组中的所有元素,其实这种类型的题目都是可以使用双指针的。本题要求从大到小排列,由于左右两边的比较大,所以我们可以用双指针从两边相向而行,加快遍历速度。
题目解答:
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> ans(nums.size(), 0); // 初始化一个数组
int left = 0; // 左指针
int len = nums.size(); // 数组的长度
int right = len - 1; // 右指针
int index = len - 1; // 新数组的索引
while (left <= right) {
if (nums[right] * nums[right] > nums[left] * nums[left]) {
// 右侧的大一些
ans[index--] = nums[right] * nums[right];
right--;
}
// 否则更新左指针
else {
ans[index--] = nums[left] * nums[left];
left++;
}
}
return ans;
}
};
今天遇到的题目都是使用双指针来做的。
总结:当遇到数组需要遍历的时候,为了提高遍历的效率,可以使用双指针。
双指针包括:快慢指针,相向指针,背向指针等等,在以后各个章节都用得着!