1969. 数组元素的最小非零乘积
给你一个正整数 p 。你有一个下标从 1 开始的数组 nums ,这个数组包含范围 [1, 2p - 1] 内所有整数的二进制形式(两端都 包含)。你可以进行以下操作 任意 次:
从 nums 中选择两个元素 x 和 y 。
选择 x 中的一位与 y 对应位置的位交换。对应位置指的是两个整数 相同位置 的二进制位。
比方说,如果 x = 1101 且 y = 0011 ,交换右边数起第 2 位后,我们得到 x = 1111 和 y = 0001 。
请你算出进行以上操作 任意次 以后,nums 能得到的 最小非零 乘积。将乘积对 109 + 7 取余 后返回。
注意:答案应为取余 之前 的最小值。
示例 1:
输入:p = 1
输出:1
解释:nums = [1] 。
只有一个元素,所以乘积为该元素。
示例 2:
输入:p = 2
输出:6
解释:nums = [01, 10, 11] 。
所有交换要么使乘积变为 0 ,要么乘积与初始乘积相同。
所以,数组乘积 1 * 2 * 3 = 6 已经是最小值。
示例 3:
输入:p = 3
输出:1512
解释:nums = [001, 010, 011, 100, 101, 110, 111]
- 第一次操作中,我们交换第二个和第五个元素最左边的数位。
- 结果数组为 [001, 110, 011, 100, 001, 110, 111] 。
- 第二次操作中,我们交换第三个和第四个元素中间的数位。
- 结果数组为 [001, 110, 001, 110, 001, 110, 111] 。
数组乘积 1 * 6 * 1 * 6 * 1 * 6 * 7 = 1512 是最小乘积。
- 结果数组为 [001, 110, 001, 110, 001, 110, 111] 。
提示:
1 <= p <= 60
贪心+快速幂:
class Solution {
const int mod = 1'000'000'007;
long long pow(long long x, int p) {
x %= mod;
long long res = 1;
while (p--) {
res = res * x % mod;
x = x * x % mod;
}
return res;
}
public:
int minNonZeroProduct(int p) {
long long k = (1LL << p) - 1;
return k % mod * pow(k - 1, p - 1) % mod;
}
};
(偷看灵神题解
2671. 频率跟踪器
请你设计并实现一个能够对其中的值进行跟踪的数据结构,并支持对频率相关查询进行应答。
实现 FrequencyTracker 类:
FrequencyTracker():使用一个空数组初始化 FrequencyTracker 对象。
void add(int number):添加一个 number 到数据结构中。
void deleteOne(int number):从数据结构中删除一个 number 。数据结构 可能不包含 number ,在这种情况下不删除任何内容。
bool hasFrequency(int frequency): 如果数据结构中存在出现 frequency 次的数字,则返回 true,否则返回 false。
示例 1:
输入
[“FrequencyTracker”, “add”, “add”, “hasFrequency”]
[[], [3], [3], [2]]
输出
[null, null, null, true]
解释
FrequencyTracker frequencyTracker = new FrequencyTracker();
frequencyTracker.add(3); // 数据结构现在包含 [3]
frequencyTracker.add(3); // 数据结构现在包含 [3, 3]
frequencyTracker.hasFrequency(2); // 返回 true ,因为 3 出现 2 次
示例 2:
输入
[“FrequencyTracker”, “add”, “deleteOne”, “hasFrequency”]
[[], [1], [1], [1]]
输出
[null, null, null, false]
解释
FrequencyTracker frequencyTracker = new FrequencyTracker();
frequencyTracker.add(1); // 数据结构现在包含 [1]
frequencyTracker.deleteOne(1); // 数据结构现在为空 []
frequencyTracker.hasFrequency(1); // 返回 false ,因为数据结构为空
示例 3:
输入
[“FrequencyTracker”, “hasFrequency”, “add”, “hasFrequency”]
[[], [2], [3], [1]]
输出
[null, false, null, true]
解释
FrequencyTracker frequencyTracker = new FrequencyTracker();
frequencyTracker.hasFrequency(2); // 返回 false ,因为数据结构为空
frequencyTracker.add(3); // 数据结构现在包含 [3]
frequencyTracker.hasFrequency(1); // 返回 true ,因为 3 出现 1 次
提示:
1 <= number <= 1e5
1 <= frequency <= 1e5
最多调用 add、deleteOne 和 hasFrequency 共计 2 * 1e5 次
双哈希:
class FrequencyTracker {
public:
unordered_map<int, int> numToFreq;
unordered_map<int, unordered_set<int>> freqToNums;
FrequencyTracker() {}
void add(int number) {
if (numToFreq[number]) {
freqToNums[numToFreq[number]].erase(number);
if (freqToNums[numToFreq[number]].empty()) {
freqToNums.erase(numToFreq[number]);
}
}
numToFreq[number]++;
freqToNums[numToFreq[number]].insert(number);
}
void deleteOne(int number) {
if (numToFreq[number]) {
freqToNums[numToFreq[number]].erase(number);
if (freqToNums[numToFreq[number]].empty()) {
freqToNums.erase(numToFreq[number]);
}
numToFreq[number]--;
if (numToFreq[number]) {
freqToNums[numToFreq[number]].insert(number);
} else {
numToFreq.erase(number);
}
}
}
bool hasFrequency(int frequency) { return freqToNums.count(frequency); }
};
/**
* Your FrequencyTracker object will be instantiated and called as such:
* FrequencyTracker* obj = new FrequencyTracker();
* obj->add(number);
* obj->deleteOne(number);
* bool param_3 = obj->hasFrequency(frequency);
*/
2617. 网格图中最少访问的格子数(Hard)
给你一个下标从 0 开始的 m x n 整数矩阵 grid 。你一开始的位置在 左上角 格子 (0, 0) 。
当你在格子 (i, j) 的时候,你可以移动到以下格子之一:
满足 j < k <= grid[i][j] + j 的格子 (i, k) (向右移动),或者
满足 i < k <= grid[i][j] + i 的格子 (k, j) (向下移动)。
请你返回到达 右下角 格子 (m - 1, n - 1) 需要经过的最少移动格子数,如果无法到达右下角格子,请你返回 -1 。
示例 1:
输入:grid = [[3,4,2,1],[4,2,3,1],[2,1,0,0],[2,4,0,0]]
输出:4
解释:上图展示了到达右下角格子经过的 4 个格子。
示例 2:
输入:grid = [[3,4,2,1],[4,2,1,1],[2,1,1,0],[3,4,1,0]]
输出:3
解释:上图展示了到达右下角格子经过的 3 个格子。
示例 3:
输入:grid = [[2,1,0],[1,0,0]]
输出:-1
解释:无法到达右下角格子。
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 1e5
1 <= m * n <= 1e5
0 <= grid[i][j] < m * n
grid[m - 1][n - 1] == 0
菜鸡不会抄了灵神题解,单调栈dp:
class Solution {
public:
int minimumVisitedCells(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size(), mn;
vector<vector<pair<int, int>>> col_stacks(n); // 每列的单调栈
vector<pair<int, int>> row_st; // 行单调栈
for (int i = m - 1; i >= 0; i--) {
row_st.clear();
for (int j = n - 1; j >= 0; j--) {
int g = grid[i][j];
auto& col_st = col_stacks[j];
mn = i < m - 1 || j < n - 1 ? INT_MAX : 1;
if (g) { // 可以向右/向下跳
// 在单调栈上二分查找最优转移来源
auto it = lower_bound(row_st.begin(), row_st.end(), j + g,
[](const auto& a, const int b) {
return a.second > b;
});
if (it < row_st.end())
mn = it->first + 1;
it = lower_bound(col_st.begin(), col_st.end(), i + g,
[](const auto& a, const int b) {
return a.second > b;
});
if (it < col_st.end())
mn = min(mn, it->first + 1);
}
if (mn < INT_MAX) {
// 插入单调栈
while (!row_st.empty() && mn <= row_st.back().first) {
row_st.pop_back();
}
row_st.emplace_back(mn, j);
while (!col_st.empty() && mn <= col_st.back().first) {
col_st.pop_back();
}
col_st.emplace_back(mn, i);
}
}
}
return mn < INT_MAX ? mn : -1; // 最后一个算出的 mn 就是 f[0][0]
}
};
题解:两种方法:单调栈优化 DP / 贪心+最小堆
2549. 统计桌面上的不同数字
多带几组数据,一行解决:
class Solution {
public:
int distinctIntegers(int n) { return max(n - 1, 1); }
};
322. 零钱兑换
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2:
输入:coins = [2], amount = 3
输出:-1
示例 3:
输入:coins = [1], amount = 0
输出:0
提示:
1 <= coins.length <= 12
1 <= coins[i] <= 231 - 1
0 <= amount <= 1e4
经典DP:
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
vector<int> dp(amount + 1, amount + 1);
dp[0] = 0;
for (int i = 0; i <= amount; i++) {
for (int coin : coins) {
if (coin <= i) {
dp[i] = min(dp[i], dp[i - coin] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
};
518. 零钱兑换 II
给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。
假设每一种面额的硬币有无限个。
题目数据保证结果符合 32 位带符号整数。
示例 1:
输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
示例 2:
输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。
示例 3:
输入:amount = 10, coins = [10]
输出:1
提示:
1 <= coins.length <= 300
1 <= coins[i] <= 5000
coins 中的所有值 互不相同
0 <= amount <= 5000
动态规划背包问题,与上一题类似:
class Solution {
public:
int change(int amount, vector<int>& coins) {
vector<int> dp(amount + 1);
dp[0] = 1;
for (int coin : coins) {
for (int i = coin; i <= amount; i++) {
dp[i] += dp[i - coin];
}
}
return dp[amount];
}
};
2642. 设计可以求最短路径的图类(Hard)
给你一个有 n 个节点的 有向带权 图,节点编号为 0 到 n - 1 。图中的初始边用数组 edges 表示,其中 edges[i] = [fromi, toi, edgeCosti] 表示从 fromi 到 toi 有一条代价为 edgeCosti 的边。
请你实现一个 Graph 类:
Graph(int n, int[][] edges) 初始化图有 n 个节点,并输入初始边。
addEdge(int[] edge) 向边集中添加一条边,其中 edge = [from, to, edgeCost] 。数据保证添加这条边之前对应的两个节点之间没有有向边。
int shortestPath(int node1, int node2) 返回从节点 node1 到 node2 的路径 最小 代价。如果路径不存在,返回 -1 。一条路径的代价是路径中所有边代价之和。
示例 1:
输入:
[“Graph”, “shortestPath”, “shortestPath”, “addEdge”, “shortestPath”]
[[4, [[0, 2, 5], [0, 1, 2], [1, 2, 1], [3, 0, 3]]], [3, 2], [0, 3], [[1, 3, 4]], [0, 3]]
输出:
[null, 6, -1, null, 6]
解释:
Graph g = new Graph(4, [[0, 2, 5], [0, 1, 2], [1, 2, 1], [3, 0, 3]]);
g.shortestPath(3, 2); // 返回 6 。从 3 到 2 的最短路径如第一幅图所示:3 -> 0 -> 1 -> 2 ,总代价为 3 + 2 + 1 = 6 。
g.shortestPath(0, 3); // 返回 -1 。没有从 0 到 3 的路径。
g.addEdge([1, 3, 4]); // 添加一条节点 1 到节点 3 的边,得到第二幅图。
g.shortestPath(0, 3); // 返回 6 。从 0 到 3 的最短路径为 0 -> 1 -> 3 ,总代价为 2 + 4 = 6 。
提示:
1 <= n <= 100
0 <= edges.length <= n * (n - 1)
edges[i].length == edge.length == 3
0 <= fromi, toi, from, to, node1, node2 <= n - 1
1 <= edgeCosti, edgeCost <= 1e6
图中任何时候都不会有重边和自环。
调用 addEdge 至多 100 次。
调用 shortestPath 至多 100 次。
dijkstra最短路:
class Graph {
public:
static constexpr int INF = INT_MAX / 2;
vector<vector<pair<int, int>>> g;
Graph(int n, vector<vector<int>>& edges) {
g.resize(n);
for (const auto& e : edges) {
int u = e[0], v = e[1], w = e[2];
g[u].emplace_back(v, w);
}
}
void addEdge(const vector<int>& edge) {
int u = edge[0], v = edge[1], w = edge[2];
g[u].emplace_back(v, w);
}
int shortestPath(int start, int end) {
int n = g.size();
vector<int> dist(n, INF);
dist[start] = 0;
priority_queue<pair<int, int>, vector<pair<int, int>>,
greater<pair<int, int>>>
que;
que.emplace(0, start);
while (!que.empty()) {
auto [d, u] = que.top();
que.pop();
if (d != dist[u]) {
continue;
}
for (auto [v, w] : g[u]) {
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
que.emplace(dist[v], v);
}
}
}
return dist[end] == INF ? -1 : dist[end];
}
};
/**
* Your Graph object will be instantiated and called as such:
* Graph* obj = new Graph(n, edges);
* obj->addEdge(edge);
* int param_2 = obj->shortestPath(node1,node2);
*/