算法与基础数据结构Leetcode部分题目解析
文章目录
1. 剑指Offer11: 旋转数组的最小元素
题目链接
题目解析:升序数组旋转后,分为前后两部分,前面一部分数据偏大,后面一部分数据一定偏小,可以以数组的最后一个数字 nums[r] 作为基点:
当数组中的某个数 nums[mid] 比 nums[r] 大时,mid 一定落在前半部分,最小值一定出现在mid的后边;
当 nums[mid] 比 nums[r] 小时,mid一定落在后半部分,最小值一定出现在mid的前面;
以上正是 二分算法 的解题思路。
比较难处理的是 nums[mid] == nums[r],若出现这种情况,可能是 [2,2,2,2,2,1,2] 这种情况,mid 落在前半部分,也可能是 [2,1,2,2,2,2,2,2,2] 这种情况,mid 落在后半部分。
针对这种情况,可以在遍历数组时首先做一个前处理,比较数组最左边和最右边的元素,若二者相等,则不断移动最左边的位置,直到二者不相等。此时nums[mid] == nums[r] 一定说明mid 是出现在后半部分,最小值在mid 的前面。
class Solution {
public:
int minArray(vector<int>& numbers) {
int l = 0, r = numbers.size() - 1;
//最左边的值只要等于最右边的值则不断将最左边的位置向右移动
while (l < numbers.size() && numbers[l] == numbers[r]) l++;
//移动到最后左边的位置索引越界,则说明数组的元素全相等,
//直接返回最右边的值
if (l == numbers.size()) return numbers[r];
//二分法查找最小值
while (l < r) {
int mid = (l + r) / 2;
if (numbers[mid] <= numbers[r]) {
r = mid;
}else if (numbers[mid] > numbers[r]) l = mid + 1;
}
return numbers[l];
}
};
2. Leetcode 658: 找到最接近的k个元素
题目链接
解题思路:可以将找到k个最接近的元素转化为要 删掉n-k个元素 (n为数组元素的数量)。
从而可以从数组的两端开始,比较左右两数和x的距离,删去较大的那个数(即该数对应的索引向内移动一位)。 直到删去n - k个数位置。
class Solution {
public:
vector<int> findClosestElements(vector<int>& arr, int k, int x) {
int i = 0, j = arr.size() - 1;
int rN = arr.size() - k;
while (rN > 0) {
if (abs(arr[i] - x) > abs(arr[j] - x)) {
//x离左边更远,所以将最左边的索引右移
i++;
}else {
//否则将最右边的索引左移
j--;
}
rN--;
}
//删除完数字,获得新的索引后给结果数组赋值
vector<int> res;
for (int l = i; l <= j; l++) {
res.push_back(arr[l]);
}
return res;
}
};
3. Leetcode 690: 员工的重要性
题目解析:深搜和广搜的模板题。用深搜时,根据输入员工,依次将其重要性加上其下属的重要性函数返回值。用广搜时,对其下属依次入队,每次出队时计算重要性。
注意每个员工可转化为hash_map存储,这样可以根据id快速获取到员工信息。
深搜代码演示:
/*
// Definition for Employee.
class Employee {
public:
int id;
int importance;
vector<int> subordinates;
};
*/
class Solution {
public:
unordered_map<int, Employee> m;
int getImportance(vector<Employee*> employees, int id) {
//为根据id快速找到对应的属性,所以用一个哈希表存储。
for (auto x: employees) m[x->id] = *x;
return getValue(id);
}
int getValue(int id) {
//给定id,递归寻找id和其下属的重要性
int ans = m[id].importance;
for (auto x: m[id].subordinates) {
ans += getValue(x);
}
return ans;
}
};
广搜代码演示:
/*
// Definition for Employee.
class Employee {
public:
int id;
int importance;
vector<int> subordinates;
};
*/
class Solution {
public:
unordered_map<int, Employee> m;
int getImportance(vector<Employee*> employees, int id) {
//广搜队列,存储员工信息
queue<Employee> que;
//为根据id快速找到对应的属性,所以用一个哈希表存储。
for (auto x: employees) m[x->id] = *x;
//初始id先入队
que.push(m[id]);
int ans = 0;
while (!que.empty()) {
//不断寻找队首元素的下属,将其入队
Employee temp = que.front();
que.pop();
ans += temp.importance;
for (auto x: temp.subordinates) que.push(m[x]);
}
return ans;
}
};
4. Leetcode 17: 电话号码的字母组合
题目链接
解题思路:每一个输入数字,都相当于把对应字母都尝试加到答案中,相当于N叉树的前序遍历。所以用深搜回溯的方法。
代码演示:
class Solution {
public:
vector<string> res;
//前两个随意赋值,重点是2-9的赋值
vector<string> str_map = {" ", "*", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
vector<string> letterCombinations(string digits) {
if (digits.size() == 0) return res;
string buff = "";
dfs(digits, buff, 0);
return res;
}
void dfs(string digits, string buff, int ind) {
if (ind == digits.size()) {
//遍历到了最后一个数字,将buff追加到结果中
res.push_back(buff);
return;
}
//digit表示ind索引对应的数字。
int digit = digits[ind] - '0';
//将digit对应的所有字母依次加到结果中,然后回溯重新递归
for (int i = 0; i < str_map[digit].size(); i++) {
buff += str_map[digit][i];
dfs(digits, buff, ind + 1);
buff.pop_back();
}
}
};
5. Leetcode 279: 完全平方数
题目链接
题目解析:可以转化为一个N叉树的层级遍历问题,第一层遍历1,4,9,…, 第二层对第一层遍历的每个数字基础上再增加1,4,9,…, 看哪一层最先遍历到给定数字N,则输出其层数即可。
所以可以用队列实现的广搜方法。队列存储数字和与对应的层数。
class Solution {
public:
struct Node {
//存储当前完全平方数的和x,与层数step
int x, step;
};
int numSquares(int n) {
unordered_set<int> s; //去重
queue<Node> que;
que.push((Node){0, 0});
int level = 0, size = 0;
//以下为队列的一般写法
while (!que.empty()) {
Node temp = que.front();
que.pop();
for (int i = 1; i <= n; i++) {
int newt = temp.x + i * i;
if (newt > n) break;
if (s.find(newt) != s.end()) continue;
if (newt == n) {
return temp.step + 1;
}
que.push((Node){newt, temp.step + 1});
s.insert(newt);
}
}
return 0;
}
};
6. Leetcode 111: 二叉树的最小深度
题目链接
题目分析:可以用深搜,也可用广搜。用深搜时,首先判断该节点是NULL节点或者叶子节点的情况,然后分别求左右节点的深度(前提条件是左右节点不能为NULL),对左右节点的深度取最小值然后加一记为当前节点的最小深度。
用广搜时,用队列存储节点和对应的深度(struct),然后层序遍历二叉树,遍历到第一个叶子节点即可输出深度并返回即可。
深搜的代码演示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int minDepth(TreeNode* root) {
if (!root) return 0;
if ((!root->left) && (!root->right)) return 1;
int l = 100000, r = 100000;
if (root->left)
l = minDepth(root->left);
if (root->right)
r = minDepth(root->right);
return min(l, r) + 1;
}
};
7. Leetcode 1306: 跳跃游戏|||
题目链接
题目解析:状态转化问题,故抽象为搜索问题,可以用深搜,也可以用广搜。
用深搜时,递归输入当前数组arr,visited去重数组和待输入下标index, 每次递归index-arr[index]和index+arr[index]。
用广搜时,队列存储当前遍历到的位置index, 同样需visited 去重。
深搜的代码演示:
class Solution {
public:
bool canReach(vector<int>& arr, int start) {
int n = arr.size();
vector<int> visited(n, 0);
visited[start] = 1;
return dfs(arr, visited, start);
}
bool dfs(vector<int> &arr, vector<int> &visited, int index) {
if (index < 0 || index >= arr.size()) return false;
if (arr[index] == 0) return true;
int left = index - arr[index];
int right = index + arr[index];
bool l = false, r = false;
if (left >= 0 && left < arr.size() && visited[left] == 0) {
visited[left] = 1;
l = dfs(arr, visited, left);
}
if (right >= 0 && right < arr.size() && visited[right] == 0) {
visited[right] = 1;
r = dfs(arr, visited, right);
}
return l || r;
}
};
8. Leetcode 310: 最小高度树
题目链接
题目解析:可以把题目中的树看成一个放射状的图,最外层的节点度为1,向里依次增加。从而可以由外向内 层序遍历这个图,遍历完一圈时向内遍历下一圈,直到遍历到的最后一圈即可组成所有要求的根节点。
class Solution {
public:
vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
if (n == 1) return vector<int>(1,0);
vector<int> degree(n, 0);
vector<vector<int>> map(n);
for (auto x: edges) {
//每个节点对应的度
degree[x[0]]++;
degree[x[1]]++;
//每个节点都和哪些节点相连
map[x[0]].push_back(x[1]);
map[x[1]].push_back(x[0]);
}
queue<int> que;
//先将度为1的节点都放入队列中
for (int i = 0; i < n; i++){
if (degree[i] == 1) {
que.push(i);
}
}
//结果数组
vector<int> res;
while (!que.empty()){
//该层有size 个节点,要将其一次性都pop出来
int size = que.size();
//pop前先清空结果数组,相当于每遍历一层刷新一次结果
res.resize(0);
for (int i = 0; i < size; i++) {
int x = que.front();
// printf("size: %d, x : %d\n", size, x);
que.pop();
//该层每一个pop出来的元素都可以组成根节点
res.push_back(x);
for (auto neighber : map[x]) {
//要在途中删除x这个节点,其相邻节点度要随之变化
degree[neighber]--;
//一旦出现了新的叶子节点则将其加到队列中
if (degree[neighber] == 1) {
que.push(neighber);
}
}
}
}
return res;
}
};
9. Leetcode 575: 分糖果
题目链接
解题思路:可以用一个集合存储糖果类型,当集合的数量不小于n/2时,最多能吃到n/2个糖果类型,否则只能吃到集合数量的糖果类型。
class Solution {
public:
int distributeCandies(vector<int>& candyType) {
unordered_set <int> set;
int n = candyType.size();
//所有类型都存到集合中
for (auto x: candyType) set.insert(x);
//集合数量不小于n/2
if (set.size() >= n / 2) return n/2;
return set.size();
}
};
10. Leetcode 1487: 保证文件名唯一
题目链接
题目解析:从"唯一"可以想到哈希表,文件名可能出现多次,故哈希表的key可以为文件名,value设为该文件名出现的次数。
遍历输入文件名name时,若其在哈希表中不存在,则直接加到结果中,同时更新哈希表的name值;
若其在哈希表中存在,获得其在哈希表中对应的value值count, 此时循环判断name + “(” + count + ")"是否在哈希表中存在,只要存在则count++继续判断。
走出上述循环判断后,name + “(” + count + ")在哈希表中必不存在,将其加到结果数组中,同时更新哈希表key为name 的值和key为name + “(” + count + ")的值。
class Solution {
public:
string get_int_name(string name, int count) {
//字符串增加后缀数字函数
return name + "(" + to_string(count) + ")";
}
vector<string> getFolderNames(vector<string>& names) {
int n = names.size();
unordered_map<string, int> map; //哈希表,存储name和对应的count
vector<string> res(n); //结果数组
for (int i = 0; i < n; i++) {
string name = names[i];
if (map.find(name) == map.end()) { //name在哈希表中不存在
map[name] = 1;
res[i] = name;
}else {
//那么在哈希表中存在,首先获取出现次数count
int count = map[name];
//循环判断name + "(" + count + ")"是否出现过
while (map.find(get_int_name(name, count)) != map.end()) {
count++;
}
//循环判断完后既要更新name的count,
//还要更新name + "(" + count + ")"对应的count
map[name] = count + 1;
map[get_int_name(name, count)] = 1;
//更新结果数组
res[i] = get_int_name(name, count);
}
}
return res;
}
};