✨✨欢迎来到T_X_Parallel的博客!!
🛰️博客主页:T_X_Parallel
🛰️专栏 : C++算法题
🛰️欢迎关注:👍点赞🙌收藏✍️留言
第一题 盛最多水的容器
题目
给定一个长度为 n
的整数数组 height
。有 n
条垂线,第 i
条线的两个端点是 (i, 0)
和 (i, height[i])
找出其中的两条线,使得它们与 x
轴共同构成的容器可以容纳最多的水
返回容器可以储存的最大水量
说明:你不能倾斜容器。
示例 1:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例 2:
输入:height = [1,1]
输出:1
提示:
n == height.length
2 <= n <= 105
0 <= height[i] <= 104
题目解析
这题的要求就是找到一个容器能够储存最大的水量,这就是经典的木桶原理,在木桶的底部面积不变和木桶正放不倾斜的情况下,能够储存多少水取决于木桶上最短的那块木板
在这道题目中,木桶的木板只有左右两块,所以我们可以使用左右两个指针来指向左右两块木板,最开始指向数组的最两边的元素,再一步步移动指针,每次只移动一步,所以按道理来说,我们会找到每种底部长度的容器,而根据下面的盛水量计算公式,可以知道min(height[left], height[right])
可以作为左右指针的移动因素,为了让盛水量最大,我们应该让指向那块短木板的指针移动
盛水量
=
m
i
n
(
h
e
i
g
h
t
[
l
e
f
t
]
,
h
e
i
g
h
t
[
r
i
g
h
t
]
)
∗
(
r
i
g
h
t
−
l
e
f
t
)
盛水量 = min(height[left], height[right]) * (right - left)
盛水量=min(height[left],height[right])∗(right−left)
即分为以下两种情况
- 当
height[left] > height[right]
时 指针right
往左移- 当
height[left] >= height[right]
时 指针left
往右移
如对这个过程感到疑惑的话,可以参考leetcode官方题解——盛最多水的容器中的解析部分(解析部分有动图可以便于理解,不太理解的可以去看看官方题解的动图过程)和证明部分,证明部分我就不过多解释了
代码
int maxArea(vector<int>& height) {
int left = 0, right = height.size() - 1;
int ret = 0;
while (left < right)
{
ret = max(ret, min(height[left], height[right]) * (right - left));
height[left] > height[right] ? right-- : left++;
}
return ret;
}
时间复杂度:O(N)
空间复杂度:O(1)
第二题 全排列Ⅱ
题目
给定一个可包含重复数字的序列 nums
,按任意顺序 返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
示例 2:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
提示:
1 <= nums.length <= 8
-10 <= nums[i] <= 10
题目解析
此题为46.全排列的进阶题,这个题的序列中包含了重复数字,所以只需使用46.全排列的解题方式,再考虑去掉重复的排列情况即可完成该题(因为存在重复数字,那么必然使用相同的方法就会出现重复的排列情况)
两个题的核心思想都是搜索回溯思想
先讲没有重复数字的序列的全排列方式,这个全排列过程其实就是将输入数组中的数字依次填入进返回数组中,并且每个位置的数字的可能性就是输入数组中的每个数字,但是在填入下一个数字需检查哪些数字未被填入,所以我们需要使用一个数组来记录数字的填入情况。
整个过程使用递归实现(当然了,如果不嫌麻烦也可以自己设计成循环来解题)
定义递归函数
backtrack(idx,perm)
,idx
就是我们递归层数即要填第几个位置的数字,perm
就是当前排列数组,这个递归过程分为下面两种情况(n为输入数组大小)
- 当
perm == n
时,说明排列完成,把perm
放进答案数组中,直接返回- 当
perm < n
时,我们需要在idx
位置放入输入数组中没有被插入的每一个数字,所以使用循环,然后检查每个数字是否插入过,如果没插入,则放进该位置,并标记该数字,再继续填下一个位置的数字,调用递归函数backtrack(idx + 1,perm)
继续递归下一层。调用完后需要撤销该数字的标记,以便在这个位置填入下一种数字后,后面的位置可以填入该数字。然后继续尝试其他没被标记的数字,即继续循环
接下来就要解决输入数组中存在重复数字所产生的全排列存在重复排列。
第一个想到的肯定是使用unordered_set
容器来去重,这是一种方法,但是会增加空间复杂度。
第二种方法就是在每层递归中的循环填入数字的过程进行判断,为了不产生重复排列,我们可以让每一组重复的数字依次从左到右被填入perm
中,即每次填入的数是这个数所在的重复集合中从左往右第一个没被填入的数字
为了这个判断,我们就需要所有的重复数字放在一起,所以我们需要在一开始将输入数组进行排序
假设我们有一个数的重复集合有三个数,那么这三个重复数的标记情况为
[未填入,未填入,未填入 ]→[填入,未填入,未填入 ]→[填入,填入,未填入 ]→[填入,填入,填入 ]
具体的判断语句请看下面的代码部分
代码
vector<int> vis;
void backtrack(const vector<int>& nums, const int n, vector<int>& perm, vector<vector<int>>& ans)
{
if (n == nums.size())
ans.push_back(perm);
int sz = nums.size();
for (int i = 0; i < sz; i++)
{
if (vis[i] || (i > 0 && nums[i] == nums[i - 1] && !vis[i - 1]))
continue;
perm.push_back(nums[i]);
vis[i] = 1;
backtrack(nums, n + 1, perm, ans);
vis[i] = 0;
perm.pop_back();
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<vector<int>> ret;
vector<int> perm;
vis = vector<int>(nums.size(), 0);
sort(nums.begin(), nums.end());
backtrack(nums, 0, perm, ret);
return ret;
}
时间复杂度:O(NxN!)
空间复杂度:O(N)
注:时间复杂度具体分析过程请参考leetcode官方题解——全排列Ⅱ
专栏:C++算法题
都看到这里了,留下你们的珍贵的👍点赞+⭐收藏+📋评论吧