刷题记录(力扣热题100)
1.两数之和
https://leetcode.cn/problems/two-sum/description/?envType=study-plan-v2&envId=top-100-liked
题目描述
给定一个动态数组和一个目标值,让你求出在这个动态数组中哪两个数字的和为目标值,返回这两个数字的下标
暴力法
可以直接两重循环直接遍历整个数组,外层循环先选定一个数,内层循环从这个数字的下一个开始遍历,寻找两个数字之和为target,这样时间复杂度为o(n * n)
哈希办法
我们每次只是需要找到数组是否存在当前数字 – num
的互补数(就是二者之和为target),所以我们只需要一个集合,在遍历整个集合时同时在集合里面寻找是否存在当前的互补数,如果不存在就需要将当前数字放入集合,再继续遍历(先寻找,后插入可以避免找出两个下标相同的数字的意外情况的出现)。同时,最后要输出下标,这样放进去的元素要有两个值,所以选择unordered_map容器作为存储数据结构
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> mp;
for (int i = 0; i < nums.size(); i ++)
{
auto it = mp.find(target - nums[i]);//使用find方法可以在unordered_map中查找一个元素,如果找到了,则返回指向该元素的迭代器,否则返回unordered_map::end()
if (it != mp.end())
{
return {it->second, i};
}
mp[nums[i]] = i;
}
return {};//不存在时返回为空
}
};
2.字母异位词分组
https://leetcode.cn/problems/group-anagrams/description/?envType=study-plan-v2&envId=top-100-liked
题目描述
给你一个动态数组,数组元素是一些单词,让你将其中一些单词的组成字母相同(包括个数)的分成一组,然后按组返回。
思路
因为构成单词的字母相同,那么将单词按同一方法排序后产生的序列一定是相同的,那么再进行哈希也一定是相同的,所以我们此时已经确定了第一维的参数,只需将第二维设置为动态数组,然后根据哈希值进行插入即可,最后遍历哈希表将其整合为一个动态数组返回即可
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string, vector<string>> mp;
for (string& str : strs)
{
string key = str;
sort(key.begin(), key.end());
mp[key].push_back(str);
}
vector<vector<string>> ans;
for (auto it = mp.begin(); it != mp.end(); it ++)
{
ans.push_back(it->second);
}
return ans;
}
};
3.最长连续序列
https://leetcode.cn/problems/longest-consecutive-sequence/description/?envType=study-plan-v2&envId=top-100-liked
题目描述
给定一个未排序的数组,让你从其中找出一个最长连续数字序列的长度,这些数字在原数组中不必连续
一般方法
我们一般可以想到,先对原数组进行排序,然后在进行遍历一次加上判断即可找出答案,这样时间复杂度就是O(nlogn + n)
哈希方法
因为我们每次只需要查找是否在这个数组中存在和当前数字相邻的的其他的数字,所以排序就有点超过最合适的方法。所以我们要建立一个哈希表,然后每次进行查找,如果不存在比自己小一的,就循环查找是否存在比自己大一的,同时对最大长度进行维护。最后,为了避免重复数字导致的额外开销,我们要构建不存在重复元素的集合
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
unordered_set<int> st;
for (const int& num : nums)
{
st.insert(num);
}
int ans = 0;
for (const int& num : nums)
{
if (!st.count(num - 1))
{
int nowNum = num;
int nowStack = 1;
while (st.count(nowNum + 1))
{
nowNum ++;
nowStack ++;
}
ans = max(ans, nowStack);
}
}
return ans;
}
};
4.移动零
https://leetcode.cn/problems/move-zeroes/description/?envType=study-plan-v2&envId=top-100-liked
题目描述
给定一个数组在不改变其它元素的相对顺序的情况下,将数组中的0移动到末尾
思路
我们可以使用快慢指针,快指针用来遍历数组,慢指针只有每次将不为0的元素复制到前面时才会加一,这样最后当遍历完数组时慢指针还没有到达数组末尾,此时我们加一个循环后面全为0即可。因为前面只复制了不为0的元素,所以缺少的元素全为0。
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int j = 0;
for (int i = 0; i < nums.size(); i ++)
{
if (nums[i] != 0)
{
nums[j ++] = nums[i];
}
}
for (j; j < nums.size(); j ++) nums[j] = 0;
}
};
5.盛水最多的容器
https://leetcode.cn/problems/container-with-most-water/description/?envType=study-plan-v2&envId=top-100-liked
题目描述
给定一个heigh数组,让你选择两条边加上x轴算一下,作为容器可以盛水的最大体积,不可以倾斜
双指针思路
两个指针从两边出发,算一下当前的体积,然后更新较小的那一条边,因为每次更新x轴都会减少,如果更新长边那么体积只会变小,而更新短边才有变大的机会,然后直到两个指针相遇结束,在此期间维护最大面积即可
class Solution {
public:
int maxArea(vector<int>& height) {
int i = 0, j = height.size() - 1;
int ans = 0;
while (i < j)
{
int area = min(height[i], height[j]) * (j - i);
ans = max(ans, area);
if (height[i] <= height[j]) i ++;
else j --;
}
return ans;
}
};
6.三数之和
https://leetcode.cn/problems/3sum/?envType=study-plan-v2&envId=top-100-liked
题目描述
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为 0
且不重复的三元组。
暴力
一个三重循环写上去,然后再对重复的数据去重即可,很复杂
双指针
对数组进行排序,每次选择一个数,从它后面的序列利用双指针进行判断,如果超过0更新大的,小于0更新小的,直到等于0插入结果中去
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size() - 1; i ++)
{
if (i > 0 && nums[i] == nums[i - 1]) continue;//为了防止重复结果,当前数字如果和前面相同就不用再遍历
int k = nums.size() - 1;
int target = -nums[i];
for (int j = i + 1; j < nums.size(); j ++)
{
if (j > i + 1 && nums[j] == nums[j - 1]) continue;//同上
while (j < k && nums[j] + nums[k] > target) k --;//这里如果不写大于回超时
if (j == k) break;
if (nums[j] + nums[k] == target) ans.push_back({nums[i], nums[j], nums[k]});
}
}
return ans;
}
};
7.接雨水
https://leetcode.cn/problems/trapping-rain-water/description/?envType=study-plan-v2&envId=top-100-liked
题目描述
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
单调栈解法
我们维护一个单调栈,按单调减小来排列,如果当前栈中有两个元素,顶端为top
,下一个则是left
。遇到当前元素i
,倘若height[i] > height[top]
我们就找到了一个可以存放水的地方,因为height[left] >= height[top]
恒成立,现在就形成一个凹区域,此时区域的宽度为i - left - 1
,高度为min(height[left], height[i]) - height[top]
,计算一下面积,加到ans
中即可,然后需要维护单调栈,同时不能忘记在栈中插入数据
class Solution {
public:
int trap(vector<int>& height) {
stack<int> sk;
int ans = 0;
int n = height.size();
for (int i = 0; i < n; i ++)
{
while (!sk.empty() && height[i] > height[sk.top()])
{
int top = sk.top();
sk.pop();
if (sk.empty()) break;
int left = sk.top();
int w = i - left - 1;
int h = min(height[i], height[left]) - height[top];
ans += w * h;
}
sk.push(i);
}
return ans;
}
};
8.无重复字符的最长字串
https://leetcode.cn/problems/longest-substring-without-repeating-characters/description/?envType=study-plan-v2&envId=top-100-liked
题目描述
给定一个字符串 s
,请你找出其中不含有重复字符的最长子串的长度。
思路
我们要寻找没有重复字符的子序列,如果利用一个数组来记录已经出现字符的个数就会造成空间的浪费,所以选择unordered_set来记录已经出现的字符,然后每次遇到字符就要在里面查找看是否前面已经出现过,同时计算最长长度
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int n = s.size();
unordered_set<char> st;
int ans = 0, r = 0;
for (int i = 0; i < n; i ++)
{
if (i) st.erase(s[i - 1]);//将前面不在计算区间内的字符清除
while (r < n && !st.count(s[r]))
{
st.insert(s[r]);
r ++;
}
ans = max(ans, r - i);
}
return ans;
}
};
9.找到字符串中所有字母异构词
https://leetcode.cn/problems/find-all-anagrams-in-a-string/?envType=study-plan-v2&envId=top-100-liked
题目描述
给定两个字符串 s
和 p
,找到 s
中所有 p
的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
思路
超时版
我们每次找到和p字符串相同长度的,然后排序后比较查看是否一致
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int> ans;
int n = s.size(), plen = p.size();
sort(p.begin(), p.end());
for (int i = 0; i < n; i ++)
{
if (i + plen > n) break;
string key;
for (int j = 0; j < plen; j ++) key.push_back(s[i + j]);
sort(key.begin(), key.end());
if (key == p) ans.push_back(i);
}
return ans;
}
};
通过版
上面那个是因为排序导致超时,所以我们需要更新一下判断方法,可以使用数组来记录这两个序列中每个字符出现的次数,如果出现的次数相同,即两个数组相同,那么就是异构词。然后我们依次往后遍历,每次去掉上一个字符,加入下一个字符,再次进行判断即可。
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int> ans;
int n = s.size(), plen = p.size();
if (n < plen) return ans;
vector<int> scount(26);
vector<int> pcount(26);
for (int i = 0; i < plen; i ++)
{
++scount[s[i] - 'a'];
++pcount[p[i] - 'a'];
}
if (scount == pcount) ans.push_back(0);
for (int i = 0; i < n - plen; i ++)
{
--scount[s[i] - 'a'];
++scount[s[i + plen] - 'a'];
if (scount == pcount) ans.push_back(i + 1);
}
return ans;
}
};
10.和为k的子数组
https://leetcode.cn/problems/subarray-sum-equals-k/description/?envType=study-plan-v2&envId=top-100-liked
题目描述
给你一个整数数组 nums
和一个整数 k
,请你统计并返回 该数组中和为 k
的子数组的个数 。子数组是数组中元素的连续非空序列。
思路
暴力法
我们可以直接从指定的数组下标开始往后面累加,每次加完都需要判断一下是否为k,且中途不能break(若出现后面序列为-1,1,这样就会少解),然后外层循环从0-n,这样O(n * n)的时间复杂度,不出意外的超时
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int n = nums.size();
int ans = 0;
for (int i = 0; i < n; i ++)
{
int sum = 0;
for(int j = i; j < n; j ++)
{
sum += nums[j];
if (sum == k) ans ++;
}
}
return ans;
}
};
前缀和加哈希优化版
我们需要找的是一个子数组的内部和为k,我们可以使用前缀和来表示从第一个到当前元素的和,那么我们就可以通过相减得出某一个子区间上的和,如果我们就只是这样去算,还是需要遍历前面的前缀和,时间复杂度没有变化。
接着我们知道的是sum[j] - sum[i - 1]
表示这一区间(i,j)上的和,若是这个和为k,则有sum[j] - sum[i - 1] = k
,即sum[i - 1] = sum[j] - k
,就是说我们只需要统计一下在我们前面出现了多少次sum[j] - k
,这样就是以当前元素为结尾的子区间的个数。
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int n = nums.size();
int sum = 0, ans = 0;
unordered_map<int, int> mp;
mp[0] = 1;//初始化mp,若第一个元素即为k,那么因为mp为空则无法计入其中
for (int i = 0; i < n; i ++)
{
sum += nums[i];
if (mp.find(sum - k) != mp.end()) ans += mp[sum - k];
mp[sum] ++;
}
return ans;
}
};
11.滑动窗口的最大值
https://leetcode.cn/problems/sliding-window-maximum/?envType=study-plan-v2&envId=top-100-liked
题目描述
给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。返回 滑动窗口中的最大值 。
思路
就是给定一个窗口区间,我们需要找到在这个范围内中的最大值,我们可以在窗口区间内利用一个大根堆来维护最大值,每次向右移动的时候,就先判断当前最大值是不是被移出窗口,然后将新添加进来的元素放入大根堆中,依次遍历即可。
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> ans;
priority_queue<pair<int, int> > q;
int n = nums.size();
for (int i = 0; i < k; i ++) q.emplace(nums[i], i);
ans.push_back(q.top().first);
for (int i = k; i < n; i ++)
{
q.emplace(nums[i], i);
while (q.top().second <= i - k) q.pop();
ans.push_back(q.top().first);
}
return ans;
}
};
12.最小覆盖字串
https://leetcode.cn/problems/minimum-window-substring/?envType=study-plan-v2&envId=top-100-liked
题目描述
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
思路
我们利用滑动窗口思想来解决,利用unordered_map来存储信息。首先我们将t中字符全部存入其中。然后先开始让s上的区间右边界往右滑动,每次遇到一个字符都要在map中查找查看是否是t中的字符,如果是就让map中相应字符数量减1,知道map中所有字符数量都不大于0时,说明该区间完美包含了t。此时我们让左边界往右滑动,同时在map中加相关字符的数量,只要map中所有字符依旧不大于0,那么就继续可以往右移动,同时维护区间长度,记录下来最小值,剩下一些细节将在代码中体现。
class Solution {
public:
unordered_map<char, int> mp;
bool check()
{
for (const auto &p : mp)
{
if (p.second > 0)
return false;
}
return true;
}
string minWindow(string s, string t) {
int n = s.size(), m = t.size();
if (n < m) return "";
for (int i = 0; i < m; i ++) ++mp[t[i]];
int l = 0, r = 0, st = -1, len = n + 1;
while (r < n)
{
if (mp.find(s[r]) != mp.end()) --mp[s[r]];
while (check() && l <= r)
{
if (r - l + 1 < len)
{
len = r - l + 1;
st = l;
if (len == m) return s.substr(st, len);
}
if (mp.find(s[l]) != mp.end()) ++mp[s[l]];
l ++;
}
r ++;
}
if (len == n + 1) return "";
else return s.substr(st, len);
}
};
13.最大子数组和
https://leetcode.cn/problems/maximum-subarray/description/?envType=study-plan-v2&envId=top-100-liked
题目描述
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
思路
使用动态规划思想,若想使得子数组和最大,那么我就要计算前面加过来和现在的最大值,也就是说只需要每一部分都是当前可以选择的最大值,那么最后结果一定就是最大的,要记得在计算途中同时维护最大值。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int ans = nums[0], pre = 0, n = nums.size();
for (int i = 0; i < n; i ++)
{
pre = max(pre + nums[i], nums[i]);//计算是否使用前驱数组
ans = max(ans, pre);//维护最大值
}
return ans;
}
};
14.合并区间
https://leetcode.cn/problems/merge-intervals/?envType=study-plan-v2&envId=top-100-liked
题目描述
以数组 intervals
表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi]
。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
思路
因为要合并区间,什么时候区间可以合并,就是当上一个区间的末尾和下一个区间的头有相交的部分,这样两个区间就可以合并。所以,我们可以使用排序算法,按区间左边界进行排序,那么这样可能有相交部分的区间在数组内部就是相邻的,我们只需要每次判断加入答案的最后一个区间和当前遍历区间的关系即可。
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
int n = intervals.size();
if (!n) return {};
sort(intervals.begin(), intervals.end());
vector<vector<int>> ans;
for (int i = 0; i < n; i ++)
{
int l = intervals[i][0], r = intervals[i][1];
if (!ans.size() || ans.back()[1] < l)//当数组为空,或者前一个和后面不相交
ans.push_back({l, r});
else ans.back()[1] = max(ans.back()[1], r);
}
return ans;
}
};
15.轮转数组
https://leetcode.cn/problems/rotate-array/description/?envType=study-plan-v2&envId=top-100-liked
题目描述
给定一个整数数组 nums
,将数组中的元素向右轮转 k
个位置,其中 k
是非负数。
思路
1.多余空间法
我们可以新建一个数组将上面数组走k步然后复制下来
2.翻转法
我们可以发现,每次移动k个元素就相当于数组末尾k个元素移动到前面来,其它元素相对位置不变,那么我们可以使用翻转的方法来解决。先将整个数组翻转,然后再将要移动的k个元素翻转,这样就实现了k个提前,最后将末尾那些翻转回来,就实现了轮转。
class Solution {
public:
void reverse(vector<int>& nums, int st, int ed)
{
while (st < ed)
{
swap(nums[st], nums[ed]);
st ++;
ed --;
}
}
void rotate(vector<int>& nums, int k) {
int n = nums.size();
k %= n;
reverse(nums, 0, n - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, n - 1);
}
};
16.除自身以外数组的乘积
https://leetcode.cn/problems/product-of-array-except-self/description/?envType=study-plan-v2&envId=top-100-liked
题目描述
给你一个整数数组 nums
,返回 数组 answer
,其中 answer[i]
等于 nums
中除 nums[i]
之外其余各元素的乘积 。题目数据 保证 数组 nums
之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。请 **不要使用除法,**且在 O(*n*)
时间复杂度内完成此题。
思路
两个数组
要求的是除自己外所有元素的乘积且不能使用除法,我们可以使用两个数组来存放自己之前所有元素的积和自己之后所有元素的积,然后再遍历一次求的结果
一个答案数组
我们从上面可以发现第一个后缀乘积数组和最后反向遍历时候的顺序是一致的,且最后只是按着顺序使用,我们可以将其优化为最后再求答案时再同时计算后缀乘积
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> ans(n);
ans[0] = 1;
for (int i = 1; i < n; i ++)
{
ans[i] = ans[i - 1] * nums[i - 1];
}
int r = 1;
for (int i = n - 1; i >= 0; i --)
{
ans[i] *= r;
r *= nums[i];
}
return ans;
}
};
17.第一个缺失的正数
https://leetcode.cn/problems/first-missing-positive/description/?envType=study-plan-v2&envId=top-100-liked
题目描述
给你一个未排序的整数数组 nums
,请你找出其中没有出现的最小的正整数。请你实现时间复杂度为 O(n)
并且只使用常数级别额外空间的解决方案。
思路
1.排序
最简单办法就是,将数组排序然后遍历一遍即可发现
2.置换法
对数组进行遍历,每次遇到不在自己位置上的数字就进行置换(自己位置是指,一个正数x应该位于nums[x - 1]的位置上
),如果我们发现nums[x - 1]
位置上的数和目前的数字相同就会导致死循环,所以我们就不再置换
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int n = nums.size();
for (int i = 0; i < n; i ++)
{
while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i])
{
swap(nums[i], nums[nums[i] - 1]);
}
}
for (int i = 0; i < n; i ++)
{
if (nums[i] != i + 1)
{
return i + 1;
}
}
return n + 1;
}
};
18.矩阵置零
题目描述
给定一个 m x n
的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地算法。
思路
1.朴素版
我们可以先申请两个标记数组用来存储要置零的地方,然后再遍历原数组,对新申请的数组进行操作,最好再输出的时候对未标记的位置进行原数组输出即可
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int n = matrix.size();
int m = matrix[0].size();
vector<bool> row(n), col(m);
for (int i = 0; i < n; i ++)
for (int j =0; j < m; j ++)
if (!matrix[i][j])
row[i] = col[j] = true;
for (int i = 0; i < n; i ++)
for (int j = 0; j < m; j ++)
if (row[i] || col[j])
matrix[i][j] = 0;
}
};
2.升级版
我们可以发现只要matrix[i][j] = 0
,那么第i行和第j列都为0,我们那么matrix[i][0] = matrix[0][j] = 0
, 因此我们只需要用第一行第一列做为标记来记录这个数组某一行和某一列是否要置为零,最后我们要提前记录一下第一行和第一列是否包含0,如果包含,那么我们需要将其置为零,放到最后面再处理
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int n = matrix.size();
int m = matrix[0].size();
bool flag1 = false, flag2 = false;
for (int i = 0; i < n; i ++)
if (!matrix[i][0])
flag1 = true;
for (int j = 0; j < m; j ++)
if (!matrix[0][j])
flag2 = true;
for (int i = 1; i < n; i ++)//打标记
for (int j = 1; j < m; j ++)
if (!matrix[i][j])
matrix[i][0] = matrix[0][j] = 0;
for (int i = 1; i < n; i ++)
for (int j = 1; j < m; j ++)
if (!matrix[i][0] || !matrix[0][j])
matrix[i][j] = 0;
if (flag1)
{
for (int i = 0; i < n; i ++)
matrix[i][0] = 0;
}
if (flag2)
{
for (int j = 0; j < m; j ++)
matrix[0][j] = 0;
}
}
};
19.螺旋矩阵
题目描述
给你一个 m
行 n
列的矩阵 matrix
,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
思路
我们可以先定义好按顺时针旋转的变化趋势,设横向为x轴,纵向为y轴,这样按顺时针方向在遇到边界和已经遍历过的转向数组为,x[4] = {1, 0, -1, 0}
,y[4] = {0, 1, 0, -1}
,这样我们在遍历时候记录一下方向,同时判断一下是否遍历过即可
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
int x[4] = {1, 0, -1, 0}, y[4] = {0, 1, 0, -1};
bool st[11][11];
int total = n * m;
vector<int> ans;
int a = -1, b = 0, d = 0;
for (int i = 0; i < total ; i ++)
{
int x1 = a + x[d], y1 = b + y[d];
if (x1 >= 0 && x1 < n && y1 >= 0 && y1 < m && !st[x1][y1])
{
ans.push_back(matrix[y1][x1]);
a = x1, b = y1;
st[x1][y1] = true;
}
else
{
for (int i = 1; i <= 4; i ++)
{
d += i;
d %= 4;
x1 = a + x[d];
y1 = b + y[d];
if (x1 >= 0 && x1 < n && y1 >= 0 && y1 < m && !st[x1][y1])
{
ans.push_back(matrix[y1][x1]);
a = x1, b = y1;
st[x1][y1] = true;
break;
}
}
}
}
return ans;
}
};
20.旋转图像
题目描述
给定一个 n × n 的二维矩阵 matrix
表示一个图像。请你将图像顺时针旋转 90 度。你必须在** 原地** 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
思路
我们可以利用翻转替代旋转,我们只需要,先上下对换一下,再按主对角线换一下就可以了
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size();
for (int i = 0; i < n / 2; i ++)
{
for (int j = 0; j < n; j ++)
swap(matrix[i][j], matrix[n - 1 - i][j]);
}
for (int i = 0; i < n; i ++)
for (int j = i + 1; j < n; j ++)
swap(matrix[i][j], matrix[j][i]);
}
};
21.搜索二维矩阵Ⅱ
题目描述
编写一个高效的算法来搜索 m x n 矩阵 matrix
中的一个目标值 target
。该矩阵具有以下特性:
- 每行的元素从左到右升序排列。
- 每列的元素从上到下升序排列。
思路
1.遍历
我们可以直接遍历整个二维数组
2.二分查找
因为每一行都有序,我们可以对每一行都进行二分查找
3.z
字搜索
因为每一行每一列都按升序排列,所以当我们查找到matrix[i][j]
时候,它左上角都比它小,它右下角都比它大,而左下和右上则不一定和它有明确的大小关系。我们可以从矩阵的右上角进行搜索,在每一步的搜索过程中,如果小了下移,大了左移。
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int m = matrix.size(), n = matrix[0].size();
int x = 0, y = n - 1;
while (x < m && y >= 0)
{
if (matrix[x][y] == target) return true;
else if (matrix[x][y] > target) y --;
else x ++;
}
return false;
}
};
22.二叉树的中序遍历
题目描述
给定一个二叉树的根节点 root
,返回 它的 中序 遍历 。
思路
1.递归
每次遇到一个节点都按照左根右的顺序遍历
2.迭代
利用栈来模拟实现递归的操作
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> ans;
stack<TreeNode*> sk;
while (root != nullptr || !sk.empty())
{
while (root != nullptr)//入栈直到左儿子不存在
{
sk.push(root);
root = root->left;
}
root = sk.top();//此时栈顶就是最左边的,然后出栈
sk.pop();
ans.push_back(root->val);
root = root->right;
}
return ans;
}
};
23.二叉树的最大深度
题目描述
给定一个二叉树 root
,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
思路
1.递归
可以通过递归算法,二叉树的最大深度为其左右子树最大深度再加一
class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == nullptr) return 0;
return max(maxDepth(root->left), maxDepth(root->right)) + 1;
}
};
2.广度优先搜索
我们可以申请一个队列,队列里放的是当前层节点,同时再定义一个变量来维护层数,最后得到的层数即是最大深度
class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == nullptr) return 0;
queue<TreeNode*> q;
int sz;
q.push(root);
int ans = 0;
while (!q.empty())
{
sz = q.size();
while (sz > 0)
{
TreeNode* node = q.front();
q.pop();
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
sz --;
}
ans ++;
}
return ans;
}
};
24.翻转二叉树
题目描述
给你一棵二叉树的根节点 root
,翻转这棵二叉树,并返回其根节点。
思路
我们可以使用递归算法,每次都先翻转它的左右子树,然后再交换左右子树位置
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == nullptr) return root;
TreeNode* left = root->left;
TreeNode* right = root->right;
invertTree(left);
invertTree(right);
root->left = right;
root->right = left;
return root;
}
};
25.对称二叉树
题目描述
给你一个二叉树的根节点 root
, 检查它是否轴对称。
思路
我们可以实现一个判断函数,每次判断递归两个子树是否对称,最后根据结果即可判断
class Solution {
public:
bool check(TreeNode *p, TreeNode *q)
{
if (!q && !p) return true;
if (!q || !p) return false;
return p->val == q->val && check(p->left, q->right) && check(p->right, q->left);
}
bool isSymmetric(TreeNode* root) {
return check(root, root);
}
};
25.二叉树的直径
题目描述
给你一棵二叉树的根节点,返回该树的 直径 。
二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root
。
两节点之间路径的 长度 由它们之间边数表示。
思路
我们可以发现,此直径一定分布在某一个节点的左右子树上,也就是说我们可以递归查询每一个节点的左右子树深度和,同时维护最大直径,最大直径为左右深度之和。
class Solution {
public:
int ans;
int depth(TreeNode * rt)
{
if (rt == nullptr) return 0;
int l = depth(rt->left);
int r = depth(rt->right);
ans = max(ans, l + r);
return max(l, r) + 1;
}
int diameterOfBinaryTree(TreeNode* root) {
ans = 0;
depth(root);
return ans;
}
};
26.二叉树的层序遍历
题目描述
给你二叉树的根节点 root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
思路
使用队列,每次记录当前层有多少个节点,然后遍历这些节点并同时将其左右节点入队
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> q;
vector<vector<int>> ans;
int h = 0, sz;
if (root == nullptr) return ans;
q.push(root);
while (!q.empty())
{
sz = q.size();
ans.push_back({});
while (sz > 0)
{
TreeNode* rt = q.front();
q.pop();
ans[h].push_back(rt->val);
if (rt->left) q.push(rt->left);
if (rt->right) q.push(rt->right);
sz --;
}
h ++;
}
return ans;
}
};
27.将有序数组转化成二叉搜索树
题目描述
给你一个整数数组 nums
,其中元素已经按 升序 排列,请你将其转换为一棵平衡二叉搜索树。
思路
因为数组有序排列,所以我们每次可以选中间的节点作为根节点,然后递归左边和右边建树,最后返回根节点
class Solution {
public:
TreeNode* buildTree(vector<int>& num, int l, int r)
{
if (l > r) return nullptr;
int mid = (l + r) / 2;
TreeNode* root = new TreeNode(num[mid]);
root->left = buildTree(num, l, mid - 1);
root->right = buildTree(num, mid + 1, r);
return root;
}
TreeNode* sortedArrayToBST(vector<int>& nums) {
return buildTree(nums, 0, nums.size() - 1);
}
};