- 键值映射
实现一个 MapSum 类,支持两个方法,insert 和 sum
标准trie树
struct Trie {
Trie* next[26];
bool isend;
int val;
Trie() {
for (int i = 0; i < 26; i++) {
next[i] = nullptr;
}
isend = false;
}
};
class MapSum {
public:
Trie root;
/** Initialize your data structure here. */
MapSum() {
// do nothing
}
void insert(string key, int val) {
Trie *cur = &root;
for (int i = 0; i < key.size(); i++) {
char c = key[i];
if (cur->next[c - 'a'] == nullptr) {
cur->next[c - 'a'] = new Trie();
}
cur = cur->next[c - 'a'];
}
cur->isend = true;
cur->val = val;
}
void dfs(Trie *root, int& ans) {
if (root->isend) {
ans += root->val;
}
for (int i = 0; i < 26; i++) {
if (root->next[i] != nullptr) {
dfs(root->next[i], ans);
}
}
}
int sum(string prefix) {
Trie *cur = &root;
int ans = 0;
for (int i = 0; i < prefix.size(); i++) {
char c = prefix[i];
if (cur->next[c - 'a'] == nullptr) {
return ans;
}
cur = cur->next[c - 'a'];
}
dfs(cur, ans);
return ans;
}
};
/**
* Your MapSum object will be instantiated and called as such:
* MapSum* obj = new MapSum();
* obj->insert(key,val);
* int param_2 = obj->sum(prefix);
*/
- 有效的括号字符串
给定一个只包含三种字符的字符串:( ,) 和 *,写一个函数来检验这个字符串是否为有效字符串。
nMin、nMax表示「可能多余的左括号」,一个下界,一个上界,很直观。执行起来就是
遇到左括号:nMin++, nMax++
遇到星号:nMin–, nMax++(因为星号有三种情况)
遇到右括号:nMin–, nMax–
其中nMin要保持不小于0。
如果nMax < 0,说明把星号全变成左括号也不够了,False
最後,如果nMin != 0,说明末尾有多余的左括号,False
class Solution {
public:
bool checkValidString(string s)
{
int nMax = 0, nMin = 0;
for (auto c : s)
{
switch (c)
{
case '(':
nMax++;
nMin++;
break;
case ')':
nMax--;
if (nMin > 0) nMin--;
break;
case '*':
if (nMin > 0) nMin--;
nMax++;
break;
}
if (nMax < 0) return false;
}
return nMin == 0;
}
};
- 24点游戏
你有 4 张写有 1 到 9 数字的牌。你需要判断是否能通过 *,/,+,-,(,) 的运算得到 24。
回溯穷举各种组合即可
class Solution {
public:
static constexpr int TARGET = 24;
static constexpr double EPSILON = 1e-6;
static constexpr int ADD = 0, MULTIPLY = 1, SUBTRACT = 2, DIVIDE = 3;
bool judgePoint24(vector<int> &nums) {
vector<double> l;
for (const int &num : nums) {
l.emplace_back(static_cast<double>(num));
}
return solve(l);
}
bool solve(vector<double> &l) {
if (l.size() == 0) {
return false;
}
if (l.size() == 1) {
return fabs(l[0] - TARGET) < EPSILON;
}
int size = l.size();
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
if (i != j) {
vector<double> list2 = vector<double>();
for (int k = 0; k < size; k++) {
if (k != i && k != j) {
list2.emplace_back(l[k]);
}
}
for (int k = 0; k < 4; k++) {
if (k < 2 && i > j) {
continue;
}
if (k == ADD) {
list2.emplace_back(l[i] + l[j]);
} else if (k == MULTIPLY) {
list2.emplace_back(l[i] * l[j]);
} else if (k == SUBTRACT) {
list2.emplace_back(l[i] - l[j]);
} else if (k == DIVIDE) {
if (fabs(l[j]) < EPSILON) {
continue;
}
list2.emplace_back(l[i] / l[j]);
}
if (solve(list2)) {
return true;
}
list2.pop_back();
}
}
}
}
return false;
}
};
- 验证回文字符串2
给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。
采用双指针滑动+贪心算法,当遇到首尾不一样的时候,检测是否能通过删除其中一个来得到回文串,如果可以则true,否则为false
class Solution {
public:
bool checkPalindrome(const string& s, int low, int high)
{
while (low < high)
{
if (s[low] != s[high]) return false;
low++;
high--;
}
return true;
}
bool validPalindrome(string s)
{
int low = 0, high = s.size() - 1;
while (low < high)
{
char c1 = s[low], c2 = s[high];
if (c1 == c2)
{
++low;
--high;
}
else
{
return checkPalindrome(s, low, high - 1) || checkPalindrome(s, low + 1, high);
}
}
return true;
}
};
- 棒球比赛
你现在是一场采用特殊赛制棒球比赛的记录员。这场比赛由若干回合组成,过去几回合的得分可能会影响以后几回合的得分。
用栈即可
class Solution {
public:
int calPoints(vector<string>& ops) {
stack<int> nums;
for (string& s : ops)
{
// cout << s << " " << nums.size() << endl;
if (s == "+")
{
// 这里要记得把弹出来的数字先插回去
int n1 = nums.top();
nums.pop();
int n2 = nums.top();
nums.push(n1);
nums.push(n1+n2);
}
else if (s == "D")
{
nums.push(nums.top() * 2);
}
else if (s == "C")
{
nums.pop();
}
else
{
nums.push(stoi(s));
}
}
// 累加和获得结果
int res = 0;
while (!nums.empty())
{
res += nums.top();
nums.pop();
}
return res;
}
};
- 冗余连接
输入一个图,该图由一个有着N个节点 (节点值不重复1, 2, …, N) 的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。结果图是一个以边组成的二维数组。每一个边的元素是一对[u, v] ,满足 u < v,表示连接顶点u 和v的无向图的边。返回一条可以删去的边,使得结果图是一个有着N个节点的树。如果有多个答案,则返回二维数组中最后出现的边。答案边 [u, v] 应满足相同的格式 u < v。
使用集合存储边,轮询检查即可
class Solution {
private:
int n = 1005; // 节点数量3 到 1000
int father[1005];
// 并查集初始化
void init() {
for (int i = 0; i < n; ++i) {
father[i] = i;
}
}
// 并查集里寻根的过程
int find(int u) {
return u == father[u] ? u : father[u] = find(father[u]);
}
// 将v->u 这条边加入并查集
void join(int u, int v) {
u = find(u);
v = find(v);
if (u == v) return ;
father[v] = u;
}
// 判断 u 和 v是否找到同一个根,本题用不上
bool same(int u, int v) {
u = find(u);
v = find(v);
return u == v;
}
public:
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
init();
for (int i = 0; i < edges.size(); i++) {
if (same(edges[i][0], edges[i][1])) return edges[i];
else join(edges[i][0], edges[i][1]);
}
return {
};
}
};
- 冗余连接2
在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。
输入一个有向图,该图由一个有着 n 个节点(节点值不重复,从 1 到 n)的树及一条附加的有向边构成。附加的边包含在 1 到 n 中的两个不同顶点间,这条附加的边不属于树中已存在的边。
结果图是一个以边组成的二维数组 edges 。 每个元素是一对 [ui, vi],用以表示 有向 图中连接顶点 ui 和顶点 vi 的边,其中 ui 是 vi 的一个父节点。
返回一条能删除的边,使得剩下的图是有 n 个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。
一样使用并查集
class Solution {
private:
static const int N = 1010; // 如题:二维数组大小的在3到1000范围内
int father[N];
int n; // 边的数量
// 并查集初始化
void init() {
for (int i = 1; i <= n; ++i) {
father[i] = i;
}
}
// 并查集里寻根的过程
int find(int u) {
return u == father[u] ? u : father[u] = find(father[u]);
}
// 将v->u 这条边加入并查集
void join(int u, int v) {
u = find(u);
v = find(v);
if (u == v) return ;
father[v] = u;
}
// 判断 u 和 v是否找到同一个根
bool same(int u, int v) {
u = find(u);
v = find(v);
return u == v;
}
// 在有向图里找到删除的那条边,使其变成树
vector<int> getRemoveEdge(const vector<vector<int>>& edges) {
init(); // 初始化并查集
for (int i = 0; i < n; i++) {
// 遍历所有的边
if (same(edges[i][0], edges[i][1])) {
// 构成有向环了,就是要删除的边
return edges[i];
}
join(edges[i][0], edges[i][1]);
}
return {
};
}
// 删一条边之后判断是不是树
bool isTreeAfterRemoveEdge(const vector<vector<int>>& edges, int deleteEdge) {
init(); // 初始化并查集
for (int i = 0; i < n; i++) {
if (i == deleteEdge) continue;
if (same(edges[i][0], edges[i][1])) {
// 构成有向环了,一定不是树
return false;
}
join(edges[i][0], edges[i][1]);
}
return true;
}
public:
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);
}
};