目录
[和为 s 的两个数](#和为 s 的两个数)
移动零
https://leetcode.cn/problems/move-zeroes/
给定一个数组,编写一个函数将所有 0 移动到数组的末尾,保持非零元素的相对顺序,必须在原数组操作
**数组划分、数组分块:**在某种规则、标准下,把一个给定的数组,划分成若干区间
解决这类题首先想到 双指针算法(利用数组下标充当指针)也可以解决快排中最核心的一步,但当相同元素很多时,逼近 N^2
2 个指针的作用:
cur:从左往右扫描数组,遍历数组
dest:已处理的区间内,非零元素的最后一个位置,所以刚开始定义为 -1

cur 从前往后遍例的过程中
-
遇到 0 元素:cur++;
-
遇到非 0 元素:swap(dest + 1, cur); dest++; cur++;
cpp
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int dest = -1, cur = 0;
while (cur < nums.size())
{
if (nums[cur] != 0)
swap(nums[++dest], nums[cur++]);
else
cur++;
}
}
};
复写零
https://leetcode.cn/problems/duplicate-zeros/
输入 arr = [1,0,2,3,0,4,5,0] 输出 [1,0,0,2,3,0,0,4] 必须在原数组操作
操作数组中元素, 先想到 双指针算法
先根据"异地"操作,优化成双指针下的"就地"操作
++异地++ 在本题指++新开的数组++ 上操作:从前向后,cur 遇到 0,dest 写 2 遍;cur 遇到非 0,dest 写 1 遍
但就地在原数组操作时,会覆盖后面 cur 没有遍历的元素,因此要从前向后不行
正确操作:cur 指向最后复写的数,dest 指向最后位置,从后往前
dest:结果中最后需要复写的位置,刚开始并不知道在哪,所以定义为 -1
找最后复写的数
-
看 cur 的值,决定 dest 走 1or2 步
-
判断 dest 是否已经到结束
-
cur++;

后面那种情况会报错,特殊处理边界情况就行:arr[n - 1] = 0; cur--; dest -= 2;
之后正常走,不影响
cpp
class Solution {
public:
void duplicateZeros(vector<int>& arr) {
// 找最后复写的数
int cur = 0, dest = -1, n = arr.size();
while (cur < n)
{
if (arr[cur] != 0)
dest++;
else
dest += 2;
if (dest >= n - 1)
break;
cur++;
}
// 处理边界情况
if (dest == n)
{
arr[n - 1] = arr[cur];
cur--;
dest -= 2;
}
// 复写
while (cur >= 0)
{
if (arr[cur] != 0)
arr[dest--] = arr[cur--];
else
{
arr[dest--] = 0;
arr[dest--] = 0;
--cur;
}
}
}
};
快乐数
https://leetcode.cn/problems/happy-number/
判断一个数是不是快乐数
快乐数判定:
-
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和
-
然后重复这个过程直到这个数变为 1,也可能是 ++无限循环++ 但始终变不到 1
-
如果这个过程 结果为 1,那么这个数就是快乐数

两个情况都可以转化为链表相交问题:快慢双指针
判断相遇时的值即可,1 则是快乐数,非 1 则不是快乐数
这里是用数充当指针

cpp
class Solution {
public:
int Sum(int n) // 返回这个数每一位的平方和
{
int ret = 0;
while (n)
{
int a = n % 10;
ret += a * a;
n /= 10;
}
return ret;
}
bool isHappy(int n) {
int slow = n, fast = Sum(n);
while (slow != fast)
{
slow = Sum(slow);
fast = Sum(Sum(fast));
}
return slow == 1;
}
};
盛水最多的容器
https://leetcode.cn/problems/container-with-most-water/
[1,8,6,2,5,4,8,3,7]
选 2 根线,返回最大水量
解法一:暴力枚举 O(N^2)
固定左边的线,依次取右边的线(2 层 for 循环)
解法二:利用单调性,使用双指针 O(N)
随便拿两个数研究区间,比如 [ 6,2,5,4 ]
先拿 6 和 4 计算,V = h * w
再++向内枚举++ ,让 4 分别和 2 5 计算,++宽度 w 永远减小;高度 h 要么减小,要么不变++ :V 永远减小
**找到规律:**left、right 算出 V 后,拿比较小的数向内枚举,V 永远减小,所以这个小的数不再考虑
**利用规律:**拿最左、最右的数算 V;干掉低的那一个再算 V,找 Vmax
cpp
class Solution {
public:
int maxArea(vector<int>& height) {
int left = 0, right = height.size() - 1, Vmax = 0;
while (left < right)
{
int V = (right - left) * min(height[left], height[right]);
Vmax = max(Vmax, V);
if (height[left] > height[right]) right--;
else left++;
}
return Vmax;
}
};
有效的三角形个数
https://leetcode.cn/problems/valid-triangle-number/
任取数组中 3 个数,判断能否构成三角形:任意两边之和大于第三边
解法一:暴力枚举 O(N^3)
cpp
for (i = 0;)
for (j = i + 1;)
for (k = j + 1;)
check(i, j, k);
优化:a<=b<=c 且 a+b>c 则能构成;否则不能构成。所以要给数组排序
解法二:利用单调性,双指针 O(N^2)
1. 先固定最大的数 N
2. 再在最大数的左区间内,用双指针快速统计个数N

