目录
一、DFS 与 BFS
1.1 钥匙和房间
class Solution {
public:
bool canVisitAllRooms(vector<vector<int>>& rooms) {
vector<bool> visited(rooms.size(), false);
dfs(rooms, 0, visited);
for (int i : visited)
if (!i) return false;
return true;
}
void dfs(const vector<vector<int>>& rooms, int key, vector<bool>& visited) {
if (visited[key]) return;
visited[key] = true;
vector<int> keys = rooms[key];
for (int key : keys)
dfs(rooms, key, visited);
}
};
1.2 所有可能的路径
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
path.push_back(0);
dfs(graph, 0);
return res;
}
void dfs(vector<vector<int>>& graph, int x) { // x表示当前遍历的节点
if (x == graph.size() - 1) { // 最后遍历的节点是n-1
res.push_back(path);
return;
}
for (int i = 0; i < graph[x].size(); i ++ ) {
path.push_back(graph[x][i]);
dfs(graph, graph[x][i]);
path.pop_back();
}
}
};
1.3 单词接龙
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
unordered_set<string> wordSet(wordList.begin(), wordList.end());
if (wordSet.find(endWord) == wordSet.end()) return 0;
unordered_map<string, int> visited; // word,查询到该word的路经长度
visited.insert({beginWord, 1});
queue<string> que; que.push(beginWord);
while (!que.empty()) {
string word = que.front(); que.pop();
int path = visited[word];
for (int i = 0; i < word.size(); i ++ ) {
string newWord = word;
for (int j = 0; j < 26; j ++ ) {
newWord[i] = j + 'a';
if (newWord == endWord) return path + 1;
if (wordSet.find(newWord) != wordSet.end() && visited.find(newWord) == visited.end()) {
visited.insert({newWord, path + 1});
que.push(newWord);
}
}
}
}
return 0;
}
};
二、并查集
2.1 冗余连接
class Solution {
public:
int n = 1005;
int father[1005];
int find(int x) {
return x == father[x] ? x : father[x] = find(father[x]);
}
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
for (int i = 0; i < n; i ++ ) father[i] = i;
for (int i = 0; i < edges.size(); i ++ ) {
int pa = find(edges[i][0]), pb = find(edges[i][1]);
if (pa == pb) return edges[i];
else father[pb] = pa;
}
return {};
}
};
2.2 冗余连接 Ⅱ
这个题目是有向图,复杂了一些,参考题解
class Solution {
public:
static const int N = 1010;
int father[N];
int n; // 边的数目
int find(int u) {
return u == father[u] ? u : father[u] = find(father[u]);
}
// 在有向图中找到删除的那条边,使其变成树
vector<int> getRemoveEdge(const vector<vector<int>>& edges) {
for (int i = 0; i <= n; i ++ ) father[i] = i;
for (int i = 0; i < n; i ++ ) {
int pa = find(edges[i][0]), pb = find(edges[i][1]);
if (pa == pb) return edges[i];
father[pb] = pa;
}
return {};
}
// 删除一条边之后是不是树
bool isTreeAfterRemoveEdge(const vector<vector<int>>& edges, int deleteEdge) {
for (int i = 0; i <= n; i ++ ) father[i] = i;
for (int i = 0; i < n; i ++ ) {
if (i == deleteEdge) continue;
int pa = find(edges[i][0]), pb = find(edges[i][1]);
if (pa == pb) return false;
father[pb] = pa;
}
return true;
}
vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
int inDegree[N] = {0}; // 节点入度
n = edges.size();
for (int i = 0; i < n; i ++ )
inDegree[edges[i][1]] ++ ;
vector<int> vec; // 记录入度为2的边
// 找入度为2的节点所对应的边,注意要倒序,因为优先返回最后出现在二维数组中的答案
for (int i = n - 1; i >= 0; i -- )
if (inDegree[edges[i][1]] == 2) vec.push_back(i);
// 处理图中情况1 和 情况2
// 如果有入度为2的节点,那么一定是两条边里删一个,看删哪个可以构成树
if (vec.size() > 0) {
if (isTreeAfterRemoveEdge(edges, vec[0])) return edges[vec[0]];
else return edges[vec[1]];
}
// 处理图中情况3
// 明确没有入度为2的情况,那么一定有有向环,找到构成环的边返回就可以了
return getRemoveEdge(edges);
}
};
三、模拟
3.1 机器人能否返回原点
class Solution {
public:
bool judgeCircle(string moves) {
int x = 0, y = 0;
for (int i = 0; i < moves.size(); i ++ ) {
if (moves[i] == 'U') y ++ ;
if (moves[i] == 'D') y -- ;
if (moves[i] == 'L') x -- ;
if (moves[i] == 'R') x ++ ;
}
return !x && !y;
}
};
3.2 下一个排列
class Solution {
public:
void nextPermutation(vector<int>& nums) {
for (int i = nums.size() - 1; i >= 0; i -- )
for (int j = nums.size() - 1; j > i; j -- )
if (nums[j] > nums[i]) {
swap(nums[i], nums[j]);
reverse(nums.begin() + i + 1, nums.end());
return;
}
// 到这里了说明整个数组都是倒序了,反转一下便可
reverse(nums.begin(), nums.end());
}
};
3.3 岛屿的周长
方法一:遍历每一个空格,遇到岛屿,计算其上下左右的情况,遇到水域或者出界的情况,就可以计算边了。
class Solution {
public:
int islandPerimeter(vector<vector<int>>& grid) {
int direction[4][2] = {0, 1, 1, 0, -1, 0, 0, -1};
int res = 0;
for (int i = 0; i < grid.size(); i ++ )
for (int j = 0; j < grid[0].size(); j ++ )
if (grid[i][j])
for (int k = 0; k < 4; k ++ ) {
int x = i + direction[k][0];
int y = j + direction[k][1];
if (x < 0 || x >= grid.size() || y < 0 || y >= grid[0].size() || !grid[x][y])
res ++ ;
}
return res;
}
};
方法二:计算出总的岛屿数量,因为有一对相邻两个陆地,边的总数就减 2,那么在计算出相邻岛屿的数量就可以了。
代码中未统计下边和右边的相邻矩阵是为了防止重复计算。
class Solution {
public:
int islandPerimeter(vector<vector<int>>& grid) {
int sum = 0, cover = 0; // 总数量,相邻数量
for (int i = 0; i < grid.size(); i ++ )
for (int j = 0; j < grid[0].size(); j ++ )
if (grid[i][j]) {
sum ++ ;
if (i - 1 >= 0 && grid[i - 1][j]) cover ++ ; // 统计上边
if (j - 1 >= 0 && grid[i][j - 1]) cover ++ ; // 统计左边
}
return sum * 4 - cover * 2;
}
};
四、位运算
计算二进制中 1 的个数:
// 方法一:
int bitCount(int n) {
int count = 0; // 计数器
while (n > 0) {
if((n & 1) == 1) count++; // 当前位是1,count++
n >>= 1 ; // n向右移位
}
return count;
}
// 方法二
int bitCount(int n) {
int count = 0;
while (n) {
n &= (n - 1); // 清除最低位的1
count++;
}
return count;
}
class Solution {
public:
vector<int> sortByBits(vector<int>& arr) {
sort(arr.begin(), arr.end(), cmp);
return arr;
}
static int bitCount(int n) {
int count = 0;
while (n)
n &= (n - 1), count ++ ;
return count;
}
static bool cmp(int a, int b) {
int countA = bitCount(a), countB = bitCount(b);
if (countA == countB) return a < b;
return countA < countB;
}
};
五、哈希表
5.1 同构字符串
class Solution {
public:
bool isIsomorphic(string s, string t) {
unordered_map<char, char> mp1, mp2;
for (int i = 0, j = 0; i < s.size(); i ++ , j ++ ) {
if (mp1.find(s[i]) == mp1.end()) mp1[s[i]] = t[j];
if (mp2.find(t[j]) == mp2.end()) mp2[t[j]] = s[i];
if (mp1[s[i]] != t[j] || mp2[t[j]] != s[i]) return false;
}
return true;
}
};
5.2 查找共用字符
class Solution {
public:
vector<string> commonChars(vector<string>& words) {
vector<string> res;
if (!words.size()) return res;
int hash[30] = {0}; // 统计所有字符串里字符出现的最小频率
for (char c: words[0]) hash[c - 'a'] ++ ; // 用第一个字符初始化
int hashOtherStr[30]; // 统计除第一个字符串以外所有字符出现的频率
for (int i = 1; i < words.size(); i ++ ) {
memset(hashOtherStr, 0, 30 * sizeof(int));
for (char c: words[i]) hashOtherStr[c - 'a'] ++ ;
for (int j = 0; j < 26; j ++ )
hash[j] = min(hash[j], hashOtherStr[j]);
}
for (int i = 0; i < 26; i ++ )
while (hash[i]) { // 注意这里是while,多个重复的字符
string s(1, i + 'a');
res.push_back(s);
hash[i] -- ;
}
return res;
}
};
5.3 长按键入
class Solution {
public:
bool isLongPressedName(string name, string typed) {
int i = 0, j = 0;
while (i < name.size() && j < typed.size())
if (name[i] == typed[j]) i ++ , j ++ ;
else {
if (!j) return false; // 如果是第一个位置不匹配
while (j < typed.size() && typed[j] == typed[j - 1]) j ++ ; // 跳过重复项
if (name[i] == typed[j]) i ++ , j ++ ;
else return false;
}
if (i < name.size()) return false; // name没有匹配完
while (j < typed.size()) // type没有匹配完
if (typed[j] == typed[j - 1]) j ++ ;
else return false;
return true;
}
};
六、二叉树
6.1 求根节点到叶节点数字之和
版本一:
class Solution {
public:
int dfs(TreeNode *root, int preSum) {
if (!root) return 0;
int sum = preSum * 10 + root->val;
if (!root->left && !root->right) return sum;
return dfs(root->left, sum) + dfs(root->right, sum);
}
int sumNumbers(TreeNode* root) {
return dfs(root, 0);
}
};
版本二:
class Solution {
public:
int res;
vector<int> path;
int sumNumbers(TreeNode* root) {
path.clear();
res = 0;
if (!root) return res;
path.push_back(root->val);
traversal(root);
return res;
}
void traversal(TreeNode* cur) {
if (!cur->left && !cur->right) {
res += vectorToInt(path);
return;
}
if (cur->left) {
path.push_back(cur->left->val);
traversal(cur->left);
path.pop_back();
}
if (cur->right) {
path.push_back(cur->right->val);
traversal(cur->right);
path.pop_back();
}
return;
}
int vectorToInt(vector<int> &vec) {
int sum = 0;
for (int x: vec) sum = sum * 10 + x;
return sum;
}
};
6.2 将二叉搜索树变平衡
class Solution {
public:
vector<int> vec;
TreeNode* balanceBST(TreeNode* root) {
traversal(root);
return getTree(vec, 0, vec.size() - 1);
}
// 有序数组转换为平衡二叉树
TreeNode* getTree(vector<int>& nums, int left, int right) {
if (left > right) return nullptr;
int mid = left + ((right - left) >> 1);
TreeNode* root = new TreeNode(nums[mid]);
root->left = getTree(nums, left, mid - 1);
root->right = getTree(nums, mid + 1, right);
return root;
}
// 有序树转换为有序数组
void traversal(TreeNode* cur) {
if (!cur) return;
traversal(cur->left);
vec.push_back(cur->val);
traversal(cur->right);
}
};
七、链表
7.1 回文链表
class Solution {
public:
bool isPalindrome(ListNode* head) {
ListNode* cur = head;
int len = 0;
while (cur) len ++ , cur = cur->next;
vector<int> vec(len, 0); // 给定vector初始长度,避免vector每次添加节点重新开辟新空间
cur = head;
int index = 0;
while (cur) vec[index ++ ] = cur->val, cur = cur->next;
for (int i = 0, j = len - 1; i < j; i ++ , j -- )
if (vec[i] != vec[j]) return false;
return true;
}
};
7.2 重排链表
方法一:数组模拟
class Solution {
public:
void reorderList(ListNode* head) {
vector<ListNode*> vec;
ListNode* cur = head;
if (!cur) return;
while (cur) vec.push_back(cur), cur = cur->next;
cur = head;
int i = 1, j = vec.size() - 1, count = 0; // count 用于计数
while (i <= j) {
if (count % 2 == 0)
cur->next = vec[j -- ];
else cur->next = vec[i ++ ];
cur = cur->next, count ++ ;
}
cur->next = nullptr;
}
};
方法二:双向队列模拟
class Solution {
public:
void reorderList(ListNode* head) {
deque<ListNode*> que;
if (!head) return;
ListNode* cur = head;
while (cur->next) que.push_back(cur->next), cur = cur->next; // 头结点不入队
cur = head;
int count = 0; // 计数
ListNode* node;
while (!que.empty()) {
if (count % 2 == 0) node = que.back(), que.pop_back();
else node = que.front(), que.pop_front();
count ++ ;
cur->next = node;
cur = cur->next;
}
cur->next = nullptr;
}
};
方法三:将链表分割成两个链表,然后把第二个链表反转,最后再拼接
class Solution {
public:
void reorderList(ListNode* head) {
if (!head) return;
ListNode* fast = head, *slow = head;
while (fast && fast->next && fast->next->next)
fast = fast->next->next, slow = slow->next;
ListNode *head1 = head, *head2 = slow->next;
slow->next = nullptr;
head2 = reverseList(head2);
ListNode *cur1 = head1, *cur2 = head2, *cur = head;
cur1 = cur1->next;
int count = 0;
while (cur1 && cur2) {
if (count % 2 == 0) cur->next = cur2, cur2 = cur2->next;
else cur->next = cur1, cur1 = cur1->next;
count ++ ;
cur = cur->next;
}
if (cur2) cur->next = cur2;
if (cur1) cur->next = cur1;
}
ListNode* reverseList(ListNode* head) {
ListNode *tmp, *cur = head, *pre = nullptr;
while (cur) {
tmp = cur->next;
cur->next = pre;
pre = cur, cur = tmp;
}
return pre;
}
};
八、数组
8.1 有多少小于当前数字的数字
排序 + 哈希 + 从后往前遍历(方便处理数值相同的情况)
class Solution {
public:
vector<int> smallerNumbersThanCurrent(vector<int>& nums) {
vector<int> vec = nums;
sort(vec.begin(), vec.end());
int hash[110];
for (int i = vec.size() - 1; i >= 0; i -- )
hash[vec[i]] = i;
for (int i = 0; i < nums.size(); i ++ )
vec[i] = hash[nums[i]];
return vec;
}
};
8.2 有效的山脉数组
双指针
class Solution {
public:
bool validMountainArray(vector<int>& arr) {
if (arr.size() < 3) return false;
int l = 0, r = arr.size() - 1;
while (l < arr.size() - 1 && arr[l] < arr[l + 1]) l ++ ;
while (r > 0 && arr[r] < arr[r - 1]) r -- ;
if (l == r && l && r != arr.size() - 1) return true; // 如果l和r都在起始位置,则不是山峰
return false;
}
};
8.3 独一无二的出现次数
class Solution {
public:
bool uniqueOccurrences(vector<int>& arr) {
int count[2010] = {0}; // 统计数字出现的频率
for (int i = 0; i < arr.size(); i ++ )
count[arr[i] + 1000] ++ ;
bool fre[1010] = {false}; // 查看相同频率是否重复出现
for (int i = 0; i <= 2000; i ++ )
if (count[i]) {
if (!fre[count[i]]) fre[count[i]] = true;
else return false;
}
return true;
}
};
8.4 旋转数组
class Solution {
public:
void rotate(vector<int>& nums, int k) {
k = k % nums.size(); // 处理 k > nums.size() 的情况
reverse(nums.begin(), nums.end());
reverse(nums.begin(), nums.begin() + k);
reverse(nums.begin() + k, nums.end());
}
};
8.5 寻找数组的中心下标
class Solution {
public:
int pivotIndex(vector<int>& nums) {
int sum = 0, lSum = 0, rSum = 0;
for (int x : nums) sum += x;
for (int i = 0; i < nums.size(); i ++ ) {
lSum += nums[i], rSum = sum - lSum + nums[i];
if (lSum == rSum) return i;
}
return -1;
}
};
8.6 按奇偶排序数组 II
class Solution {
public:
vector<int> sortArrayByParityII(vector<int>& nums) {
int oddIndex = 1;
for (int i = 0; i < nums.size(); i += 2)
if (nums[i] % 2) { // 在偶数位置上遇到的奇数
while (nums[oddIndex] % 2) oddIndex += 2; // 在奇数位找一个偶数
swap(nums[i], nums[oddIndex]);
}
return nums;
}
};