1. 移动零(t283)
简单难度,题目示例如下:
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:
输入: nums = [0]
输出: [0]
暴力解法:从头开始遍历,如果遇到非零元素,将其交换到末尾。
class Solution {
public:
void moveZeroes(vector<int>& nums) {
for (int i = 0; i < nums.size(); i++){
if (nums[i] == 0){
for (int j = i + 1; j < nums.size(); j++){
if (nums[j] != 0){
swap(nums[i], nums[j]);
break;
}
}
}
}
}
};
时间复杂度: O(); 空间复杂度:O(1)
更好的解法:使用双指针的思路,左指针指向当前已经处理好的序列的尾部,右指针指向待处理序列的头部。右指针不断向右移动,每次右指针指向非零数,则将左右指针对应的数交换,同时左指针右移。
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int n = nums.size(), left = 0, right = 0;
while (right < n){
if (nums[right]){
swap(nums[left], nums[right]);
left++;
}
right++;
}
}
};
时间复杂度: O(n); 空间复杂度:O(1)
2. 盛最多水的容器(t11)
中等难度,题目示例如下:
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
解法思路:这套题的基础思路很简单,用双指针一头一尾。但是指针的移动条件,比较巧妙,通过比较左右两块板的高度,如果一侧较低,就移动。一句话概括:尽量手持大的板,去找更大的板。
class Solution {
public:
int maxArea(vector<int>& height) {
int n = height.size(), left = 0, right = n - 1;
int area = 0, max_area = 0;
while (left < right){
area = min(height[left], height[right]) * (right - left);
if (area > max_area) max_area = area;
if (height[left] < height[right]){
left ++;
}
else{
right --;
}
}
return max_area;
}
};
时间复杂度: O(n); 空间复杂度:O(1)。
看到题解下面的有条评论说得很精彩,摘录如下:
by Quirky Satoshi4QI: 这个思路我理解了很久。本质上这是一个如何去简化两个for循环的问题。可以这么来理解:假设本身要用
x y作为数组下标分别遍历才能完成所有情况的枚举,那么怎么才能简化呢?我们假设x是从左往右递增的,y是从右往左递减的,那么当x的木板比y的木板短时,说明这一轮的y的遍历提前结束,后面不论y如何向左移动都没用了。 当这一轮结束后,x向右指向一个,就变成了求解同样的一个新问题,只不过求解的范围缩小了而已。 最终xy相遇之后,所有的该遍历的都遍历到了,被简化掉的遍历项目就是我们这个算法的收益。
3. 三数之和(t15)
中等难度,题目示例如下:
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
解题思路:这题涉及到三元组,初看没想到如何求解,看了题解才清楚思路。这道题采用双指针的思路,核心解决两个问题:1.如何去重?2.双指针如何设置?
具体思路是先对数组进行从小到大排序,遍历每一个元素位置,双指针区间初始设置在该元素后面,通过相邻元素判断来解决去重问题。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int n = nums.size();
sort(nums.begin(), nums.end());
vector<vector<int>> ans;
for (int i = 0; i < n; i++){
if (i > 0 && nums[i] == nums[i - 1]) continue;
int l = i + 1, r = n - 1;
while (l < r){
if (nums[l] + nums[r] + nums[i] == 0){
ans.push_back({nums[i], nums[l], nums[r]});
l++;
r--;
while (l < r && nums[l] == nums[l - 1]) l++;
while (l < r && nums[r] == nums[r + 1]) r--;
}
elseif (nums[l] + nums[r] + nums[i] < 0){
l++;
}
else {
r--;
}
}
}
return ans;
}
};
时间复杂度:O(),引入双指针的核心作用就是让原本需要三次循环减少到两次,左右指针往中心靠近的过程,减少了一次循环带来的计算量。
空间复杂度:O(logN)主要来自于排序。
4. 接雨水(t42)
困难难度,题目示例如下:
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例 2:
输入:height = [4,2,0,3,2,5]
输出:9
解题思路:题目涉及物理模拟,初见比较难,没什么思路,直接看题解,官方提供了三种解题思路。
思路1:动态规划
动态规划的核心就是做问题拆解,因此,可以将问题拆成三个步骤:
- 1.假设右边无限高,那么接水量只取决于左边,先统计这种情况下每列最多能接多少水。
- 2.假设左边无限高,那么接水量只取决于右边,再统计这种情况下每列最多能接多少水。
- 3.最终实际的接水量就是两者最小值 - 本身容器高度。
class Solution {
public:
int trap(vector<int>& height) {
int n = height.size();
if (n == 0) {
return0;
}
vector<int> leftMax(n);
leftMax[0] = height[0];
for (int i = 1; i < n; i++) {
leftMax[i] = max(leftMax[i - 1], height[i]);
}
vector<int> rightMax(n);
rightMax[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; i--) {
rightMax[i] = max(rightMax[i + 1], height[i]);
}
int ans = 0;
for (int i = 0; i < n; i++) {
ans += min(leftMax[i], rightMax[i]) - height[i];
}
return ans;
}
};
时间复杂度:O(n),共遍历数组三次。
空间复杂度:O(n),额外创建 leftMax 和 rightMax 两个数组。
思路2:单调栈**
单调栈是指栈内元素按照单调顺序排列的栈。例如,单调递减栈,如果遇到比栈顶大的元素,就开始出栈,直到再次保持单调递减。
单调递减栈做这道题很合适,从左往右遍历,当遇到比栈顶还高的柱子时,就说明右边界出现了,可以开始计算中间能接多少水。
class Solution {
public:
int trap(vector<int>& height) {
int ans = 0;
stack<int> stk;
int n = height.size();
for (int i = 0; i < n; i++) {
while (!stk.empty() && height[i] > height[stk.top()]) {
int top = stk.top();
stk.pop();
if (stk.empty()) {
break;
}
int left = stk.top();
int currWidth = i - left - 1;
int currHeight = min(height[left], height[i]) - height[top];
ans += currWidth * currHeight;
}
stk.push(i);
}
return ans;
}
};
时间复杂度:O(n)
空间复杂度:O(n)
思路3:双指针
双指针实际上是对思路1的优化,通过左右两个指针不断往中间靠近,减小循环次数。核心思路是:如果左边更矮,那么当前左边所能接的水只受左边的最大值控制;如果右边更矮,就由右边最大值控制。非常巧妙,边移动边计算。
class Solution {
public:
int trap(vector<int>& height) {
int ans = 0;
int left = 0, right = height.size() - 1;
int leftMax = 0, rightMax = 0;
while (left < right) {
leftMax = max(leftMax, height[left]);
rightMax = max(rightMax, height[right]);
if (height[left] < height[right]) {
ans += leftMax - height[left];
++left;
} else {
ans += rightMax - height[right];
--right;
}
}
return ans;
}
};
时间复杂度:O(n)
空间复杂度:O(1)