a + b > c: 则这 3 数能构成 △;a 右边的数 > a,则 a 右边的数与 bc 都能构成 △,个数:ri - le
此时,right 左边的数都和 right、c 构成 △,并已统计个数,right 失去价值,right--

a + b <= c: 则这 3 数不能构成 △;b 左边的数 < b,则 b 左边的数与 ac 都不能构成 △
此时,left 右边的数都不能和 left、c 构成 △,left 失去价值,left++
cpp
class Solution {
public:
int triangleNumber(vector<int>& nums) {
int ret = 0;
sort(nums.begin(), nums.end());
for (int i = nums.size() - 1; i >= 2; i--) // 先固定最大的数
{
// 再在最大数的左区间内,用双指针快速统计个数
int left = 0, right = i - 1;
while (left < right)
{
if (nums[left] + nums[right] > nums[i])
{
ret += right - left;
right--;
}
else
{
left++;
}
}
}
return ret;
}
};
和为 s 的两个数
https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/description/
输入一个递增数组和 s,查找两个数,使和正好是 s。如果有多对数字和是 s,则输出任意一对即可
解法一:暴力枚举,没有利用有序的特性
cpp
for (i = 0;)
for (j = i + 1;)
解法二:利用单调性,双指针
第一个数先和最后的数相加

sum > t: right 不动,left 后面的数是递增的,与 right 的和一定 > t,所以 right 没用了,right--

sum < t: left 不动,right 前面的数是递减的,与 left 的和一定 < t,所以 left 没用了,left++
**sum == t:**返回
cpp
class Solution {
public:
vector<int> twoSum(vector<int>& price, int target) {
int left = 0, right = price.size() - 1;
while (left < right)
{
int sum = price[left] + price[right];
if (sum > target) right--;
else if (sum < target) left++;
else break;
}
vector<int> ret;
ret.push_back(price[left]);
ret.push_back(price[right]);
return ret;
}
};
简化:
cpp
class Solution {
public:
vector<int> twoSum(vector<int>& price, int target) {
int left = 0, right = price.size() - 1;
while (left < right)
{
int sum = price[left] + price[right];
if (sum > target) right--;
else if (sum < target) left++;
else return { price[left], price[right] };
}
return { -1, -1 };
}
};
三数之和
https://leetcode.cn/problems/3sum/
判断数组中是否存在 下标互不相同,且和为零的 3 个数;返回所有++互不相同(忽略顺序)++的三元组
第一个和第三个算相同的,要去重
为方便去重,防止出现上面的情况,要先排序
解法一:排序 + 暴力枚举 + 利用 set 去重 O(N^3)
解法二:排序 + 双指针 (这里我们不用 set 去重,太 low 了)
1. 排序
2. 固定一个 <=0 的数 a(正数后面一定是正数,和不可能为 0)
3. 在 a 后面的区间内,用双指针找和为 -a 的两个数

处理细节问题:
1. 不漏: 找到一组后不能停,要缩小区间继续找
2. 去重(注意细节,避免越界!!!)
找到一组后,left 和 right 要跳过重复元素
使用完一组双指针算法后,i 也要跳过重复元素
cpp
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<vector<int>> vv;
int n = nums.size();
for (int i = 0; i < n; )
{
if (nums[i] > 0) break;
int left = i + 1, right = n - 1, t = -nums[i];
while (left < right)
{
int sum = nums[left] + nums[right];
if (sum < t) left++;
else if (sum > t) right--;
else
{
vv.push_back({ nums[left], nums[right], nums[i] });
// 不漏
left++, right--;
// 去重 left right
while (left < right && nums[left - 1] == nums[left]) left++;
while (left < right && nums[right + 1] == nums[right]) right--;
}
}
// 去重 i
i++;
while (i < n && nums[i - 1] == nums[i]) i++;
}
return vv;
}
};
四数之和
https://leetcode.cn/problems/4sum/description/
给数组,找出下标互不相同,且和为 target 的 4 个数;返回所有++互不相同(忽略顺序)++的四元组
解法二:排序 + 双指针
1. 排序
2. 固定一个数 a
3. 在 a 后面的区间内,用"三数之和"找和为 target - a 的三个数
1. 固定一个数 b
2. 在 a 后面的区间内,用双指针找和为 target - a - b 的两个数

处理细节问题:
1. 不漏: 找到一组后不能停,要缩小区间继续找
2. 去重(注意细节,避免越界!!!)
cpp
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> vv;
sort(nums.begin(), nums.end());
int n = nums.size();
for (int j = 0; j < n; ) // 固定数 a
{
for (int i = j + 1; i < n; ) // 固定数 b
{
int left = i + 1, right = n - 1;
long long t = (long long)target - nums[i] - nums[j];
while (left < right)
{
int sum = nums[left] + nums[right];
if (sum > t) right--;
else if (sum < t) left++;
else
{
vv.push_back({ nums[left], nums[right], nums[i], nums[j]});
// 不漏
left++, right--;
// 去重 left right
while (left < right && nums[left - 1] == nums[left]) left++;
while (left < right && nums[right + 1] == nums[left]) right--;
}
}
// 去重 b
i++;
while (i < n && nums[i - 1] == nums[i]) i++;
}
// 去重 a
j++;
while (j < n && nums[j - 1] == nums[j]) j++;
}
return vv;
}
};
本篇的分享就到这里了,感谢观看 ,如果对你有帮助,别忘了点赞+收藏+关注 。
小编会以自己学习过程中遇到的问题为素材,持续为您推送文章