目录
704. 二分查找
给定一个 n
个元素有序的(升序)整型数组 nums
和一个目标值 target
,写一个函数搜索 nums
中的 target
,如果目标值存在返回下标,否则返回 -1
。
二分模板题
class Solution {
public:
int search(vector<int>& nums, int target) {
int l = 0, r=nums.size()-1;
while (l < r)
{
int mid = (l + r)/ 2;
if (nums[mid] == target) return mid;
else if (nums[mid] > target) r = mid - 1;
else l = mid + 1;
}
if (nums[l] == target) return l; //当查找到l==r时,nums[l]||nums[r]可能为答案
else return -1;
}
};
35. 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n)
的算法。
依旧是二分模板题,当while循环不满足条件时,此时l==r。
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1;
while (l < r)
{
int mid = (l + r) / 2;
if (nums[mid] == target) return mid;
else if (nums[mid] > target) r = mid;
else l = mid + 1;
}
if (nums[l] == target) return l;
else if (nums[l] < target) return l + 1;
else return l;
}
};
34. 在排序数组中查找元素的第一个和最后一个位置
给你一个按照非递减顺序排列的整数数组 nums
,和一个目标值 target
。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target
,返回 [-1, -1]
。
你必须设计并实现时间复杂度为 O(log n)
的算法解决此问题。
依旧是二分模板题,y总的模板真好用。。。
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
if (!nums.size()) return vector<int>{-1, -1}; // 当nums为空时
// 寻找左边界
int l = 0, r = nums.size() - 1;
while (l < r)
{
int mid = (l + r) / 2;
if (nums[mid] >= target) r = mid;
else l = mid + 1;
}
int res1,res2;
if (nums[l] != target)
{
res1=-1;
}else{
res1=l;
}
//寻找右边界
l = 0, r = nums.size() - 1;
while (l < r)
{
int mid = (l + r + 1) / 2;
if (nums[mid] <= target) l = mid;
else r = mid - 1;
}
if (nums[l] != target)
{
res2=-1;
}else{
res2=l;
}
return vector<int>{res1, res2};
}
};
69. x 的平方根
给你一个非负整数 x
,计算并返回 x
的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5)
或者 x ** 0.5
。
浮点数二分,本题需要返回x的算术平方根向下取整的整数结果,如x=8时结果为2,x=4时结果为2。当mid*mid>x时,则mid不可能是结果,当mid*mid<=x时,mid可能是结果。
class Solution {
public:
int mySqrt(int x) {
int l = 0, r = 0x3f3f3f3f;
while (l < r)
{
int mid = l + (r - l + 1) / 2; //代替mid=(l+r)/2这种可能爆int的写法
if (mid > x / mid) r = mid - 1; //防止mid爆int
else l = mid;
}
return r;
}
};
367. 有效的完全平方数
给你一个正整数 num
。如果 num
是一个完全平方数,则返回 true
,否则返回 false
。
完全平方数 是一个可以写成某个整数的平方的整数。换句话说,它可以写成某个整数和自身的乘积。
不能使用任何内置的库函数,如 sqrt
。
这题和上一题x的平方根是一样的思路
class Solution {
public:
bool isPerfectSquare(int num) {
int l = 0, r = 0x3f3f3f3f;
while (l < r)
{
int mid = l + (r - l + 1) / 2 ;
if (mid > num / mid) r = mid - 1;
else l = mid;
}
return l * l == num;
}
};
27. 移除元素
给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1)
额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
本题可以采用双指针来完成,可以定义一个慢指针指向新数组(不含val元素的数组)的末尾,一个快指针遍历原数组。当快指针找到val时,则跳过该元素;否则将快指针所在元素赋给当前慢指针的下一个位置。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int i, j;
for (i = 0, j = 0; i < nums.size(); i++)
{
if (nums[i] != val)
{
nums[j] = nums[i];
j++;
}
}
return j;
}
};
26. 删除有序数组中的重复项
给你一个 非严格递增排列 的数组 nums
,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums
中唯一元素的个数。
考虑 nums
的唯一元素的数量为 k
,你需要做以下事情确保你的题解可以被通过:
- 更改数组
nums
,使nums
的前k
个元素包含唯一元素,并按照它们最初在nums
中出现的顺序排列。nums
的其余元素与nums
的大小不重要。 - 返回
k
。
思路和上一题一样
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int i, j;
int flag = nums[0]; // 用于储存快指针的上一个元素
for (i = 1, j = 1; i < nums.size(); i++)
{
flag = nums[j-1];
if (nums[i] != flag)
{
nums[j] = nums[i];
j++;
}
}
return j;
}
};
283. 移动零
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
依然是和前两题一模一样的思路
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int i, j;
for (i = 0, j = 0; i < nums.size(); i++)
{
if (nums[i] != 0)
{
nums[j] = nums[i];
j++;
}
}
while (j < nums.size()) //把0补上
{
nums[j++] = 0;
}
}
};
844. 比较含退格的字符串
给定 s
和 t
两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true
。#
代表退格字符。
注意:如果对空文本输入退格字符,文本继续为空。
分别定义两个指针指向两个字符串的末尾,以及两个变量来储存当前遇到的连续的#号数目,若变量>0时则使指针多前进一步(不包括指针当前指向的仍是#号时),然后使变量大小-1。在指针移动完后比较两个指针指向的字符,若不相等则结束循环;若有一个指针已遍历到头也结束循环。
class Solution {
public:
bool backspaceCompare(string s, string t) {
int snum = 0, tnum = 0; //用于记录# int的默认值不是0吗。。。
int i = s.size() - 1, j = t.size() - 1; //双指针
while (1)
{
while (i >= 0)
{
if (s[i] == '#') snum++;
else if (snum) snum--;
else break; // 已经将跳过了s个非#字符
i--;
}
while (j >= 0)
{
if (t[j] == '#') tnum++;
else if (tnum) tnum--;
else break; // 已经将跳过了t个非#字符
j--;
}
if (i < 0 || j < 0) break; //有一个字符串已经遍历完
if (s[i] != t[j]) return false;
i--;
j--;
}
if(i == -1 && j == -1) return true;
return false;
}
};
977. 有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
本题可以直接使用各种排序来做,这里依然使用双指针的方法。基于原数组已经是排序好的这一特性,定义两个指针分别指向原数组的开头和末尾,若指向元素的平方数大于对方,使该平方数添加到答案当中,并则使当前指针移动一步。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int size = nums.size();
vector<int> res(size);
for (int i = 0, j = size - 1, k = size - 1; i <= j;)
{
if (abs(nums[i]) > abs(nums[j]))
{
res[k--] = nums[i] * nums[i];
i++;
}else
{
res[k--] = nums[j] * nums[j];
j--;
}
}
return res;
}
};
209. 长度最小的子数组
给定一个含有 n
个正整数的数组和一个正整数 target
。
找出该数组中满足其总和大于等于 target
的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr]
,并返回其长度。如果不存在符合条件的子数组,返回 0
。
一道非常标准的滑动窗口题
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int n = nums.size();
int res = 0x3f3f3f3f;
int st, ed; // 滑动窗口下标
int sum = 0; //滑动窗口的和
for (st = 0, ed = 0; ed < n; ed++)
{
sum += nums[ed];
while (sum >= target)
{
res = min(res, ed - st + 1);
sum -= nums[st];
st++;
}
}
if (res != 0x3f3f3f3f) return res;
else return 0;
}
};
904. 水果成篮
你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits
表示,其中 fruits[i]
是第 i
棵树上的水果 种类 。
你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:
- 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
- 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
- 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits
,返回你可以收集的水果的 最大 数目。
和上一题差不多的思路,不过这题调整滑动窗口的左边界更加困难,可以用一个哈希表来储存当前滑动窗口内水果种类及其个数。
class Solution {
public:
int totalFruit(vector<int>& fruits) {
int n = fruits.size();
int st, ed; //滑动窗口下标
unordered_map<int, int> cnt; //储存滑动窗口内两种水果的个数
int ans = 0;
for (st = 0, ed = 0; ed < n; ed++)
{
cnt[fruits[ed]]++;
while (cnt.size() > 2)
{
auto it = cnt.find(fruits[st]);
it->second--; //将滑动窗口最左元素移除
if(it->second == 0)
{
cnt.erase(it);
}
st++; //滑动窗口左移一位
}
ans = max(ans, ed - st + 1);
}
return ans;
}
};
76. 最小覆盖子串
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
注意:
- 对于
t
中重复字符,我们寻找的子字符串中该字符数量必须不少于t
中该字符数量。 - 如果
s
中存在这样的子串,我们保证它是唯一的答案。
这题仍然是滑动窗口问题,但是与前两题不同的是,这题需要寻找最小的滑动窗口。这题记录滑动窗口中的值与上题类似,可以用一个哈希表来存储。在ed不断扩张滑动窗口时可以判断是否能够缩小st,若可以则缩小。
可以定义一个变量cnt来记录当前窗口内的字符是否已经满足t字符串,当滑动窗口中某个字符为t中字符,并且并非冗余字符时cnt++;当cnt等于t的size时可以判断当前滑动窗口是否是最小的覆盖子串。
class Solution {
public:
string minWindow(string s, string t) {
unordered_map<char, int> tcnt, scnt; //用于存储t中的字符出现个数,以及滑动窗口中字符出现的个数
int st, ed, cnt = 0; // cnt用于记录当前滑动窗口中满足条件的字符个数(不包括重复的) cnt在整个过程只增不减
string res = s + "initialize a max string"; // 储存结果
for (auto tmp : t) //初始化
{
tcnt[tmp]++;
}
for (st = 0, ed = 0; ed < s.size(); ed++)
{
scnt[s[ed]]++;
if (tcnt[s[ed]] >= scnt[s[ed]]) // 当前字符是t中的字符,且没有超过t中该字符的个数
{
cnt++;
}
while (scnt[s[st]] > tcnt[s[st]]) //当前的ed位置出现了新的st位置的字符,可以把滑动窗口往右缩
{
scnt[s[st]]--;
st++;
}
if (cnt == t.size())
{
if (ed - st + 1 < res.size()) //求最小的子串
{
res = s.substr(st, ed - st + 1);
}
}
}
if (res == s + "initialize a max string") return "";
return res;
}
};
59. 螺旋矩阵 II
给你一个正整数 n
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
矩阵模拟题,k神好强Qrt
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(n, 0));
int left = 0, right = n - 1, top = 0, bottom = n - 1; //边界
int cnt = 1;
while (cnt <= n * n)
{
// 从左到右
for (int j = left; j <= right; j++)
{
res[top][j] = cnt++;
}
top++;
//从上到下
for (int i = top; i <= bottom; i++)
{
res[i][right] = cnt++;
}
right--;
//从右到左
for (int j = right; j >= left; j--)
{
res[bottom][j] = cnt++;
}
bottom--;
//从下到上
for (int i = bottom; i >= top; i--)
{
res[i][left] = cnt++;
}
left++;
}
return res;
}
};
54. 螺旋矩阵
给你一个正整数 n
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
这题思路大体与上题一样,但是注意上题是一个正方形矩阵,而本题不一定为正方形,所以可能出现重复遍历一些元素的情况,所以要加上一些判断,判断当前是否已经遍历完矩阵。(不理解的话就自己模拟一遍吧)
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> res;
int left = 0, right = matrix[0].size() - 1;
int top = 0, bottom = matrix.size() - 1;
int cnt = 1;
int n = matrix.size(), m = matrix[0].size();
while (cnt <= n * m)
{
for (int j = left; j <= right && cnt <= n * m; j++)
{
res.push_back(matrix[top][j]);
cnt++;
}
top++;
for (int i = top; i <= bottom && cnt <= n * m; i++)
{
res.push_back(matrix[i][right]);
cnt++;
}
right--;
for (int j = right; j >= left && cnt <= n * m; j--)
{
res.push_back(matrix[bottom][j]);
cnt++;
}
bottom--;
for (int i = bottom; i >= top && cnt <= n * m; i--)
{
res.push_back(matrix[i][left]);
cnt++;
}
left++;
}
return res;
}
};
LCR 146. 螺旋遍历二维数组
给定一个二维数组 array
,请返回「螺旋遍历」该数组的结果。
螺旋遍历:从左上角开始,按照 向右、向下、向左、向上 的顺序 依次 提取元素,然后再进入内部一层重复相同的步骤,直到提取完所有元素。
与上面一模一样的题目。