目录
移动零
复写零
快乐数
盛最多水的容器
有效三角形的个数
查找总价格为目标值的两个商品
三数之和
四数之和
移动零
下图以样例1为例,看下图如何做到保证非零元素相对顺序前提下,移动零元素。
代码实现如下:
class Solution {
public:
void moveZeroes(vector<int>& nums) {
for (int cur = 0, dest = -1; cur < nums.size(); cur++)
if (nums[cur])//非0就交换
swap(nums[++dest], nums[cur]);
}
};
复写零
注意边界情况:例如 [ 1 0 2 3 0 4 ] dest会越界访问,此时需要把cur和dest回归到上一步位置。
代码实现如下:
class Solution {
public:
void duplicateZeros(vector<int>& arr) {
// 1.找到最后一个复写的数
int cur = 0, dest = -1, n = arr.size();
while (dest < n)
{
if (arr[cur])
dest++;
else
dest += 2;
if (dest >= n - 1)
break;
cur++;
}
// 2.处理边界情况
if (dest == n)
{
cur--; dest -= 2;
arr[n - 1] = 0;
}
// 3.从后往前遍历完成复写
while (cur >= 0)
{
if (arr[cur])
arr[dest--] = arr[cur--];
else
{
arr[dest--] = 0;
arr[dest--] = 0;
cur--;
}
}
}
};
快乐数
先看示例2为什么会输出false:
此题目一共三种情况:1、进入全是1的环 2、进入全部不是1的环 3、无限延伸下去不带环
但是题目告诉我们只有两种情况,为什么不会无限延伸下去,可以用鸽巢原理解释:
鸽巢原理: 把(n+1)个物体任意放进n个鸽巢中(n是非0自然数),一定有一个鸽巢中至少放进了2个物体。
题目给出范围n最大为2 ^ 31 - 1,即 2.1 * 10 ^ 9,假设是9999999999,那么经过一次平方和后变成810,也就是说n最大值经过一次平方和后最大不超过810,最小不低于1。
那么把810看作巢穴,把一次平方和规则看作鸽,经过811次平方和后,会导致810个巢穴不够分,至少有一个巢穴是两个鸽:意味着经过810次以上的平方和规则后,势必出现重复,重复后就进环。
代码入下:
class Solution {
public:
// 计算每个位置上的数字的平方和
int summary(int n)
{
int sum = 0;
while (n)
{
int tmp = n % 10;
sum += tmp * tmp;
n /= 10;
}
return sum;
}
bool isHappy(int n) {
int slow = n, fast = summary(n);
// 快指针走两步 慢指针走一步
while (slow != fast)
{
slow = summary(slow);
fast = summary(summary(fast));
}
return slow == 1;
}
};
盛最多水的容器
设置两个左右指针,由于下标相减所得宽度是不断减小的,那么在有限长度内,保证宽度最大的前提下得到最大的长度
宽度变小,长度可能不变,也可能变小,根据上图得出以下代码:
class Solution {
public:
int maxArea(vector<int>& height) {
int left = 0, right = height.size() - 1, area = 0;
while (left < right)
{
int lenth = right - left;
int width = min(height[left], height[right]);
area = max(area, width * lenth);
if (height[left] > height[right])
right--;
else
left++;
}
return area;
}
};
有效三角形的个数
首先先对数组进行排序,例如 a , b , c 现在是升序状态,两个比c小的数相加大于c,那么c 加两个小数中的任何一个小数都比另外一个小数大。此外暴力枚举的时间复杂度过大,本题不适合。
首先固定最大的数,以一个循环为例,先固定最大数10,左指针指向下标为0的元素(2),右指针指向下标为5的元素(9),a就是2,b就是9,c就是10,a + b > c,那么a——b这个区间里所有数加b都大于c,个数即为right - left。一轮过后,right--找到次小的数5,再使left从下标0开始,a + b小于c,那么让left++,使得left变大,如果left == right时还小于最大数10,说明剩下的数无法组合成三角形,接着固定9作为最大数c,循环上述步骤。
代码如下:
class Solution {
public:
int triangleNumber(vector<int>& nums) {
// 1.优化排序
sort(nums.begin(), nums.end());
// 2.利用双指针解决问题
int count = 0;
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])
{
count += (right - left);
right--;
}
else
left++;
}
}
return count;
}
};
查找总价格为目标值的两个商品
首先最重要一点:题目告诉我们此数组为升序,那么可以利用单调性来解决问题。
左指针右移相当于做加法操作,因为指向了一个更大的数,同理右指针左移相当于做减法操作,因为指向了一个更小的数。代码实现如下:
class Solution {
public:
vector<int> twoSum(vector<int>& price, int target) {
vector<int> ret;
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
{
ret.push_back(price[left]);
ret.push_back(price[right]);
break;
}
}
return ret;
}
};
三数之和
此题最关键的为去重问题和避免越界问题,那么如何避免这两个问题发生呢?
首先要保证left指针始终在right左边,然后left++或者right--之后才可以和left前一个数或者right后一个数比较,并且 i 始终小于该数组元素个数。
代码实现如下:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ret;
// 1.排序
sort(nums.begin(), nums.end());
// 2.利用双指针解决问题
for (int i = 0; i < nums.size() - 2;)
{
// 大于0意味着后面全是正数 不可能相加为0
if (nums[i] > 0)
break;
int left = i + 1, right = nums.size() - 1, target = -nums[i];
while (left < right)
{
vector<int> ans;
int sum = nums[left] + nums[right];
if (sum > target)
{
right--;
}
else if (sum < target)
{
left++;
}
else
{
ans.push_back(nums[i]);
ans.push_back(nums[left]);
ans.push_back(nums[right]);
ret.push_back(ans);
left++; right--;
// 去重 left 和 right
while (left < right && nums[left] == nums[left - 1])
left++;
while (left < right && nums[right] == nums[right + 1])
right--;
}
}
// 去重 i
i++;
while (i < nums.size() && nums[i] == nums[i - 1])
i++;
}
return ret;
}
};
四数之和
此题唯一有问题的地方在于数据的大小可能会超过int类型的最大值,那么我们需要将他强制类型转换成long long类型的数据,这样便于后面的比较。其余部分和三数之和并无多大区别。
代码如下:
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
// 1.排序
sort(nums.begin(), nums.end());
// 2.利用双指针解决问题
vector<vector<int>> ret;
// 解决特殊情况
// 1.1 特殊情况 元素个数小于4
if (nums.size() < 4)
return ret;
for (int a = 0; a < nums.size() - 3;) // 固定数 a
{
for (int b = a + 1; b < nums.size() - 2;) // 固定数 b
{
// 1.2 特殊情况 数据溢出
long long my_target = (long long)target - nums[a] - nums[b];
int left = b + 1, right = nums.size() - 1;
while (left < right)
{
int sum = nums[left] + nums[right];
if (sum > my_target)
right--;
else if (sum < my_target)
left++;
else
{
ret.push_back({ nums[a],nums[b],nums[left],nums[right] });
left++; right--;
// 去重 left 和 right
while (left < right && nums[left] == nums[left - 1])
left++;
while (left < right && nums[right] == nums[right + 1])
right--;
}
}
// 去重 b
b++;
while (b < nums.size() && nums[b] == nums[b - 1])
b++;
}
// 去重 a
a++;
while (a < nums.size() && nums[a] == nums[a - 1])
a++;
}
return ret;
}
};