leetcode
9. 回文数
题目
给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
示例 1:
输入:x = 121
输出:true
示例 2:
输入:x = -121
输出:false
解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:
输入:x = 10
输出:false
解释:从右向左读, 为 01 。因此它不是一个回文数。
题解
直接上代码:
class Solution {
public:
bool isPalindrome(int x) {
// 特殊情况:
// 如上所述,当 x < 0 时,x 不是回文数。
// 同样地,如果数字的最后一位是 0,为了使该数字为回文,
// 则其第一位数字也应该是 0
// 只有 0 满足这一属性
if (x < 0 || (x % 10 == 0 && x != 0)) {
return false;
}
int revertedNumber = 0;
while (x > revertedNumber) {
revertedNumber = revertedNumber * 10 + x % 10;
x /= 10;
}
// 当数字长度为奇数时,我们可以通过 revertedNumber/10 去除处于中位的数字。
// 例如,当输入为 12321 时,在 while 循环的末尾我们可以得到 x = 12,revertedNumber = 123,
// 由于处于中位的数字不影响回文(它总是与自己相等),所以我们可以简单地将其去除。
return x == revertedNumber || x == revertedNumber / 10;
}
};
复杂度
时间复杂度:
O
(
l
o
g
n
)
O(logn)
O(logn),每次都除以10
空间复杂度:
O
(
1
)
O(1)
O(1)
关键点
分奇偶考虑问题
41. 缺失的第一个正数
题目
给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。
进阶:你可以实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案吗?
示例 1:
输入:nums = [1,2,0]
输出:3
示例 2:
输入:nums = [3,4,-1,1]
输出:2
示例 3:
输入:nums = [7,8,9,11,12]
输出:1
题解
核心思想:翻转数组元素作为下标对应的元素,之后找到没有被翻转过元素中对应的元素最小下标值+1,即为目标。
但这种算法的问题在于没有考虑数组中出现负数,解决办法为将数组中负数直接赋值为n+1。这样能够很好地兼容算法核心思想,因为取值为n+1的数组元素,不可能翻转数组中原有元素,进而不会影响到原有数组元素的翻转逻辑,示例代码如下所示:
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int length = nums.size();
for (int i = 0; i < length; ++i) {
if (nums[i] <= 0) {
nums[i] = length + 1;
}
}
for (int i = 0; i < length; ++i) {
int index = abs(nums[i]) - 1;
if (index <= length - 1) {
// 防止重复的元素负负为正
nums[index] = -abs(nums[index]);
}
}
for (int i = 0; i < length; ++i) {
if (nums[i] > 0) {
return i + 1;
break;
}
}
return length + 1;
}
};
复杂度
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
关键点
需要考虑到数组内出现负数的情况
[hot] 42. 接雨水
题目
给定 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 个单位的雨水(蓝色部分表示雨水)。
题解
定义两个数组L和R,L[i]的含义为从i向左看高度的最大值,R[i]的含义为从i向右看高度的最大值,这样min(L[i], R[i]) - height[i]即为i位置贡献的雨水的量,原理在上图已经给出。示例代码如下所示:
class Solution {
public:
int trap(vector<int>& height) {
int length = height.size();
vector<int> L(length, height[0]);
vector<int> R(length, height[length - 1]);
for (int i = 1; i < length; ++i) {
L[i] = max(height[i], L[i -1]);
}
for (int i = length - 2; i >=0; --i) {
R[i] = max(height[i], R[i + 1]);
}
int res = 0;
for (int i = 0; i < length; ++i) {
res += min(L[i], R[i]) - height[i];
}
return res;
}
};
复杂度
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
题解2
单调栈直接找水洼,示例代码如下所示:
class Solution {
public:
int trap(vector<int>& height) {
int n = height.size();
stack<int> stk;
int ans = 0;
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 width = i - left - 1;
int h = min(height[left], height[i]) - height[top];
ans += width * h;
}
stk.push(i);
}
return ans;
}
};
复杂度
时间:
O
(
n
)
O(n)
O(n)
空间:
O
(
n
)
O(n)
O(n)
题解3
双指针寻找水洼,示例代码如下所示:
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(n)
O(n)
空间:
O
(
1
)
O(1)
O(1)
[hot] 136. 只出现一次的数字
题目
给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
示例 1 :
输入:nums = [2,2,1]
输出:1
示例 2 :
输入:nums = [4,1,2,1,2]
输出:4
示例 3 :
输入:nums = [1]
输出:1
题解
异或运算解决问题,示例代码如下所示:
class Solution {
public:
int singleNumber(vector<int>& nums) {
int res = 0;
for (const auto& n: nums) {
res ^= n;
}
return res;
}
};
复杂度
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
[hot] 238. 除自身以外数组的乘积
题目
给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
示例:
输入: [1,2,3,4]
输出: [24,12,8,6]
提示:题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
题解
两遍遍历数组,将每一遍的中间结果记录在L和R中,最后将两个数组的取值乘在一起得到最终结果,示例代码如下所示:
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int length = nums.size();
// L[i]存储[0, i-1]的累乘结果
vector<int> L(length, 1);
// R[i]存储[i+1, length-1]的累乘结果
vector<int> R(length, 1);
for (int i = 1; i < length; ++i) {
L[i] = L[i - 1] * nums[i - 1];
}
for (int i = length - 2; i >= 0; --i) {
R[i] = R[i + 1] * nums[i + 1];
}
vector<int> result;
for (int i = 0; i < length; ++i) {
result.emplace_back(L[i] * R[i]);
}
return result;
}
};
复杂度
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
题解2
常数空间复杂度,示例代码如下:
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int length = nums.size();
vector<int> res(length, 0);
res[0] = 1;
for (int i = 1; i < length; ++i) {
res[i] = res[i - 1] * nums[i - 1];
}
int R = 1;
for (int i = length - 1; i >= 0; --i) {
res[i] = res[i] * R;
R *= nums[i];
}
return res;
}
};
268. 丢失的数字
题目
给定一个包含 [0, n] 中 n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。进阶:你能否实现线性时间复杂度、仅使用额外常数空间的算法解决此问题?
示例 1:
输入:nums = [3,0,1]
输出:2
解释:n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2 是丢失的数字,因为它没有出现在 nums 中。
示例 2:
输入:nums = [0,1]
输出:2
解释:n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2 是丢失的数字,因为它没有出现在 nums 中。
示例 3:
输入:nums = [9,6,4,2,3,5,7,0,1]
输出:8
解释:n = 9,因为有 9 个数字,所以所有的数字都在范围 [0,9] 内。8 是丢失的数字,因为它没有出现在 nums 中。
示例 4:
输入:nums = [0]
输出:1
解释:n = 1,因为有 1 个数字,所以所有的数字都在范围 [0,1] 内。1 是丢失的数字,因为它没有出现在 nums 中。
题解
本题有多种解决办法,例如hash表等,这里提供数学解法。因为数组的范围是[0,n]的n个数,这样缺失的数字一定在这个范围内,因而可以计算n个数字期望和以及当前数组的和,相减即可得到缺失的数字,示例代码如下所示:
class Solution {
public:
int missingNumber(vector<int>& nums) {
int length = nums.size();
int expect_sum = length * (length + 1) / 2;
int actual_sum = accumulate(nums.begin(), nums.end(), 0);
return expect_sum - actual_sum;
}
};
复杂度
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
[hot] 287. 寻找重复数
题目
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。
示例 1:
输入:nums = [1,3,4,2,2]
输出:2
示例 2:
输入:nums = [3,1,3,4,2]
输出:3
题解
直接复刻<找出数组中重复的数字>的做法,示例代码如下:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
for (int i = 0; i < nums.size(); ++i) {
nums[i] -= 1;
}
int i = 0;
while (i < nums.size()) {
if (nums[i] == i) {
++i;
continue;
}
if (nums[i] == nums[nums[i]]) {
return nums[i] + 1;
}
swap(nums[i], nums[nums[i]]);
}
return -1;
}
};
复杂度
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
题解2
快慢指针解决问题,示例代码如下所示,思路 比较难理解。
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int slow = 0, fast = 0;
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast);
slow = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
};
复杂度
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
[hot] 295. 数据流的中位数
题目
中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值例如
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
题解
定义小根堆和大根堆,满足如下两个条件:
- 小根堆存放数组较大一半元素,大根堆存放数组较小一半元素,即小根堆中每个元素都比大根堆大。
- 数组元素个数为奇数时,大根堆元素个数比小根堆多1;数组元素个数为偶数时,两个堆的元素个数相同。
具体实现逻辑的代码实现如下所示:
// #include <iostream>
// #include <queue>
// #include <algorithm>
// using namespace std;
class MedianFinder {
public:
MedianFinder() {}
void addNum(int num) {
// 如果min_q和max_q size相同,则一定先push min_q,然后再push max_q
// 这样才能保证max_q的size永远都会小于等于min_q
if (min_q.size() == max_q.size()) {
min_q.push(num);
max_q.push(min_q.top());
min_q.pop();
} else {
max_q.push(num);
min_q.push(max_q.top());
max_q.pop();
}
}
double findMedian() {
if (max_q.size() == min_q.size()) {
// 一定注意返回值类型
return double(max_q.top() + min_q.top()) / 2;
}
return max_q.top();
}
private:
priority_queue<int> max_q; // 大根堆
priority_queue<int, vector<int>, greater<int>> min_q; // 小根堆
};
// 只要能保证max_q和min_q的大小差一,先push谁都无所谓
class MedianFinder {
public:
MedianFinder() {}
void addNum(int num) {
if (min_q.size() == max_q.size()) {
max_q.push(num);
min_q.push(max_q.top());
max_q.pop();
} else {
min_q.push(num);
max_q.push(min_q.top());
min_q.pop();
}
}
double findMedian() {
if (min_q.size() == max_q.size()) {
return double(min_q.top() + max_q.top()) / 2.0;
}
return double(min_q.top());
}
private:
priority_queue<int> max_q;
priority_queue<int, vector<int>, greater<int>> min_q;
};
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder* obj = new MedianFinder();
* obj->addNum(num);
* double param_2 = obj->findMedian();
*/
复杂度
时间复杂度:
O
(
l
o
g
n
)
O(logn)
O(logn),维护堆的时间复杂度
空间复杂度:
O
(
n
)
O(n)
O(n),优先级队列
题解2
有序集合解决问题,示例代码如下所示
#include <set>
class MedianFinder {
multiset<int> nums;
multiset<int>::iterator left, right;
public:
MedianFinder() : left(nums.end()), right(nums.end()) {}
void addNum(int num) {
const size_t n = nums.size();
nums.insert(num);
if (!n) {
left = right = nums.begin();
} else if (n & 1) {
if (num < *left) {
left--;
} else {
right++;
}
} else {
if (num > *left && num < *right) {
left++;
right--;
} else {
if (num >= *right) {
left++;
} else {
right--;
}
left = right;
}
}
}
double findMedian() {
return (*left + *right) / 2.0;
}
};
复杂度
时间复杂度:
O
(
l
o
g
n
)
O(logn)
O(logn),维护堆的时间复杂度
空间复杂度:
O
(
n
)
O(n)
O(n),有序集合
[hot] 338. 比特位计数
题目
给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。
示例 1:
输入:n = 2
输出:[0,1,1]
解释:
0 --> 0
1 --> 1
2 --> 10
示例 2:
输入:n = 5
输出:[0,1,1,2,1,2]
解释:
0 --> 0
1 --> 1
2 --> 10
3 --> 11
4 --> 100
5 --> 101
题解
x=x & (x−1) 循环调用,获取二进制中1的位数,示例代码如下所示:
class Solution {
public:
int core(int x) {
int res = 0;
while (x > 0) {
x &= x - 1;
++res;
}
return res;
}
vector<int> countBits(int n) {
vector<int> res;
for (int i = 0; i <= n; ++i) {
res.emplace_back(core(i));
}
return res;
}
};
复杂度
时间复杂度:
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn),每个数字二进制的1的个数不超过logn
空间复杂度:
O
(
1
)
O(1)
O(1)
题解2
直接动态规划解决问题,示例代码如下所示:
class Solution {
public:
vector<int> countBits(int n) {
vector<int> bits(n + 1);
for (int i = 1; i <= n; i++) {
bits[i] = bits[i & (i - 1)] + 1;
}
return bits;
}
};
复杂度
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
349. 两个数组的交集
题目
给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的
题解
哈希表和排序两种策略,示例代码如下:
// 时间复杂度为O(m+n),空间复杂度为O(m+n)
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> set1, set2;
for (auto& num : nums1) {
set1.insert(num);
}
for (auto& num : nums2) {
set2.insert(num);
}
return getIntersection(set1, set2);
}
vector<int> getIntersection(unordered_set<int>& set1, unordered_set<int>& set2) {
if (set1.size() > set2.size()) {
return getIntersection(set2, set1);
}
vector<int> intersection;
for (auto& num : set1) {
if (set2.count(num)) {
intersection.push_back(num);
}
}
return intersection;
}
};
// 时间复杂度为O(mlogm+nlogn),空间复杂度取决于排序引入的空间复杂度
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
sort(nums1.begin(), nums1.end());
sort(nums2.begin(), nums2.end());
int length1 = nums1.size(), length2 = nums2.size();
int index1 = 0, index2 = 0;
vector<int> intersection;
while (index1 < length1 && index2 < length2) {
int num1 = nums1[index1], num2 = nums2[index2];
if (num1 == num2) {
// 保证加入元素的唯一性
if (!intersection.size() || num1 != intersection.back()) {
intersection.push_back(num1);
}
index1++;
index2++;
} else if (num1 < num2) {
index1++;
} else {
index2++;
}
}
return intersection;
}
};
350. 两个数组的交集 II
题目
给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
提示:
1 <= nums1.length, nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 1000
题解
哈希表解决问题,而且交集中重复元素也会写入结果中,图示如下,
实例代码如下所示:
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
if (nums1.size() > nums2.size()) {
return intersect(nums2, nums1);
}
unordered_map<int, int> m;
vector<int> res;
for (auto& elem: nums1) {
++m[elem];
}
for (auto& elem: nums2) {
if (m.count(elem) > 0) {
res.emplace_back(elem);
--m[elem];
if (m[elem] == 0) {
m.erase(elem);
}
}
}
return res;
}
};
复杂度
时间复杂度:
O
(
m
+
n
)
O(m + n)
O(m+n),遍历nums1和nums2的时间复杂度
空间复杂度:
O
(
m
i
n
(
m
,
n
)
)
O(min(m, n))
O(min(m,n)),哈希表的空间大小
题解2
不借助哈希表,直接排序,示例代码如下所示:
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
sort(nums1.begin(), nums1.end());
sort(nums2.begin(), nums2.end());
int length1 = nums1.size(), length2 = nums2.size();
vector<int> intersection;
int index1 = 0, index2 = 0;
while (index1 < length1 && index2 < length2) {
if (nums1[index1] < nums2[index2]) {
index1++;
} else if (nums1[index1] > nums2[index2]) {
index2++;
} else {
intersection.push_back(nums1[index1]);
index1++;
index2++;
}
}
return intersection;
}
};
复杂度
时间复杂度:
O
(
m
l
o
g
m
+
n
l
o
g
n
)
O(mlogm + nlogn)
O(mlogm+nlogn),排序消耗的时间
空间复杂度:
O
(
1
)
)
O(1))
O(1)),如果不算输出结果的空间
[hot] 448. 找到所有数组中消失的数字
题目
给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。找到所有在 [1, n] 范围之间没有出现在数组中的数字。
示例:
输入: [4,3,2,7,8,2,3,1]
输出: [5,6]
题解
鸽笼原理,和<41. 缺失的第一个正数>的原理一样,遍历nums元素nums[i],把位置为nums[i]的元素进行正负翻转,现象为缺失数字位置的元素没有机会被翻转。再次遍历nums,找到那些没有被翻转的元素再+1即为最终结果。示例代码如下所示:
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
int length = nums.size();
for (int i = 0; i < length; ++i) {
int index = abs(nums[i]) - 1;
nums[index] = -abs(nums[index]);
}
vector<int> res;
for (int i = 0; i < length; ++i) {
if (nums[i] > 0) {
res.emplace_back(i + 1);
}
}
return res;
}
};
复杂度
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
509. 斐波那契数
题目
斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给你 n ,请计算 F(n) 。
示例 1:
输入:2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1
题解
按照斐波那契数列的推导过程,进行结果的计算,示例代码如下:
class Solution {
public:
int fib(int n) {
if (n < 2) {
return n;
}
int n1 = 0;
int n2 = 1;
int res = 1;
for (int i = 2; i <= n; ++i) {
res = n1 + n2;
n1 = n2;
n2 = res;
}
return res;
}
};
复杂度
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
题解2
矩阵快速幂方法,具体解释可以见这里
示例代码如下所示,
class Solution {
public:
int fib(int n) {
if (n < 2) {
return n;
}
vector<vector<int>> q{{1, 1}, {1, 0}};
vector<vector<int>> res = matrix_pow(q, n - 1);
return res[0][0];
}
vector<vector<int>> matrix_pow(vector<vector<int>>& a, int n) {
vector<vector<int>> ret{{1, 0}, {0, 1}};
while (n > 0) {
if (n & 1) {
ret = matrix_multiply(ret, a);
}
n >>= 1;
a = matrix_multiply(a, a);
}
return ret;
}
vector<vector<int>> matrix_multiply(vector<vector<int>>& a, vector<vector<int>>& b) {
vector<vector<int>> c{{0, 0}, {0, 0}};
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
c[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j];
}
}
return c;
}
};
复杂度
时间复杂度:
O
(
l
o
g
n
)
O(logn)
O(logn)
空间复杂度:
O
(
1
)
O(1)
O(1)
665. 非递减数列
题目
给你一个长度为 n 的整数数组,请你判断在 最多 改变 1 个元素的情况下,该数组能否变成一个非递减数列。我们是这样定义一个非递减数列的: 对于数组中任意的 i (0 <= i <= n-2),总满足 nums[i] <= nums[i + 1]。
示例 1:
输入: nums = [4,2,3]
输出: true
解释: 你可以通过把第一个4变成1来使得它成为一个非递减数列。
示例 2:
输入: nums = [4,2,1]
输出: false
解释: 你不能在只改变一个元素的情况下将其变为非递减数列。
题解
一图抵千言,两种情况如下所示:
示例代码如下所示:
class Solution {
public:
bool checkPossibility(vector<int>& nums) {
int length = nums.size();
int minus_count = 0;
for (int i = 1; i < length; ++i) {
if (nums[i] < nums[i - 1]) {
// 当nums[i] == nums[i - 2]时,要把nums[i - 1]打压下去
if (i == 1 || nums[i] >= nums[i - 2]) {
nums[i - 1] = nums[i];
} else {
nums[i] = nums[i - 1];
}
++minus_count;
}
if (minus_count > 1) {
return false;
}
}
return true;
}
};
复杂度
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
697. 数组的度
题目
给定一个非空且只包含非负数的整数数组 nums,数组的度的定义是指数组里任一元素出现频数的最大值。你的任务是在 nums 中找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。
示例 1:
输入:[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.
示例 2:
输入:[1,2,2,3,1,4,2]
输出:6
题解
class Solution {
public:
int findShortestSubArray(vector<int>& nums) {
unordered_map<int, vector<int>> m;
for (int i = 0; i < nums.size(); ++i) {
int elem = nums[i];
if (m.count(elem) > 0) {
m[elem][0]++;
m[elem][2] = i;
} else {
m[elem] = {1, i, i};
}
}
int max_num = -1;
int min_len = 0;
for (auto& elem: m) {
auto& vec = elem.second;
if (vec[0] > max_num) {
max_num = vec[0];
min_len = vec[2] - vec[1] + 1;
} else if (vec[0] == max_num) {
min_len = min(vec[2] - vec[1] + 1, min_len);
}
}
return min_len;
}
};
复杂度
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
724. 寻找数组的中心下标
题目
给你一个整数数组 nums,请编写一个能够返回数组 “中心下标” 的方法。数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。如果数组不存在中心下标,返回 -1 。如果数组有多个中心下标,应该返回最靠近左边的那一个。
注意:中心下标可能出现在数组的两端。
题解
边遍历边记录元素左侧元素之和,示例代码如下所示:
class Solution {
public:
int pivotIndex(vector<int>& nums) {
int total = accumulate(nums.begin(), nums.end(), 0);
int sum = 0;
for (int i = 0; i < (int)nums.size(); ++i) {
if (2 * sum + nums[i] == total) {
return i;
}
sum += nums[i];
}
return -1;
}
};
复杂度
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
746. 使用最小花费爬楼梯
题目
数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值 cost[i](下标从 0 开始)。每当你爬上一个阶梯你都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。请你找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初始阶梯。
示例 1:
输入:cost = [10, 15, 20]
输出:15
解释:最低花费是从 cost[1] 开始,然后走两步即可到阶梯顶,一共花费 15 。
示例 2:
输入:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
输出:6
解释:最低花费方式是从 cost[0] 开始,逐个经过那些 1 ,跳过 cost[3] ,一共花费 6 。
题解
类似斐波那契数列,用prev、cur、next三个数字来模拟动态规划的更新过程,示例代码如下所示:
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
int prev = 0; // i - 2阶台阶的最小体力值
int cur = 0; // i - 1阶台阶的最小体力值
// 注意for循环的终止条件是i <= cost.size(),因为当i == cost.size()时,
// next(or cur)才是最终目标值
for (int i = 2; i <= cost.size(); ++i) {
int next = min(prev + cost[i - 2], cur + cost[i - 1]);
prev = cur;
cur = next;
}
return cur;
}
};
复杂度
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
1365. 有多少小于当前数字的数字
题目
给你一个数组 nums,对于其中每个元素 nums[i],请你统计数组中比它小的所有数字的数目。换而言之,对于每个 nums[i] 你必须计算出有效的 j 的数量,其中 j 满足 j != i 且 nums[j] < nums[i] 。以数组形式返回答案。
提示:
2 <= nums.length <= 500
0 <= nums[i] <= 100
示例 1:
输入:nums = [8,1,2,2,3]
输出:[4,0,1,1,3]
解释:
对于 nums[0]=8 存在四个比它小的数字:(1,2,2 和 3)。
对于 nums[1]=1 不存在比它小的数字。
对于 nums[2]=2 存在一个比它小的数字:(1)。
对于 nums[3]=2 存在一个比它小的数字:(1)。
对于 nums[4]=3 存在三个比它小的数字:(1,2 和 2)。
示例 2:
输入:nums = [6,5,4,8]
输出:[2,1,0,3]
示例 3:
输入:nums = [7,7,7,7]
输出:[0,0,0,0]
题解
由于限制了nums[i]的取值范围,因而可以通过计数排序的方式进行求解,示例代码如下所示:
class Solution {
public:
vector<int> smallerNumbersThanCurrent(vector<int>& nums) {
vector<int> cnt(101, 0);
for (const auto& elem: nums) {
++cnt[elem];
}
// cnt[i]记录的是,数组nums中<=i的元素个数
for (int i = 1; i <= 100; ++i) {
cnt[i] += cnt[i - 1];
}
vector<int> ret;
for (int i = 0; i < (int)nums.size(); ++i) {
// cnt[nums[i] - 1]才是数组中小于nums[i]的元素个数
int freq = nums[i] == 0 ? 0 : cnt[nums[i] - 1];
ret.emplace_back(freq);
}
return ret;
}
};
复杂度
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
剑指offer
3. 找出数组中重复的数字
题目
在一个长度为n的数组里的所有数字都在0到n-1的范围内。数组中某些数字是重复的,但不知道有几个数字重复了, 也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2, 3, 1, 0, 2, 5, 3},那么对应的输出是重复的数字2或者3。
题解
示例代码如下:
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
int i = 0;
while (i < nums.size()) {
if (nums[i] == i) {
++i;
continue;
}
if (nums[i] == nums[nums[i]]) {
return nums[i];
}
swap(nums[i], nums[nums[i]]);
}
return -1;
}
};
复杂度
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
[hot] 39. 数组中出现次数超过一半的数字
题目
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2
题解
类似位运算处理,定义times来记录遍历过程中当前遍历元素在之前的遍历过程中出现的次数,初始值为1,定义result为最终结果,在遍历过程中其永远都等于出现次数最多的元素。遍历过程中如果result等于当前遍历元素则++times,否则–times,如果times变为0,则将当前遍历元素赋值给result,times重新赋值为1,遍历结束后返回result即可。示例代码如下:
class Solution {
public:
int majorityElement(vector<int>& nums) {
int result = nums[0];
int times = 1;
for (int i = 1; i < nums.size(); ++i) {
if (times == 0) {
times = 1;
result = nums[i];
} else if (result == nums[i]) {
++times;
} else {
--times;
}
}
return times > 0 ? result : -1;
}
};
复杂度
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
56. 数组中数字出现的次数
题目
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
题解
将数组中所有的元素异或,即可得到只出现一次的两个元素a、b的异或值。可以找到a和b异或值二进制表达值的任意一位为1的位置,数组中这一位为1分为一组,为0分为一组,两组数据分别异或即可得到a和b。示例代码如下所示:
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
int ret = 0;
for (const auto& elem: nums) {
ret ^= elem;
}
int div = 1; // 注意标志位的初始化
while ((div & ret) == 0) { // & 能够找到只出现一次的两个数二进制表示的第一个不相同位
div <<= 1;
}
int a = 0;
int b = 0;
for (const auto& elem: nums) {
if (elem & div) { // & 能将a和b区分开来,原因同上
a ^= elem;
} else {
b ^= elem;
}
}
return vector<int>{a, b};
}
};
复杂度
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)