hello,小伙伴们大家好~ 我是雨墨,近期开始准备刷 leetcode ~ 故想将自己的刷题笔记以博客的形式分享出来,这是我的第一篇博客,小伙伴们可以在评论区讨论和点赞哟~~
仅以此篇博文记录自己刷数组经典题的过程,日后好复习,持续更新中,每日更新两三题,直到刷完规化好的数组题~
现已将计划刷的数组题刷完了,后续开始刷其他类型的题目~~~
传送门:https://leetcode-cn.com/problems/two-sum/
题目描述
给定一个数组和一个整数目标值,使得两个数之和等于目标值,题目保证一定有解,要求返回数组下标。
样例
给定数组 nums = [2,7,11,15], target = 9
由于 nums[0] + nums[1] = 2 + 7 = 9
所以 return [0, 1]
算法1
(暴力枚举) O ( n 2 ) O(n^2) O(n2)
暴力枚举方法:枚举下标 i , j i, j i,j,然后判断 n u m s [ i ] + n u m s [ j ] = = t a r g e t nums[i] + nums[j] == target nums[i]+nums[j]==target
时间复杂度:由于是两重循环,所以时间复杂度是 O ( n 2 ) O(n^2) O(n2)
C++代码
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
for (int i = 0; i < nums.size(); ++i) {
for (int j = i + 1; j < nums.size(); ++j) {
if (nums[i] + nums[j] == target)
return {i, j};
}
}
return {};
}
};
算法2:
(哈希表) O ( n ) O(n) O(n)
使用C++中的哈希表——unrodered_map
循环一遍 n u m s nums nums数组,在每一步循环中做两件事:
解释:由于数据保证有且仅有一组解,假设是 [ i , j ] ( i < j ) [i, j](i < j) [i,j](i<j),则我们循环到 j j j时, n u m s [ i ] nums[i] nums[i]一定在哈希表中,且有 n u m s [ i ] + n u m s [ j ] = = t a r g e t nums[i] + nums[j] == target nums[i]+nums[j]==target,所以我们一定能找到解。
时间复杂度:由于只扫描一遍,每次哈希表的查找和插入操作的复杂度是 O ( 1 ) O(1) O(1),所以时间复杂度是 O ( n ) O(n) O(n)
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> hash;
for (int i = 0; i < nums.size(); ++i) {
int j = target - nums[i];
if (hash.count(j)) return {hash[j], i};
hash[nums[i]] = i;
}
return {};
}
};
传送门:https://leetcode-cn.com/problems/spiral-matrix/
题目描述
给定一个m
行n
列的矩阵matrix
,按照顺时针螺旋的顺序将它的所有元素用新的一个数组存放再返回出来。
样例
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
算法
(建立坐标系) O ( m n ) O(mn) O(mn)
按照上面的顺序,对数据进行模拟,设置类似于BFS的偏移量dx, dy
,总共为4个方向,每个方向遍历完就换另外一个方向,由此往复,就能将所有的元素顺时针螺旋遍历到。
时间复杂度:由于要遍历m*n
次,所以时间复杂度是 O ( m n ) O(mn) O(mn)
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> res;
int m = matrix.size();
if (!m) return res;
int n = matrix[0].size();
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};
vector<vector<bool>> st(m, vector<bool>(n)); // 初始化一个状态数组
for (int i = 0, x = 0, y = 0, d = 0; i < m * n; ++i) {
res.push_back(matrix[x][y]);
st[x][y] = true;
int a = x + dx[d], b = y + dy[d];
if (a < 0 || a >= m || b < 0 || b >= n || st[a][b]) {
d = (d + 1) % 4; // d有四个,所以每次更新就模4
a = x + dx[d], b = y + dy[d];
}
x = a, y = b;
}
return res;
}
};
传送门:https://leetcode-cn.com/problems/spiral-matrix-ii/submissions/
题目描述
给定一个正整数n
,让你帮忙生成一个1
到n^2
的螺旋矩阵。
样例
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
算法
(建立坐标系) O ( n 2 ) O(n^2) O(n2)
和上一题方法一摸一样,使用偏移量,当访问不下去了,就换另一条路走;
时间复杂度:由于矩阵大小是n*n
,所以时间复杂度是 O ( n 2 ) O(n^2) O(n2)
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> Matrix(n, vector<int>(n));
if (!n) return Matrix;
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};
vector<vector<bool>> st(n, vector<bool>(n));
for (int i = 1, x = 0, y = 0, d = 0; i <= n * n; ++i) {
Matrix[x][y] = i;
int a = x + dx[d], b = y + dy[d];
if (a < 0 || a >= n || b < 0 || b >= n || Matrix[a][b]) {
d = (d + 1) % 4;
a = x + dx[d], b = y + dy[d];
}
x = a, y = b;
}
return Matrix;
}
};
传送门:https://leetcode-cn.com/problems/plus-one/
题目描述
给定一个数组,数组中从头到尾的数字串起来表示一个整数。要在该数的基础上加一。然后再将加一过后的整数挨个存储在数组中。除了0之外,数组中的元素都不会以0开头。
样例
输入:digits = [1,2,3]
输出:[1,2,4]
解释:输入数组表示数字 123。
算法
(模拟) O ( n ) O(n) O(n)
取一个数出来,进行模拟加一后的结果,发现可能会发生进位,这就类似于高精度加法,也就是应该将原数组中的元素reverse
一遍,让进位存在数组的尾部,最后再reverse
回来。
时间复杂度:由于需要遍历数组中的每一位一次,所以时间复杂度是 O ( n ) O(n) O(n)。
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
reverse(digits.begin(), digits.end());
int t = 1; // 由于题目要求在该数的基础上加上一
for (int i = 0; i < digits.size(); ++i) {
t += digits[i];
digits[i] = t % 10; // 覆盖原来的元素
t /= 10;
}
if (t) digits.push_back(t);
reverse(digits.begin(), digits.end());
return digits;
}
};
传送门:https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/
题目描述
给定一个长度为n
的数组,已知数组的所有数字都在0~n-1
的范围内,数组中有的数字会重复,但是不知道重复了多少个,返回任意一个重复的数字。
样例
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
算法1
(原地交换) O ( n ) O(n) O(n)
下面的算法的主要思想是把每一个元素放在对应的位置上去,即nums[i] = i
(如下图所示的效果)。
x != i && nums[x] == x
,说明了 x x x已经出现了多次,直接返回 x x x。nums[x] != x
,那就需要将 x x x交换到正确的位置上去,即swap(nums[x], nums[i])
,交换完之后如果nums[i] != i
,则重复此操作。由于每次交换都会将一个数放在正确的位置上,所以swap操作最多进行 n n n次,不会发生死循环。循环结束过后,如果没有找到重复的数,则返回-1
。
时间复杂度:每次swap
操作都会将一个数放在正确的位置,最后一次swap
会将两个数放在正确的位置,一共只有 n n n个数和 n n n个位置,所以swap
最多会进行n-1
次操作,所以总的时间复杂度是 O ( n ) O(n) O(n)。
空间复杂度:由于是使用原有数组进行操作,所以空间复杂度是 O ( 1 ) O(1) O(1)。
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
int n = nums.size();
for (int i = 0; i < n; ++i) {
while (nums[i] != nums[nums[i]]) swap(nums[i], nums[nums[i]]);
if (i != nums[i]) return nums[i];
}
return -1;
}
};
算法2
(哈希存储) O ( n ) O(n) O(n)
下面算法的主要思想是想确认nums[i]
是否已经出现过了:
nums
,如果record[x] == false
,表示还没有出现过这个元素值,将其标记为true
record[x] == true
,则return x
遍历结束,还没有返回的话,说明没有出现重复的元素,return -1
。
时间复杂度:最多访问 n n n次元素,所以时间复杂度是 O ( n ) O(n) O(n);
空间复杂度:由于使用额外的哈希表来存储,所以空间复杂度是 O ( n ) O(n) O(n)。
因此,第一种算法可能更容易让面试官眼前一亮。
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
unordered_map<int, bool> record;
for (auto& x : nums) {
if (record[x]) return x;
record[x] = true;
}
return -1;
}
};
传送门:https://leetcode-cn.com/problems/sort-colors/
题目描述
给定一个数组,包含红色、白色、蓝色三种颜色,分别用0、1、2表示,原地对数组进行排序,使得排序过后的结果为红色的在一块、白色的在一块、蓝色的在一块。要求仅用常数空间扫描一趟。
样例
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
算法1
(类似于双指针的三指针算法) O ( n ) O(n) O(n)
下面算法是设立分界线来确定不同的颜色该放到哪一个位置:
0~j-1
的区间存放的是0,j~i-1
存放的是1,k~n-1
存放的是2;nums[i] == 0
,说明应该跟nums[j]
做交换,交换完之后,j++
;nums[i] == 2
,说明应该跟nums[k]
做交换,此时k--
,但是不能确定nums[k]
存放的值是0还是1,所有不能对 i i i进行操作;nums[i] == 1
,直接++i
。时间复杂度:由于只需要遍历整个数组一遍,所以时间复杂度是 O ( n ) O(n) O(n)。
空间复杂度:由于只是在原地进行交换,所以空间复杂度是 O ( 1 ) O(1) O(1)。
class Solution {
public:
void sortColors(vector<int>& nums) {
int n = nums.size();
if (!n) return;
for (int i = 0, j = 0, k = n - 1; i <= k;) {
if (nums[i] == 0) swap(nums[i++], nums[j++]);
else if (nums[i] == 2) swap(nums[i], nums[k--]);
else ++i;
}
}
};
算法2
遍历整个数组,分别记录0的个数是多少,1的个数是多少,2的个数是多少,然后再分别插入回去。(水题可以,但是水面试官就不太行)
算法3
直接sort一遍,纯属找死…
传送门:https://leetcode-cn.com/problems/subarray-sum-equals-k/
题目描述
给定一个数组nums
和一个整数k
,返回数组中和为k
的连续子数组的个数。
样例
输入:nums = [1,1,1], k = 2
输出:2
算法
(前缀和+哈希表) O ( n ) O(n) O(n)
算法描述:
时间复杂度:由于算前缀和的时间复杂度是 O ( n ) O(n) O(n),哈希表取出每个元素的时间复杂度是 O ( 1 ) O(1) O(1),但是需要遍历 n n n次,所以时间复杂度也是 O ( n ) O(n) O(n),综合来看时间复杂度为 O ( n ) O(n) O(n)。
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int n = nums.size();
if (!n) return -1;
vector<int> prefixSum(n + 1);
// 求 PrefixSum[j-1]
for (int i = 1; i <= n; ++i) prefixSum[i] = prefixSum[i - 1] + nums[i - 1];
unordered_map<int, int> hash;
hash[0] = 1; // 初始时刻前缀和为0
int cnt = 0;
for (int i = 1; i <= n; ++i) {
cnt += hash[prefixSum[i] - k];
++hash[prefixSum[i]];
}
return cnt;
}
};
优化
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int n = nums.size();
unordered_map<int, int> hash;
int cnt = 0, prefixSum = 0;
hash[0] = 1; // 初始时刻前缀和为0
for (auto& x : nums) {
prefixSum += x;
cnt += hash[prefixSum - k];
++hash[prefixSum];
}
return cnt;
}
};
传送门:https://leetcode-cn.com/problems/binary-search/
题目描述
给定一个大小为n
的升序的数组nums
和target
,查找数组中有无元素等于target
,有则返回下标,没有则返回-1
,下标从0开始。
样例
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
算法
(二分查找) O ( l o g n ) O(logn) O(logn)
算法描述:
nums[mid] >= target
,即所找到的元素一定是第一个大于等于target
的check
函数的条件,则可以想象到答案一定在mid
左边,包含mid
,如果不满足条件,则答案一定在mid
右边,且不包含mid
时间复杂度:由于是折半查找,所以时间复杂度是 O ( l o g n ) O(logn) O(logn)
class Solution {
public:
int search(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1;
while (l < r) {
int mid = l + r >> 1;
if (nums[mid] >= target) r = mid; // 一定是左边区间满足
else l = mid + 1; // 如果不满足, 答案肯定在右边区间
}
if (nums[l] == target) return l;
return -1;
}
};
传送门:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/
题目描述
给定一个数组,要求原地删除重复出现的元素,返回删除后新数组的长度。
样例
输入:nums = [1,1,2]
输出:2, nums = [1,2]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
算法
(双指针) O ( n ) O(n) O(n)
算法描述:为什么说他经典呢?C++中的unique算法就是这样的思路
fastIndex
遍历整个数组,慢指针slowIndex
确定新的数组fastIndex
遍历的数与前一个数相同,则不加入到满指针确定的数组中,如果不相等,则加入到新的数组当中。时间复杂度:最坏的情况是,整个数组没有重复的元素,此时快慢指针都会遍历n
个元素,时间复杂度仍然为 O ( n ) O(n) O(n)。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.size(); ++fastIndex) {
if (!fastIndex || nums[fastIndex] != nums[fastIndex - 1])
nums[slowIndex++] = nums[fastIndex];
}
return slowIndex;
}
};
传送门:https://leetcode-cn.com/problems/remove-element/submissions/
题目描述
给定一个数组nums
和一个值val
,原地移除所有等于val
的值,返回新的数组的长度。
样例
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
算法
(双指针算法) O ( n ) O(n) O(n)
跟前面那道题思路一摸一样,不再进行过多阐述。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.size(); ++fastIndex) {
if (nums[fastIndex] != val)
nums[slowIndex++] = nums[fastIndex];
}
return slowIndex;
}
};
传送门:https://leetcode-cn.com/problems/squares-of-a-sorted-array/
题目描述
给定一个非递减序的整数nums
,返回一个非递减的由每个数字的平方组成的新数组。
样例
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
算法1
(暴力+快排) O ( l o g n ) O(logn) O(logn)
算法描述:直接将所有元素取平方再sort
一遍。
时间复杂度:取平方的时间复杂度是 O ( n ) O(n) O(n),快排的时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn),所以总的时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
for (auto& x : nums) x *= x;
sort(nums.begin(), nums.end());
return nums;
}
};
算法2
(归并双指针) O ( n ) O(n) O(n)
算法描述:
时间复杂度:由于只扫描了一遍原数组,所以时间复杂度是 O ( n ) O(n) O(n)的。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int n = nums.size();
vector<int> res(n);
for (int i = 0, j = n - 1, k = n - 1; i <= j;) {
if (nums[i] * nums[i] > nums[j] * nums[j]) {
res[k] = nums[i] * nums[i];
--k, ++i;
}else {
res[k] = nums[j] * nums[j];
--k, --j;
}
}
return res;
}
};
传送门:https://leetcode-cn.com/problems/minimum-size-subarray-sum/
题目描述
给定一个n
个正整数的数组和一个正整数target
。要求找出满足≥ target
的长度最小的连续子数组,并返回其长度。如果不存在符合条件的子数组,则返回 0 0 0。
样例
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
算法
(双指针) O ( n ) O(n) O(n)
算法描述:
fastIndex
,且保证的是快指针一定在慢指针之后,然后考虑fastIndex
往后移动的时候,slowIndex
也会往后移,那就说明具有单调性。确定单调性有什么用,当然是保证双指针算法一定能够被使用。slowIndex
减小连续数组的长度。时间复杂度:由于是双指针算法,所以时间复杂度是 O ( n ) O(n) O(n)。
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int res = INT_MAX;
for (int fastIndex = 0, slowIndex = 0, sum = 0; fastIndex < nums.size(); ++fastIndex) {
sum += nums[fastIndex];
// 不断试探移动slowIndex
while (sum - nums[slowIndex] >= target) sum -= nums[slowIndex++];
if (sum >= target) res = min(res, fastIndex - slowIndex + 1);
}
if (res == INT_MAX) return 0;
return res;
}
};
传送门:https://leetcode-cn.com/problems/gou-jian-cheng-ji-shu-zu-lcof/
题目描述
给定一个数组a[0, 1, …, n-1]
,构建一个b
数组,其中b[i]
的值是数组a
除了下标i
以外的元素的乘积,言外之意就是计算a[i]
从0~n-1
的乘积然后再除以特定的a[i]
的值。但是本题要求不能使用除法。
思考
能不能使用常数的空间
样例
输入: [1,2,3,4,5]
输出: [120,60,40,30,24]
算法
(拆分遍历) O ( n ) O(n) O(n)
算法描述:
i
左边的a[i]
计算出来,然后再乘上 i i i 右侧的a[i]
,这样解题思路就有了。tmp
,表示i
之前的乘积,然后分别赋给b[i]
,这样就完成了求i
之前乘积的工作了,然后考虑i
之后的乘积如何求,从n - 1
倒着给b[i] * tmp
(这也是本题的绝妙做法)。每次都给tmp
初始化为 1 ,因为在i = 0
之前或i = n -1
之后都没有元素,所以应该将tmp
定义为 1 。时间复杂度:两次遍历,一共 2n 次,所以时间复杂度是 O ( n ) O(n) O(n)
class Solution {
public:
vector<int> constructArr(vector<int>& a) {
if (a.empty()) return vector<int> ();
int n = a.size();
vector<int> b(n);
for (int i = 0, tmp = 1; i < n; ++i) {
b[i] = tmp;
tmp *= a[i];
}
for (int i = n - 1, tmp = 1; ~i; --i) {
b[i] *= tmp;
tmp *= a[i];
}
return b;
}
};
传送门:https://leetcode-cn.com/problems/third-maximum-number/
题目描述
给定一个非空数组,要你返回数组中第三大的数(数不能是重复值),如果不存在这个数,则返回最大的数。
提示
1 <= nums.length <= 10^4
-2^31 <= nums[i] <= 2^31 - 1
样例
输入:[1, 2]
输出:2
解释:第三大的数不存在, 所以返回最大的数 2 。
输入:[2, 2, 3, 1]
输出:1
解释:注意,要求返回第三大的数,是指在所有不同数字中排第三大的数。
此例中存在两个值为 2 的数,它们都排第二。在所有不同数字中排第三大的数为 1 。
算法1
(分段讨论) O ( n ) O(n) O(n)
一开始我以为是双指针算法,但是那样我没有想出来如何设计出按照降序排列的双指针,故放弃。
算法描述:
firstNum
、secondNum
、thirdNum
三个值存放最大值、次大值、第三大值-INF
,因为看数据范围,最大的数是long long
,因此在声明变量的时候应该注意这一点,否则就会出现buffer-overflow
的情况。x
大于firstNum
的时候,firstNum
应该要移动到x
的位置,同理secondNum
、thirdNum
也应该分别移动到firstNum
、secondNum
之前的位置,所以代码中需要注意交换的次序,同时还需要定义一个cnt
来计数。x
只是大于secondNum
但不大于firstNum
。x
只是大于thirdNum
但不大于secondNum
。时间复杂度:由于只是遍历一次数组,所以时间复杂度为 O ( n ) O(n) O(n)。
class Solution {
public:
int thirdMax(vector<int>& nums) {
long long INF = 1e10;
long long firstNum = -INF, secondNum = -INF, thirdNum = -INF, cnt = 0;
for (auto& x : nums) {
if (x > firstNum)
++cnt, thirdNum = secondNum, secondNum = firstNum, firstNum = x;
else if (x > secondNum && x < firstNum)
++cnt, thirdNum = secondNum, secondNum = x;
else if (x > thirdNum && x < secondNum)
++cnt, thirdNum = x;
}
if (cnt < 3) return firstNum;
return thirdNum;
}
};
算法2
(利用set的自动排序) O ( n ) O(n) O(n)
算法描述:由于set
底层是使用红黑树实现的,红黑树能够自动排序,且使用的是unique_insert
,所以使用set
就能轻而易举的拍好序且不出现重复元素(妙啊~~~)
时间复杂度:插入红黑树需要 O ( l o g n ) O(logn) O(logn)的时间复杂度,遍历需要 O ( n ) O(n) O(n)的时间复杂度,所以总的时间复杂度是 O ( n ) O(n) O(n)
class Solution {
public:
int thirdMax(vector<int>& nums) {
set<int> s;
for (const auto& x : nums) {
s.insert(x);
if (s.size() > 3) s.erase(s.begin());
}
if (s.size() == 3) return *s.begin();
return *s.rbegin();
}
};
传送门:https://leetcode-cn.com/problems/shortest-unsorted-continuous-subarray/
题目描述
给定一个数组,要求你找到一个连续子数组,使得将这个子数组进行升序排序之后,整个数组都是呈升序排序的,言外之意就是说,这个子数组的两边都是升序排序的且排序子数组之后这两边的序列仍然呈原有顺序排列。要求返回该子数组的长度,若没有这样的数组返回0
。
样例
输入:nums = [2,6,4,8,10,9,15]
输出:5
解释:你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。
输入:nums = [1,2,3,4]
输出:0
算法1
(快排+遍历) O ( l o g n ) O(logn) O(logn)
根据上述题目描述,很容易想到,可以将nums
数组拷贝到另一个数组中,对另一个数组进行sort
一遍,然后从头找到第一个不相等的位置,这就是子数组的起点,再从尾扫一遍,找到第一个不相等的位置,这就是子数组的尾部,最后算子数组的长度即可。
时间复杂度:由于使用了快排,所以总的时间复杂度为 O ( l o g n ) O(logn) O(logn)。
class Solution {
public:
int findUnsortedSubarray(vector<int>& nums) {
if (nums.empty()) return -1;
int n = nums.size();
vector<int> cop(nums.begin(), nums.end());
sort(cop.begin(), cop.end());
int i = 0, j = n - 1;
for (; i < n; ++i) {
if (nums[i] != cop[i])
break;
}
for (; ~j; --j) {
if (nums[j] != cop[j])
break;
}
if (j > i) return j - i + 1;
return 0;
}
};
算法2
(优化版本,不使用排序) O ( n ) O(n) O(n)
算法描述:
while loop
,即while (nums[left] <= nums[left + 1])
,如果left
已经走到了整个序列的终点,说明没有题目要求的子数组,所以return 0
;while loop
,即while (nums[right - 1] <= nums[right])
,最终right
会停下;nums[left]
是否满足小于右边序列的最小值,但是不用刻意去求右边序列的最小值,而是挨个和右边序列的每一个点比较,如果大于右边序列某一个值,说明它不属于左边序列;时间复杂度:由于遍历都是常数 n 的时间,所以时间复杂度是 O ( n ) O(n) O(n)
class Solution {
public:
int findUnsortedSubarray(vector<int>& nums) {
if (nums.empty()) return -1;
int left = 0, right = nums.size() - 1, len = nums.size();
// 首先寻找左边序列
while (left + 1 < len && nums[left] <= nums[left + 1]) ++left;
// 判断left是否已经走到了终点
if (left == right) return 0;
// 寻找右边序列
while (right - 1 >= 0 && nums[right - 1] <= nums[right]) --right;
// 寻找满足左边序列右端点条件的点
for (int i = left + 1; i < len; ++i) {
while (left >= 0 && nums[left] > nums[i])
--left;
}
// 寻找满足右边序列左端点条件的点
for (int i = right - 1; ~i; --i) {
while (right < len && nums[right] < nums[i])
++right;
}
// 不包含左端点
return right - left - 1;
}
};
传送门:https://leetcode-cn.com/problems/can-place-flowers/
题目描述
给定一个01
数组,要求每个1
之间不能相邻,问:能否插入n
个1
?如何可以则返回true
,不行则返回false
。
提示
0 <= n <= flowerbed.length
样例
输入:flowerbed = [1,0,0,0,1], n = 1
输出:true
算法
(分类讨论) O ( n ) O(n) O(n)
能否插入1
需要看0
的个数,讨论三种情况(需要画图手动模拟):
时间复杂度:由于只遍历了一遍数组,所以时间复杂度是 O ( n ) O(n) O(n)。
class Solution {
public:
bool canPlaceFlowers(vector<int>& flowerbed, int n) {
if (!n) return true;
int len = flowerbed.size();
int res = 0;
// 寻找某一段0的个数
for (int fastIndex = 0, slowIndex = 0; fastIndex < len; ++fastIndex) {
if (flowerbed[fastIndex] == 1) continue;
slowIndex = fastIndex;
while (slowIndex < len && !flowerbed[slowIndex]) ++slowIndex;
// 第一种情况:1 ... 1 之间,此时计算 0 个数的公式为 k - 1 / 2
int cnt = slowIndex - fastIndex - 1;
// 第二种情况:0 0 ... 1,只有一边有 1 ,此时计算 0 个数的公式为 k / 2
if (fastIndex == 0) ++cnt;
// 第三种情况:0 0 0 ... 0 0,全部都是 0 ,此时计算 0 个数的公式为 k + 1 / 2
if (slowIndex == len) ++cnt;
res += cnt / 2;
if (res >= n) return true;
fastIndex = slowIndex;
}
return false;
}
};
传送门:https://leetcode-cn.com/problems/maximum-product-of-three-numbers/
题目描述
给定一个数组,数组元素不小于3
,要求输出数组中由三个数组成的最大乘积。
样例
输入:nums = [1,2,3,4]
输出:24
算法
(分类讨论+排序) O ( n l o g n ) O(nlogn) O(nlogn)
讨论描述:
算法描述:由上述讨论结果,得出结论:只需要在数组最大的三个元素的乘积和数组最小的两个数与最大的数的乘积之间取最大值,结果一定是最终答案。取元素可以通过排序轻而易举取出。
时间复杂度:由于使用了sort
,所以时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)。
class Solution {
public:
int maximumProduct(vector<int>& nums) {
sort(nums.begin(), nums.end());
int len = nums.size();
return max(nums[len - 1] * nums[len - 2] * nums[len - 3],
nums[0] * nums[1] * nums[len - 1]);
}
};
传送门:https://leetcode-cn.com/problems/maximum-average-subarray-i/
题目描述
给定一个数组nums
和一个整数k
,要求返回最大的长为k
的连续子数组的平均数。
样例
输入:nums = [1,12,-5,-6,50,3], k = 4
输出:12.75
解释:最大平均数 (12-5-6+50)/4 = 51/4 = 12.75
算法
(滑动窗口) O ( n ) O(n) O(n)
算法描述:维持一个长度为k
的窗口,计算每个窗口的总和的平均值,求最大的返回。
class Solution {
public:
double findMaxAverage(vector<int>& nums, int k) {
double res = -1e5;
for (int rh = 0, lh = 0, sum = 0; rh < nums.size(); ++rh) {
sum += nums[rh];
if (rh - lh > k - 1) sum -= nums[lh++];
if (rh >= k - 1) res = max(res, sum / (double)k);
}
return res;
}
};
传送门:https://leetcode-cn.com/problems/non-decreasing-array/
题目描述
给定一个数组,要求判断至多改变一次元素就能使整个数组呈升序状态(至于怎么改,那是我的事,想怎么改就怎么改),若能返回true
,若不能返回false
。
样例
输入: nums = [4,2,3]
输出: true
解释: 你可以通过把第一个4变成1来使得它成为一个非递减数列。
算法
(遍历+检查) O ( n ) O(n) O(n)
算法描述:本题延外之意就是要寻找是否有逆序对儿,如果有,尝试修改这个逆序对儿,修改之后再check
这个数组是否升序排列。
修改方案:由于要使得它升序,就让逆序的两个数都等于第一个数或是都等于第二个数。
class Solution {
public:
bool check(vector<int>& nums) {
for (int i = 0; i + 1 < nums.size(); ++i) {
if (nums[i] > nums[i + 1])
return false;
}
return true;
}
bool checkPossibility(vector<int>& nums) {
for (int i = 0; i + 1 < nums.size(); ++i) {
if (nums[i] <= nums[i + 1]) continue;
int tmp1 = nums[i], tmp2 = nums[i + 1];
nums[i] = nums[i + 1] = tmp1;
if (check(nums)) return true;
nums[i] = nums[i + 1] = tmp2;
if (check(nums)) return true;
return false;
}
return true;
}
};
传送门:https://leetcode-cn.com/problems/degree-of-an-array/
题目描述
给定一个非空且只含非负元素的数组nums
,数组的度定义的是数组中任一元素出现频数的最大值(即众数),要求在nums
中找到与nums
具有相同的度且长度最短的连续子数组,返回其长度。
样例
输入:[1, 2, 2, 3, 1]
输出:2
解释:
输入数组的度是2,因为元素1和2的出现频数最大,均为2.
连续子数组里面拥有相同度的有如下所示:
[1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2]
最短连续子数组[2, 2]的长度为2,所以返回2.
算法
(哈希表存储) O ( n ) O(n) O(n)
算法描述:
class Solution {
public:
int findShortestSubArray(vector<int>& nums) {
unordered_map<int, int> cnt, firstp, lastp;
int span = 0, res = INT_MAX;
int len = nums.size();
for (int i = 0; i < len; ++i) {
int x = nums[i];
if (!cnt[x]) firstp[x] = i; // 记录第一次出现的位置
span = max(span, ++cnt[x]); // 找数组的度
lastp[x] = i; // 记录最后一次出现的位置
}
for (const auto& x : nums) {
if (cnt[x] == span) {
int tmp = lastp[x] - firstp[x] + 1;
res = tmp < res ? tmp : res;
}
}
return res;
}
};
传送门:https://leetcode-cn.com/problems/longest-continuous-increasing-subsequence/
题目描述
给定一个未经排序的整数数组,找到最长且连续递增的子序列,返回其长度。
样例
输入:nums = [1,3,5,4,7]
输出:3
算法
(双指针算法) O ( n ) O(n) O(n)
这是一道非常经典的双指针算法题目。由于序列具有单调性,所以可以使用双指针。
class Solution {
public:
int findLengthOfLCIS(vector<int>& nums) {
int len = 0, r = 0;
for (int l = 0; l < nums.size(); ++l) {
r = l + 1;
while (r < nums.size() && nums[r] > nums[r - 1]) ++r;
len = max(len, r - l);
l = r - 1;
}
return len;
}
};
传送门:https://leetcode-cn.com/problems/1-bit-and-2-bit-characters/
题目描述
给定两个特殊字符,分别使用1比特0
和2比特10
或11
表示,先取出若干比特组成的字符串,问最后一个字符是否必定为1比特字符,若是则返回true
。
样例
输入:
bits = [1, 0, 0]
输出: True
输入:
bits = [1, 1, 1, 0]
输出: False
算法
(模拟) O ( n ) O(n) O(n)
如果是 1 开始的数,则必定是2比特字符,所以index
加 2 ,否则加 1 ,若到达最后位置且尾末元素是 0 ,返回true
。
class Solution {
public:
bool isOneBitCharacter(vector<int>& bits) {
int len = bits.size();
for (int i = 0; i < len; ++i) {
if (i == len - 1 && !bits[i]) return true;
if (bits[i]) ++i;
}
return false;
}
};
传送门:https://leetcode-cn.com/problems/positions-of-large-groups/
题目描述
给定一个由小写字母构成的字符串s
,该字符串包含一些连续的相同字符所构成的分组,如果这些分组长度大于或等于三,称之为较大分组,找出题目中较大分组的区间,按起始下标递增排序后返回结果。
样例
输入:s = "abbxxxxzzy"
输出:[[3,6]]
解释:"xxxx" 是一个起始于 3 且终止于 6 的较大分组
输入:s = "aba"
输出:[]
算法
(双指针) O ( n ) O(n) O(n)
这道题是一道非常 nice 的求区间问题,看到这样的题我首先想到的就是双指针。
class Solution {
public:
vector<vector<int>> largeGroupPositions(string s) {
int len = s.size();
if (len == 1 || len == 2) return vector<vector<int>> ();
vector<vector<int>> res;
for (int slowIndex = 0; slowIndex < len; ++slowIndex) {
int fastIndex = slowIndex + 1;
while (fastIndex < len && s[fastIndex] == s[fastIndex - 1]) ++fastIndex;
if (fastIndex - slowIndex >= 3) res.push_back({slowIndex, fastIndex - 1});
slowIndex = fastIndex - 1;
}
return res;
}
};
传送门:https://leetcode-cn.com/problems/magic-squares-in-grid/
题目描述
3 x 3
的幻方是指一个填充从1
到9
的矩阵 3 x 3
矩阵,其中每行每列以及两条对角线上的个数之和都相等。问:给定一个多维数组grid
,其中有多少个幻方矩阵。
提示
1 <= grid.length <= 10
1 <= grid[0].length <= 10
0 <= grid[i][j] <= 15
样例
输入: [[4,3,8,4],
[9,5,1,9],
[2,7,6,2]]
输出: 1
下面的子矩阵是一个 3 x 3 的幻方:
438
951
276
而这一个不是:
384
519
762
算法
(暴力枚举) O ( n m ) O(nm) O(nm)
由于给定数据较小,直接枚举就完事了。
class Solution {
public:
bool check(int x, int y, const vector<vector<int>>& grid) {
vector<bool> st(10, false);
int tmp = 0;
// 枚举每一个元素
for (int r = x; r < x + 3; ++r) {
for (int c = y; c < y + 3; ++c) {
tmp = grid[r][c];
if (tmp < 1 || tmp > 9) return false;
if (st[tmp]) return false;
st[tmp] = true;
}
}
// 枚举每一个行每一列
for (int i = 0; i < 3; ++i) {
if (grid[x + i][y] + grid[x + i][y + 1] + grid[x + i][y + 2] != 15) return false;
if (grid[x][y + i] + grid[x + 1][y + i] + grid[x + 2][y + i] != 15) return false;
}
// 枚举每一个对角线和反对角线
if (grid[x][y] + grid[x + 1][y + 1] + grid[x + 2][y + 2] != 15) return false;
if (grid[x + 2][y] + grid[x + 1][y + 1] + grid[x][y + 2] != 15) return false;
return true;
}
int numMagicSquaresInside(vector<vector<int>>& grid) {
int row = grid.size(), col = grid[0].size();
int res = 0;
for (int x = 0; x + 3 <= row; ++x) {
for (int y = 0; y + 3 <= col; ++y) {
if (check(x, y, grid))
++res;
}
}
return res;
}
};
传送门:https://leetcode-cn.com/problems/maximize-distance-to-closest-person/
题目描述
给定一个数组seats
表示一排座位,0
表示该座位没人,1
表示该座位有人,下标从0
开始。至少有一个空座位且至少有一个人已经在座位上了。Alex 比较喜欢静一静,他希望坐在离他距离最近的人最远的位置。
样例
输入:seats = [1,0,0,0,1,0,1]
输出:2
如果亚历克斯坐在第二个空位(seats[2])上,他到离他最近的人的距离为 2 。
如果亚历克斯坐在其它任何一个空位上,他到离他最近的人的距离为 1 。
因此,他到离他最近的人的最大距离是 2 。
算法
(双指针) O ( n ) O(n) O(n)
这道题没有想到区间的时候,我根本就没觉得会使用双指针算法,但是知道以后发现是真的妙啊~
算法描述:
class Solution {
public:
int maxDistToClosest(vector<int>& seats) {
int len = seats.size();
int res = 0;
for (int slowIndex = 0; slowIndex < len; ++slowIndex) {
// 从第一个空座位开始
if (seats[slowIndex]) continue;
int fastIndex = slowIndex + 1;
while (fastIndex < len && !seats[fastIndex]) ++fastIndex;
// 特判,如果是边界没有人(左边界或是右边界)
if (!slowIndex || fastIndex == len) res = max(res, fastIndex - slowIndex);
// 否则就取相邻最近的两个人的终点
else res = max(res, (fastIndex - slowIndex + 1) / 2);
}
return res;
}
};
传送门:https://leetcode-cn.com/problems/fair-candy-swap/
题目描述
给定两个数组,问能否通过交换两个数组中的一个元素,使得两个数组的元素总和一样大。
样例
输入:A = [1,1], B = [2,2]
输出:[1,2]
算法
(哈希表+推导公式) O ( n ) O(n) O(n)
算法描述:
aliceSizes
时计算 c a n d y o f B o b candyofBob candyofBob,看能否寻得哈希表中有此值,有的话返回 c a n d y o f A l i c e , c a n d y o f B o b {candyofAlice, candyofBob} candyofAlice,candyofBobclass Solution {
public:
vector<int> fairCandySwap(vector<int>& aliceSizes, vector<int>& bobSizes) {
int sumAlice = accumulate(aliceSizes.begin(), aliceSizes.end(), 0);
int sumBob = accumulate(bobSizes.begin(), bobSizes.end(), 0);
unordered_set<int> hash_table;
int tmp = (sumAlice - sumBob) / 2;
for (const auto& candy : bobSizes) hash_table.insert(candy);
for (const auto& candy: aliceSizes) {
if (hash_table.count(candy - tmp))
return {candy, candy - tmp};
}
return {};
}
};
传送门:https://leetcode-cn.com/problems/x-of-a-kind-in-a-deck-of-cards/
题目描述
给定一副牌,每张牌都有对应的数组,问能否将牌分为一组或多组:
X
张牌当且仅当可选的X >= 2
时返回true
。
样例
输入:[1,2,3,4,4,3,2,1]
输出:true
算法
(最大公约数) O ( n l o g n ) O(nlogn) O(nlogn)
算法描述:
num
对应的牌数sum
与X
之间的最大公约数,使用辗转相除法class Solution {
public:
// 辗转相除法求最大公约数
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
bool hasGroupsSizeX(vector<int>& deck) {
unordered_map<int, int> cnt;
int maxDiv = 0; // 最大公约数
for (const auto& card : deck) ++cnt[card];
for (const auto& [num, sum] : cnt) maxDiv = gcd(sum, maxDiv);
return maxDiv >= 2;
}
};
传送门:https://leetcode-cn.com/problems/valid-mountain-array/
题目描述
问给定数组是否满足先单调递增后单调递减。
样例
输入:arr = [0,3,2,1]
输出:true
算法
(模拟) O ( n ) O(n) O(n)
先模拟前半段是否单增,如果压根就没有前半段单增或者整个数组都是单增,则return false
;再判断后半段是否单调递减,如果是,那么指针应该走到了数组的尾末,判断是否满足即可。
class Solution {
public:
bool validMountainArray(vector<int>& arr) {
int i = 0;
int len = arr.size();
// 模拟前半段
while (i + 1 < len && arr[i + 1] > arr[i]) ++i;
if (i == len - 1 || !i) return false;
// 模拟后半段
while (i + 1 < len && arr[i + 1] < arr[i]) ++i;
return i == len - 1;
}
};
传送门:https://leetcode-cn.com/problems/add-to-array-form-of-integer/
题目描述
给定一个非负整数,将其每位数字存入数组中,给它加上一个数,返回它的数组形式。
样例
输入:A = [1,2,0,0], K = 34
输出:[1,2,3,4]
我的题解
(高精度加法) O ( n ) O(n) O(n)
这题一看就是高精度加法,很简单~
class Solution {
public:
vector<int> addToArrayForm(vector<int>& num, int k) {
reverse(num.begin(), num.end());
for (auto& nu : num) {
k += nu;
nu = k % 10;
k /= 10;
}
while (k) {
num.push_back(k % 10);
k /= 10;
}
reverse(num.begin(), num.end());
return num;
}
};
传送门:https://leetcode-cn.com/problems/container-with-most-water/
题目描述
给定一个数组,数组中每个元素表示容器的高度,问怎样能够使得容器盛最多的水
样例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OkGcvUy5-1634193532506)(leetcode实战篇-数组.assets/question_11.jpg)]
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
算法
(双指针) O ( n ) O(n) O(n)
这道题比较难想到使用双指针来做。用生活中的道理来说明为何可以使用双指针:木桶容量由短板决定, 移动长板的话, 水面高度不可能再上升, 而宽度变小了, 所以只有通过移动短板, 才有可能使水位上升。
做法:使用两个指针head
,tail
表明容器的头和尾,如果num[head] < num[tail]
,则说明head
所指的是短板,应该寻找长版,故++head
,否则–-tail
,直至两个指针相遇为止,每次更新指针也要更新容器的最大容量。
反证法证明双指针可行:假设容器容量达到最大时,h
为头,t
为尾,不妨设head
指针先到达h
处,此时tail
指针还在来的路上,那么一定有tail
到达最优解的路上所遇都是比num[t]
小的木板。
class Solution {
public:
int maxArea(vector<int>& height) {
int res = 0;
for (int head = 0, tail = height.size() - 1; head < tail;) {
res = max(res, min(height[head], height[tail]) * (tail - head));
if (height[head] < height[tail]) ++head;
else --tail;
}
return res;
}
};