📚 博主的专栏
目录
·283. 移动零 - 力扣(LeetCode)--- 数组划分(数组分块)使用双指针算法
1089. 复写零 - 力扣(LeetCode)-- 使用双指针算法
LCR 179. 查找总价格为目标值的两个商品 - 力扣(LeetCode)
·283. 移动零 - 力扣(LeetCode)--- 数组划分(数组分块)使用双指针算法
题目解析:
特点:给一个数组,给定一个规则,需要将数组划分多个区间
算法原理:
规则:将非0元素移到左边,0移到右边 ---> 两个区间
在数组中,利用数组下标,充当指针。
这个算法实际上是快排算法中,最核心的一步,数据划分。
两个指针的作用(dest,cur):
cur:从左往右扫描数组,遍历数组
dest:已处理的区间,非0元素的最后一个位置,用作分割线(左边是非0,右边0)
三个区间:[0,dest] (非0),[dest + 1,cur - 1](0),[cur,n - 1](未处理)
示例 1:
输入: nums =
[0,1,0,3,12]
输出:[1,3,12,0,0]
初始状态:dest = -1,cur = 0
1.当cur 遇到 0 ,则++cur;
2.当cur 遇到非0,dest++;
3.swap(dest + 1,cur);
4.dest++ ,cur++;
交换一次后:
第二次:
第三次:
代码编写:
class Solution {
public:
void moveZeroes(vector<int>& nums) {
for (int cur = 0, dest = -1; cur < nums.size(); cur++) {
if (nums[cur])
swap(nums[++dest], nums[cur]);
}
}
};
1089. 复写零 - 力扣(LeetCode)-- 使用双指针算法
题目解析:
给一个数组,将数组中的0都多写一次,其余元素就不管,就直接写一遍
算法原理:
双指针算法,先根据“异地操作”,然后优化成双指针下的“就地"操作
异地操作
- 1.定义两个指针一个cur指向原始数组的第一个元素,一个dest指向新数组的第一个元素
- 2.cur非0,dest抄写,cur++,dest++
- 3.cur为0,dest复写,cur++,dest++,当dest到数组size的时候退出。
就地操作:
从左向右的方法不可取,会导致非0值被0覆盖。
删除数组中值为val的元素,这道题就能根据异地操作,直接在原数组修改成功
从右向左:让复写操作从左向右进行
1.cur先找到最后一个需要“抄写”的数,dest找到最后一个要被复写的"0"
2.从右向左完成复写操作
如何找到最后一个需要复写得数?--> 双指针
1.cur指向第一个元素,dest指向-1,先判断 cur 位置的值是否是需要复写的0
2.决定dest向后移动一步或者两步(cur 位置为 0 , dest+=2,否则 dest++)
3.判断dest是否已经到结束位置,到结束位置就退出(dest == n 就需要处理边界情况了)
4.dest没有到结束位置,cur++
这道题的特例:
移动时,dest在判断出当前位置为需要复写的0之后,向后移动两步的时候,直接越界了,cur的位置一定是正确的。
1.5. 因此还需要再dest要移动时,处理边界情况,单独处理这种情况
arr[ n - 1 ]= 0, cur--,dest -= 2;
代码编写:
class Solution { public: void duplicateZeros(vector<int>& arr) { // 1.先找到最后一个数 int cur = 0, dest = -1; int len = arr.size(); while(cur < len) { if(arr[cur]) dest++; else dest +=2; if(dest >= len-1) break; cur++; } //2.处理边界情况 if(dest == len) { arr[len - 1] = 0; cur--; dest-=2; } //3.从右向左进行复写操作 while(cur >= 0) { if(arr[cur]) arr[dest--] = arr[cur--]; else //当cur位置就是0的时候 { arr[dest--] = 0; arr[dest--] = 0; cur--; } } } };
202. 快乐数 - 力扣(LeetCode)
1.题目解析:
根据示例:
数在变化的时候,总会有环,在之前有讲解过环形链表:
链表oj--数据结构--c语言--环形链表(1)(2)(以及笔试题slow一次走1、n步,fast一次走2、3、m步一定会相遇吗讲解)-优快云博客
快乐数:环里的数都是1
非快乐数:环里的数无1
算法原理:快慢双指针
- 定义快慢指针,这里的意思是让快慢指针表示数
- 慢指针每次向后移动1步,快指针每次向后移动2步,只要两个指针进入环,他们一定在某个时间相遇。
- 判断相遇时候的值是否是1。
但是这道题,如果题目没有告诉会循环,我们还有可能会想到所有的数都是不重复的数,无限往下永远无法变成1。
证明,一定会有循环:鸽巢原理(抽屉原理)
有n个巢穴,n+1个鸽子,至少有一个巢穴里面的鸽子数量>1
题目给出的数字范围是
1 <= n <= 231 - 1,那么我们直接扩更大的范围,每一位都是9,那么表示的最大数就是810,那么就有1-810能不重复的值,假如现在有x值,在经历810次经历所有的不同的数之后,在第811次,这个数一定属于1-810,造成循环。
代码编写:
class Solution {
public:
int bitSum(int n)
{
//返回n上数每一位的平方和
int sum = 0;
while(n)
{
int m = n%10;//1
sum+=m*m;//81+1
n/=10;//1 0
}
return sum; //82
}
bool isHappy(int n) {
int slow = n, fast = bitSum(n);
while(slow != fast){
slow = bitSum(slow);
fast = bitSum(bitSum(fast));//fast走两步
}
return slow == 1;
}
};
11. 盛最多水的容器 - 力扣(LeetCode)
题目解析:木桶效应
解法一,暴力解法:两层for循环,直接找盛水最多的容器,但是会超时O(n^2)
解法二:
利用单调性,使用双指针来解决问题,遍历数组一遍就能解决问题O(n)
611. 有效三角形的个数 - 力扣(LeetCode)
题目解析:
省略
算法原理:
数学知识:给我们三个数,是否能构成三角形
获得三个数的大小顺序,仅需判断较小的两个数之和大于最大边
优化:先对整个数组排序
解法一:暴力枚举O(N^3)
伪代码:
for(i = 0; i< n;i++)
for(j = i + 1; j < n; j++)
for(k = j + 1; k < n; k++)
check(i, j, k);
解法二:利用单调性,使用双指针算法解决问题 O(N^2)
1.先排序然后,固定最大数(最右边) N次
2.在最大的数的左区间内,使用双指针算法,快速统计出符合要求的三元组的个数 N次
代码编写:
class Solution {
public:
int triangleNumber(vector<int>& nums) {
// 1.优化:
sort(nums.begin(), nums.end());
// 2.利用双指针解决问题
int len = nums.size();
// int len = nums.size() - 1;
int k = 0;
// int left = 0, right = len - 1;
for(int i = len - 1; i >= 2; --i)
{
int left = 0, right = i - 1;
while(left < right)
{
if(nums[left] + nums[right] > nums[i])
{
k += right - left;
--right;
}
else
{
++left;
}
}
}
// while (len) {
// while (left < right) {
// if (nums[left] + nums[right] > nums[len]) {
// k += right - left;
// --right;
// } else {
// ++left;
// }
// }
// --len;
// left = 0;
// right = len - 1;
// }
return k;
}
};
LCR 179. 查找总价格为目标值的两个商品 - 力扣(LeetCode)
1.题目解析:
一个升序排序的数组,找出其中任意两个数的和能等于target就返回
2.算法原理
双指针
1.right先从末尾开始找,找到第一个比target小的数,left指向首位元素
2.left + right如果等于target就直接返回这两个数,若小于,left++,若大于,right--。
3.代码编写
class Solution {
public:
vector<int> twoSum(vector<int>& price, int target) {
int right = price.size() - 1;
int left = 0;
// 1.找到刚好比target小的第一个数
while (right) {
if (price[right] < target)
break;
--right;
}
// 2.开始
vector<int> res;
while (left < right) {
if (price[left] + price[right] == target) {
res.push_back(price[left]);
res.push_back(price[right]);
break;
} else if (price[left] + price[right] < target) {
++left;
} else {
--right;
}
}
return res;
}
};
15. 三数之和 - 力扣(LeetCode)
题目解析:
三个下标不相同的数相加 = 0,返回这三个数的数组集,不能有相同的三个数
算法原理
解法一:排序 + 暴力枚举 + 去重 O(N^3)
如何更方便的去重?原本我们可能会得到多个数组中元素顺序不一样,但数值一样的多个数组,需要将他们排序再去重,这样麻烦并且消耗大,因此先排序,再进行寻找,所得到的都是数值顺序一样的数组。再使用set去重。
解法二:二分,双指针(优先选择)
处理细节问题
a.去重 b.不漏 c.避免越界
1.使用双指针,以及一个固定数i(<len - 2并且小于0),定数刚开始指向首元素,left为i+1,right为最后一个
2.left + right,若值>-i,right--;若值<-i,left++;若值=-i,则存入[i , left, right],再继续缩小区间(left++,right--)重复2步骤,直到left = right退出,找到所有情况
3.因为数组有序,在数组元素当中,相同的数都放在一起,当找到一种对应的数组后,利用双指针移动跳过重复元素来去重。
下一个i仍然是-4,因此直接跳过 :
越界情况
代码编写
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(), nums.end());
int len = nums.size();
vector<vector<int>> ret;
for (int i = 0; i < len - 2;) {
if (nums[i] > 0) {
break;
}
int left = i + 1;
int right = len - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum < 0) {
++left;
} else if (sum > 0) {
--right;
} else {
ret.push_back({nums[i], nums[left], nums[right]});
++left;
--right;
//去重l r
while (left < right && nums[left] == nums[left - 1]) left++;
while (left < right && nums[right] == nums[right + 1]) right--;
}
}
//去重i
i++;
while(i < len - 2 && nums[i] == nums[i - 1]) i++;
}
return ret;
}
};
这里最巧妙的就是去掉了for循环中的i++,就被坑在这里了
18. 四数之和 - 力扣(LeetCode)
题目解析
实际上题目就类似于三数之和
算法原理
解法一:排序 + 暴力枚举 + set去重 O(N^4)
解法二:排序 + 双指针
1.依次固定a;
2.在a后面的区间找到三个数,计算有这三数之和,等于target - a
3.在后面的区间中即为求三数之和等于target - a,这里直接利用上一题三数之和的思路就可以
4.这道题与上一题不一样的地方在于,有数据溢出的风险。因此使用long long(使用的时候不仅在定义的时候用long long,后面任意一个数需要强转为long long类型)
代码编写
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
//1.先排序
sort(nums.begin(), nums.end());
int n = nums.size();
vector<vector<int>> res;
//2.第一层for循环,固定的第一个数
for (int i = 0; i < n - 3;) {
//target - a
//3.第二层for循环,固定第二个数
for (int k = i + 1; k < n - 2;) {
long long sub = (long long)target - nums[i] - nums[k];//target - a - b
int left = k + 1;
int right = n - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum < sub) {
++left;
} else if (sum > sub) {
--right;
} else {
res.push_back(
{nums[i], nums[k], nums[left], nums[right]});
++left;
--right;
while (left < right && nums[left] == nums[left - 1])
++left;
while (left < right && nums[right] == nums[right + 1])
--right;
}
}
++k;
while (k < n - 2 && nums[k] == nums[k - 1])
++k;
}
++i;
while (i < n - 3 && nums[i] == nums[i - 1])
++i;
}
return res;
}
};
结语:
随着这篇关于题目解析的博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。
在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。
你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容,让我们在知识的道路上共同前行。