- 旋转字符串
给定两个字符串, A 和 B。A 的旋转操作就是将 A 最左边的字符移动到最右边。 例如, 若 A = ‘abcde’,在移动一次之后结果就是’bcdea’ 。如果在若干次旋转操作之后,A 能变成B,那么返回True。
题目主要思路是,A + A若包含B则满足,不包含则没有,所以实际问题是找子串的问题,采取KMP即可
class Solution {
public:
void getNext(string& p, vector<int>& next) {
int pLen = p.size();
next.resize(pLen, -1);
int k = -1, j = 0;
while (j < pLen - 1) {
if (k == -1 || p[k] == p[j]) {
k++;
j++;
next[j] = k;
} else {
k = next[k];
}
}
}
bool rotateString(string A, string B) {
if (A.size() != B.size()) return false;
A += A;
int ALen = A.size();
int BLen = B.size();
vector<int> next;
getNext(B, next);
int i = 0, j = 0;
while (i < ALen && j < BLen) {
if (j == -1 || A[i] == B[j]) {
i++;
j++;
} else {
j = next[j];
}
}
return j == BLen ? true : false;
}
};
- 所有可能的路径
给你一个有 n 个节点的 有向无环图(DAG),请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序)二维数组的第 i 个数组中的单元都表示有向图中 i 号节点所能到达的下一些节点,空就是没有下一个结点了。
深度优先求解
class Solution {
public:
vector<vector<int>> ans;
vector<int> stk;
void dfs(vector<vector<int>>& graph, int x, int n) {
if (x == n) {
ans.push_back(stk);
return;
}
for (auto& y : graph[x]) {
stk.push_back(y);
dfs(graph, y, n);
stk.pop_back();
}
}
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
stk.push_back(0);
dfs(graph, 0, graph.size() - 1);
return ans;
}
};
- 得分最高的最小轮调
给定一个数组 A,我们可以将它按一个非负整数 K 进行轮调,这样可以使数组变为 A[K], A[K+1], A{K+2], … A[A.length - 1], A[0], A[1], …, A[K-1] 的形式。此后,任何值小于或等于其索引的项都可以记作一分。
例如,如果数组为 [2, 4, 1, 3, 0],我们按 K = 2 进行轮调后,它将变成 [1, 3, 0, 2, 4]。这将记作 3 分,因为 1 > 0 [no points], 3 > 1 [no points], 0 <= 2 [one point], 2 <= 3 [one point], 4 <= 4 [one point]。
在所有可能的轮调中,返回我们所能得到的最高分数对应的轮调索引 K。如果有多个答案,返回满足条件的最小的索引 K
差分解法
对于num,它位于 [num, n-1]里是能得分的, 基于现在数值,我们就能知道它的有效区间是什么
开始的点+1,结束的点-1
范围是 [i+1, n+i+1-nums[i]]: 这里要注意范围溢出,所以会用n来取模
计算出差分数组后,单次遍历不断累加取最大结果就是答案
class Solution {
public:
int bestRotation(vector<int>& nums) {
int n = nums.size();
int diff[n];
memset(diff, 0, sizeof(diff));
for (int i = 0; i < n; ++i)
{
++diff[(i+1)%n];
--diff[(n+i+1-nums[i])%n];
}
int res = 0;
int currScore = 0;
int maxScore = INT_MIN;
for (int i = 0; i < n; ++i)
{
currScore += diff[i];
if (currScore > maxScore)
{
maxScore = currScore;
res = i;
}
}
return res;
}
};
- 香槟塔
现在当倾倒了非负整数杯香槟后,返回第 i 行 j 个玻璃杯所盛放的香槟占玻璃杯容积的比例(i 和 j都从0开始)。
逐行遍历判断即可
class Solution {
public:
double champagneTower(int poured, int query_row, int query_glass) {
double glasses[10000] = {
0};
glasses[0] = poured;
int cur_row = 0;
while(cur_row <= query_row){
//溢出到下一行
for(int i = cur_row * (cur_row + 1) / 2; i < (cur_row + 1) * (cur_row + 2) / 2; i++){
double temp = glasses[i];
if(temp > 1){
glasses[i] = 1;
glasses[i + cur_row + 1] += (temp - 1) / 2;
glasses[i + cur_row + 2] += (temp - 1) / 2;
}
}
cur_row++;
}
return glasses[query_row * (query_row + 1) / 2 + query_glass];
}
};
- 使序列递增的最小交换次数
我们有两个长度相等且不为空的整型数组 A 和 B 。
我们可以交换 A[i] 和 B[i] 的元素。注意这两个元素在各自的序列中应该处于相同的位置。
在交换过一些元素之后,数组 A 和 B 都应该是严格递增的(数组严格递增的条件仅为A[0] < A[1] < A[2] < … < A[A.length - 1])。给定数组 A 和 B ,请返回使得两个数组均保持严格递增状态的最小交换次数。假设给定的输入总是有效的。
判断 A[i] 和 B[i] 是否交换时,只需要考虑它们之前的一个元素 A[i - 1] 和 B[i - 1] 是否被交换就可以了,因此可用动态规划求解
class Solution {
public:
int minSwap(vector<int>& nums1, vector<int>& nums2) {
int dn = 0;
int ds = 1;
int n = nums1.size();
for (int i = 1; i < n; ++i)
{
int dn2 = INT_MAX;
int ds2 = INT_MAX;
if (nums1[i-1] < nums1[i] && nums2[i-1] < nums2[i])
{
dn2 = min(dn2, dn);
ds2 = min(ds2, ds+1);
}
if (nums1[i-1] < nums2[i] && nums2[i-1] < nums1[i])
{
dn2 = min(dn2, ds);
ds2 = min(ds2, dn+1);
}
dn = dn2;
ds = ds2;
}
return min(dn, ds);
}
};
- 找到最终的安全状态
在有向图中,以某个节点为起始节点,从该点出发,每一步沿着图中的一条有向边行走。如果到达的节点是终点(即它没有连出的有向边),则停止。
对于一个起始节点,如果从该节点出发,无论每一步选择沿哪条有向边行走,最后必然在有限步内到达终点,则将该起始节点称作是 安全 的。
返回一个由图中所有安全的起始节点组成的数组作为答案。答案数组中的元素应当按 升序 排列。
该有向图有 n 个节点,按 0 到 n - 1 编号,其中 n 是 graph 的节点数。图以下述形式给出:graph[i] 是编号 j 节点的一个列表,满足 (i, j) 是图的一条有向边。
使用DFS+三色标记法求解
class Solution {
public:
vector<int> eventualSafeNodes(vector<vector<int>> &graph) {
int n = graph.size();
vector<int> color(n);
function<bool(int)> safe = [&](int x) {
if (color[x] > 0) {
return color[x] == 2;
}
color[x] = 1;
for (int y : graph[x]) {
if (!safe(y)) {
return false;
}
}
color[x] = 2;
return true;
};
vector<int> ans;
for (int i = 0; i < n; ++i) {
if (safe(i)) {
ans.push_back(i);
}
}
return ans;
}
};
- 打砖块
给你一个数组 hits ,这是需要依次消除砖块的位置。每当消除 hits[i] = (rowi, coli) 位置上的砖块时,对应位置的砖块(若存在)会消失,然后其他的砖块可能因为这一消除操作而掉落。一旦砖块掉落,它会立即从网格中消失(即,它不会落在其他稳定的砖块上)。返回一个数组 result ,其中 result[i] 表示第 i 次消除操作对应掉落的砖块数目。
并查集的使用
class UnionFind {
private:
vector<int> f, sz;
public:
UnionFind(int n): f(n), sz(n) {
for (int i = 0; i < n; i++) {
f[i] = i;
sz[i] = 1;
}
}
int find(int x) {
if (f[x] == x) {
return x;
}
int newf = find(f[x]);
f[x] = newf;
return f[x];
}
void merge(int x, int y) {
int fx = find(x), fy = find(y);
if (fx == fy) {
return;
}
f[fx] = fy;
sz[fy] += sz[fx];
}
int size(int x) {
return sz[find(x)];
}
};
class Solution {
public:
vector<int> hitBricks(vector<vector<int>>& grid, vector<vector<int>>& hits) {
int h = grid.size(), w = grid[0].size();
UnionFind uf(h * w + 1);
vector<vector<int>> status = grid;
for (int i = 0; i < hits.size(); i++) {
status[hits[i][0]][hits[i][1]] = 0;
}
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
if (status[i][j] == 1) {
if (i == 0) {
uf.merge(h * w, i * w + j);
}
if (i > 0 && status[i - 1][j] == 1) {
uf.merge(i * w + j, (i - 1) * w + j);
}
if (j > 0 && status[i][j - 1] == 1) {
uf.merge(i * w + j, i * w + j - 1);
}
}
}
}
const vector<pair<int, int>> directions{
{
0, 1},{
1, 0},{
0, -1},{
-1, 0}};
vector<int> ret(hits.size(), 0);
for (int i = hits.size() - 1; i >= 0; i--) {
int r = hits[i][0], c = hits[i][1];
if (grid[r][c] == 0) {
continue;
}
int prev = uf.size(h * w);
if (r == 0) {
uf.merge(c, h * w);
}
for (const auto [dr, dc]: directions) {
int nr = r + dr, nc = c + dc;
if (nr >= 0 && nr < h && nc >= 0 && nc < w) {
if (status[nr][nc] == 1) {
uf.merge(r * w + c, nr * w + nc);
}
}
}
int size = uf.size(h * w);
ret[i] = max(0, size - prev - 1);
status[r][c] = 1;
}
return ret;
}
};