diff --git a/.gitignore b/.gitignore index 84610780c..f21e3d69a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build .idea +.DS_Store diff --git a/3485.Longest-Common-Prefix-of-K-Strings-After-Removal/3485.Longest-Common-Prefix-of-K-Strings-After-Removal.cpp b/3485.Longest-Common-Prefix-of-K-Strings-After-Removal/3485.Longest-Common-Prefix-of-K-Strings-After-Removal.cpp new file mode 100644 index 000000000..a0d2156a6 --- /dev/null +++ b/3485.Longest-Common-Prefix-of-K-Strings-After-Removal/3485.Longest-Common-Prefix-of-K-Strings-After-Removal.cpp @@ -0,0 +1,58 @@ +class TrieNode { + public: + TrieNode* next[26]; + int count = 0; + TrieNode() + { + for (int i=0; i<26; i++) + next[i]=NULL; + } +}; + +class Solution { + TrieNode* root; + int k; + mapMap; +public: + void add(string& s, int delta) { + TrieNode* node = root; + int depth = 0; + for (auto ch: s) { + if (node->next[ch-'a']==NULL) + node->next[ch-'a']=new TrieNode(); + node = node->next[ch-'a']; + node->count+=delta; + depth++; + + int oldValue = node->count-delta; + int newValue = node->count; + if (oldValue=k) { + Map[depth]++; + } else if (oldValue>=k && newValue longestCommonPrefix(vector& words, int k) { + this->k = k; + root = new TrieNode(); + for (auto& word: words) + add(word, 1); + + vectorrets; + for (auto& word: words) { + add(word, -1); + + if (Map.empty()) + rets.push_back(0); + else + rets.push_back(Map.rbegin()->first); + + add(word, 1); + } + return rets; + } +}; diff --git a/3485.Longest-Common-Prefix-of-K-Strings-After-Removal/Readme.md b/3485.Longest-Common-Prefix-of-K-Strings-After-Removal/Readme.md new file mode 100644 index 000000000..e5b22e15f --- /dev/null +++ b/3485.Longest-Common-Prefix-of-K-Strings-After-Removal/Readme.md @@ -0,0 +1,7 @@ +### 3485.Longest-Common-Prefix-of-K-Strings-After-Removal + +我们知道,可以在字典树的每个节点增加一个count变量,来记录字典树里每一条路径的增删。于是此题的关键就在于,如果快速知道在任意时候,count>=k的节点里最大的深度。 + +于是我们容易想到,维护一个key为depth并且有序的map,记录每种深度所对应的节点数量。并且我们在这个map里只收录count>=k的节点。这样就可以容易知道当前所有节点里的最大深度(即map里的最后一个迭代器)。 + +具体操作时,我们在增删每一条路径时,对于路过的节点要做Map的维护。如果该节点的count增大至k时,我们就需要`map[depth]+=1`;反之如果该节点的count恰好减少至k以下时,我们就需要`map[depth]-=1`。并且,如果value变成了零,记得及时把这个key消除。这样任意时刻map最后一个迭代器所对应的key,就是当前所有count>=k的节点里的最大深度。 diff --git a/BFS/1245.Tree-Diameter/1245.Tree-Diameter.cpp b/BFS/1245.Tree-Diameter/1245.Tree-Diameter.cpp index 579951bb5..8b5f7fc83 100644 --- a/BFS/1245.Tree-Diameter/1245.Tree-Diameter.cpp +++ b/BFS/1245.Tree-Diameter/1245.Tree-Diameter.cpp @@ -1,25 +1,23 @@ class Solution { - vector>adj; - int V; public: int treeDiameter(vector>& edges) { - V = edges.size()+1; - adj.resize(V); - for (auto edge:edges) + int n = edges.size()+1; + vector>next(n); + for (auto edge: edges) { - adj[edge[0]].push_back(edge[1]); - adj[edge[1]].push_back(edge[0]); + next[edge[0]].push_back(edge[1]); + next[edge[1]].push_back(edge[0]); } - - auto t1 = bfs(0); - auto t2 = bfs(t1.first); + auto t1 = bfs(next, 0); + auto t2 = bfs(next, t1.first); return t2.second; } - - pair bfs(int u) + + pair bfs(vector>&next, int u) { - vectordis(V, -1); + int n = next.size(); + vectordis(n, -1); queue q; q.push(u); @@ -30,7 +28,7 @@ class Solution { int t = q.front(); q.pop(); - for (auto it = adj[t].begin(); it != adj[t].end(); it++) + for (auto it = next[t].begin(); it != next[t].end(); it++) { int v = *it; if (dis[v] == -1) @@ -42,9 +40,9 @@ class Solution { } int maxDis = 0; - int nodeIdx; + int nodeIdx = 0; - for (int i = 0; i < V; i++) + for (int i = 0; i < n; i++) { if (dis[i] > maxDis) { @@ -53,5 +51,5 @@ class Solution { } } return make_pair(nodeIdx, maxDis); - } + } }; diff --git a/BFS/2577.Minimum-Time-to-Visit-a-Cell-In-a-Grid/2577.Minimum-Time-to-Visit-a-Cell-In-a-Grid.cpp b/BFS/2577.Minimum-Time-to-Visit-a-Cell-In-a-Grid/2577.Minimum-Time-to-Visit-a-Cell-In-a-Grid.cpp new file mode 100644 index 000000000..d39f07620 --- /dev/null +++ b/BFS/2577.Minimum-Time-to-Visit-a-Cell-In-a-Grid/2577.Minimum-Time-to-Visit-a-Cell-In-a-Grid.cpp @@ -0,0 +1,44 @@ +using AI3 = array; +class Solution { + vector>dir= {{0,1},{0,-1},{1,0},{-1,0}}; +public: + int minimumTime(vector>& grid) + { + if (grid[0][1]>1 && grid[1][0]>1) return -1; + + int m = grid.size(), n = grid[0].size(); + vector>arrival(m, vector(n,-1)); + + priority_queue, greater<>>pq; + if (grid[0][1]<=1) pq.push({1,0,1}); + if (grid[1][0]<=1) pq.push({1,1,0}); + + while (!pq.empty()) + { + auto [t,x,y] = pq.top(); + pq.pop(); + if (arrival[x][y]!=-1) + continue; + arrival[x][y] = t; + if (x==m-1 && y==n-1) + break; + + for (int k=0; k<4; k++) + { + int i = x+dir[k].first; + int j = y+dir[k].second; + if (i<0||i>=m||j<0||j>=n) continue; + if (arrival[i][j] != -1) continue; + + if (grid[i][j]<=t+1) + pq.push({t+1, i, j}); + else if ((grid[i][j]-t)%2==0) + pq.push({grid[i][j]+1, i, j}); + else + pq.push({grid[i][j], i, j}); + } + } + + return arrival[m-1][n-1]; + } +}; diff --git a/BFS/2577.Minimum-Time-to-Visit-a-Cell-In-a-Grid/Readme.md b/BFS/2577.Minimum-Time-to-Visit-a-Cell-In-a-Grid/Readme.md new file mode 100644 index 000000000..a2ce41a08 --- /dev/null +++ b/BFS/2577.Minimum-Time-to-Visit-a-Cell-In-a-Grid/Readme.md @@ -0,0 +1,7 @@ +### 2577.Minimum-Time-to-Visit-a-Cell-In-a-Grid + +如果本题没有“you must move to any adjacent cell”这个要求,那么套用Dijkstra算法的模板即可求得到达右下角的最短路径。其中任意两条相邻的格子a->b之间的边权就是`max(1, grid[b]-arrival[a]`,表示到达a之后,可以立即走一步到达b,或者原地等待到b的准入时刻再进入b。 + +以上算法的问题在于,我们不能在原地等待。假设我们到达a的时刻是3,但是其相邻的b点的准入时刻是5。显然,我们不能在时刻4的时候进入b,但我们可以再时刻5的时候进入b吗?其实也不能。我们唯一能拖延时间的方法就是从a走到一个相邻的格子再走回a,这样可以拖延两秒的时间,再进入b的时刻就是6. 同理我们可以发现,从a到任何与其相邻的格子b,考虑到“往复拖延”的策略,所需要的时间增量必然是+1,+3,+5,... 直至大于等于b的准入时刻。 + +所以我们只需要更新Dijkstra的部分代码。假设到达a的时刻是t,其相邻格子的准入时间是grid[b],那么如果`grid[b]<=t+1`,说明最早可以在t+1的时刻进入b;如果`(grid[b]-t)%2==1`,那么我们可以反复横跳之后恰好在grid[b]时刻进入b;否则我们需要在`grid[b]+1`时刻进入b。 diff --git a/BFS/2662.Minimum-Cost-of-a-Path-With-Special-Roads/2662.Minimum-Cost-of-a-Path-With-Special-Roads_v1.cpp b/BFS/2662.Minimum-Cost-of-a-Path-With-Special-Roads/2662.Minimum-Cost-of-a-Path-With-Special-Roads_v1.cpp new file mode 100644 index 000000000..659589a21 --- /dev/null +++ b/BFS/2662.Minimum-Cost-of-a-Path-With-Special-Roads/2662.Minimum-Cost-of-a-Path-With-Special-Roads_v1.cpp @@ -0,0 +1,57 @@ +using LL = long long; +class Solution { + int dp[405][405]; +public: + int minimumCost(vector& start, vector& target, vector>& specialRoads) + { + specialRoads.push_back({start[0], start[1], target[0], target[1], abs(start[0]-target[0])+abs(start[1]-target[1])}); + + vector>point; + mapreverseMap; + for (int i=0; i; +class Solution { +public: + int minimumCost(vector& start, vector& target, vector>& specialRoads) + { + priority_queue, greater<>>pq; // {dist to node, node id} + pq.push({0, encode(start[0], start[1])}); + for (auto& road: specialRoads) + { + int x = road[2], y = road[3]; + pq.push({abs(start[0]-x)+abs(start[1]-y), encode(x,y)}); + } + + mapdist; + LL ret = INT_MAX; + while (!pq.empty()) + { + auto [len, id] = pq.top(); + pq.pop(); + if (dist.find(id)!=dist.end()) continue; + dist[id] = len; + auto [x,y] = decode(id); + + ret = min(ret, len + abs(x-target[0])+abs(y-target[1])); + + for (auto& road: specialRoads) + { + int x1 = road[0], y1 = road[1]; + int x2 = road[2], y2 = road[3]; + int cost = road[4]; + LL id2 = encode(x2,y2); + + if (dist.find(id2)==dist.end()) + pq.push({len + abs(x-x1)+abs(y-y1) + cost, id2}); + } + + } + + return ret; + } + + LL encode(LL x, LL y) {return (x<<32) + y;} + PLL decode(LL id) {return {id>>32, id%(1LL<<32)};} +}; diff --git a/BFS/2662.Minimum-Cost-of-a-Path-With-Special-Roads/Readme.md b/BFS/2662.Minimum-Cost-of-a-Path-With-Special-Roads/Readme.md new file mode 100644 index 000000000..ab5677766 --- /dev/null +++ b/BFS/2662.Minimum-Cost-of-a-Path-With-Special-Roads/Readme.md @@ -0,0 +1,13 @@ +### 2662.Minimum-Cost-of-a-Path-With-Special-Roads + +#### 解法1:Floyd +考虑到有200条road,意味着400个点。用n^3的floyd算法,也许可以在时间范围内勉强求得任意两点之间的最短距离。我们只需要在special roads里加一条从start到target的曼哈顿距离,就可以构图套用模板了。 + +注意本题需要将点去重,否则会TLE。 + +注意,本题的初始化包括:1.同一点的距离是0 2.任意两点之间的距离有曼哈顿距离保底 3.road的两点之间的距离可以更新为cost。 + +#### 解法2:Dijkstra +对于每条special road,它的起点其实都是无关紧要的,保底用start到其曼哈顿距离即可。只有这些special road的终点才是改变这张图拓扑关系的关键点(否则永远都是trivial的网格结构)。所以我们可以用Dijkstra算法,来更新start到各个road终点的最短距离。最后在所有的终点x里,挑一个最小的`start->x->target`的距离,其中`x->target`是曼哈顿距离。 + +更具体地,我们从pq里弹出当前某点p的最短距离len,那么我们就可以利用从x到y的road,更新从p到y的距离:`len + abs|p-x| + cost`. diff --git a/BFS/2714.Find-Shortest-Path-with-K-Hops/2714.Find-Shortest-Path-with-K-Hops.cpp b/BFS/2714.Find-Shortest-Path-with-K-Hops/2714.Find-Shortest-Path-with-K-Hops.cpp new file mode 100644 index 000000000..ceaf49038 --- /dev/null +++ b/BFS/2714.Find-Shortest-Path-with-K-Hops/2714.Find-Shortest-Path-with-K-Hops.cpp @@ -0,0 +1,37 @@ +using AI3 = array; +using PII = pair; +class Solution { + vectornext[500]; +public: + int shortestPathWithHops(int n, vector>& edges, int source, int destination, int k) + { + for (auto& edge: edges) + { + int a = edge[0], b = edge[1], w = edge[2]; + next[a].push_back({b,w}); + next[b].push_back({a,w}); + } + priority_queue, greater<>>pq; + pq.push({0, source, k}); + + vector>dist(n, vector(k+1, INT_MAX/2)); + + while (!pq.empty()) + { + auto [d, cur, t] = pq.top(); + pq.pop(); + if (dist[cur][t]!=INT_MAX/2) continue; + dist[cur][t] = d; + if (cur==destination) return d; + + for (auto [nxt, weight]:next[cur]) + { + if (dist[nxt][t]==INT_MAX/2) + pq.push({d+weight, nxt, t}); + if (t>=1 && dist[nxt][t-1]==INT_MAX/2) + pq.push({d, nxt, t-1}); + } + } + return -1; + } +}; diff --git a/BFS/2714.Find-Shortest-Path-with-K-Hops/Readme.md b/BFS/2714.Find-Shortest-Path-with-K-Hops/Readme.md new file mode 100644 index 000000000..e133f601f --- /dev/null +++ b/BFS/2714.Find-Shortest-Path-with-K-Hops/Readme.md @@ -0,0 +1,5 @@ +### 2714.Find-Shortest-Path-with-K-Hops + +此题和`2093.Minimum-Cost-to-Reach-City-With-Discounts`几乎一样。我们用Dijkstra求最短距离时需要有两个参量,即`dist[node][hops]`表示还剩hops机会时node离原点的最短距离。当某状态向量`(dist, node, hops)`弹出队列时,我们可以加入两种相邻的状态`{dist+weight, nxt, hops}`或者`{dist, nxt, hops-1}`. + +注意当PQ第一次弹出destination时,无论hops是多少,即可以输出最短距离。 diff --git a/BFS/2812.Find-the-Safest-Path-in-a-Grid/2812.Find-the-Safest-Path-in-a-Grid.cpp b/BFS/2812.Find-the-Safest-Path-in-a-Grid/2812.Find-the-Safest-Path-in-a-Grid.cpp new file mode 100644 index 000000000..d1b8b8dc8 --- /dev/null +++ b/BFS/2812.Find-the-Safest-Path-in-a-Grid/2812.Find-the-Safest-Path-in-a-Grid.cpp @@ -0,0 +1,78 @@ +using PII = pair; +class Solution { +public: + vectordir = {{0,1},{0,-1},{1,0},{-1,0}}; + int maximumSafenessFactor(vector>& grid) + { + int n = grid.size(); + + queueq; + for (int i=0; i=n||j<0||j>=n) continue; + if (grid[i][j]!=0) continue; + grid[i][j] = grid[x][y]+1; + q.push({i,j}); + } + } + } + + int left = 0, right = n; + while (left < right) + { + int mid = right-(right-left)/2; + if (isOK(mid, grid)) + left = mid; + else + right = mid-1; + } + + return left; + } + + bool isOK(int d, vector>& grid) + { + int n = grid.size(); + vector>visited(n, vector(n, 0)); + + if (grid[0][0]<=d) return false; + + queueq; + q.push({0,0}); + visited[0][0] = 1; + + while (!q.empty()) + { + auto [x,y] = q.front(); + q.pop(); + for (int k=0; k<4; k++) + { + int i = x+dir[k].first; + int j = y+dir[k].second; + if (i<0||i>=n||j<0||j>=n) continue; + if (grid[i][j]<=d) continue; + if (visited[i][j]) continue; + + visited[i][j] = 1; + if (i==n-1 && j==n-1) return true; + q.push({i,j}); + } + } + + return false; + } +}; diff --git a/BFS/2812.Find-the-Safest-Path-in-a-Grid/Readme.md b/BFS/2812.Find-the-Safest-Path-in-a-Grid/Readme.md new file mode 100644 index 000000000..e6a9d4540 --- /dev/null +++ b/BFS/2812.Find-the-Safest-Path-in-a-Grid/Readme.md @@ -0,0 +1,5 @@ +### 2812.Find-the-Safest-Path-in-a-Grid + +我们预先处理grid,通过多源BFS,求出每个格子到离其最近的thief的距离grid[i][j]。为了便于处理grid里已经存在数值为1的格子,在这里我们填充grid[i][j]表示该点"离最近的thief的距离+1". + +然后我们二分搜值这个safety factor。假设是d,那么我们尝试寻找一条从左上到右下的通路,使得该路径不能包含有grid[i][j]<=d的格子,再走一次bfs即可判断。然后根据判断值,不断调整d的大小直至收敛。 diff --git a/BFS/3552.Grid-Teleportation-Traversal/3552.Grid-Teleportation-Traversal.cpp b/BFS/3552.Grid-Teleportation-Traversal/3552.Grid-Teleportation-Traversal.cpp new file mode 100644 index 000000000..d18d7b1e5 --- /dev/null +++ b/BFS/3552.Grid-Teleportation-Traversal/3552.Grid-Teleportation-Traversal.cpp @@ -0,0 +1,67 @@ +using pii = pair; +const int INF = 1e9; + +class Solution { +public: + int minMoves(vector& grid) + { + int m = grid.size(), n = grid[0].size(); + vector> dist(m, vector(n, INF)); + deque dq; + + vector> portals(26); + for(int i = 0; i < m; i++) + { + for(int j = 0; j < n; j++){ + char c = grid[i][j]; + if(c >= 'A' && c <= 'Z') + portals[c - 'A'].push_back({i, j}); + } + } + vector used(26, false); + + dist[0][0] = 0; + dq.push_back({0, 0}); + + int dirs[4][2] = {{1,0},{-1,0},{0,1},{0,-1}}; + + while(!dq.empty()) + { + auto [x,y] = dq.front(); + dq.pop_front(); + int cd = dist[x][y]; + if(x == m-1 && y == n-1) + return cd; + + char c = grid[x][y]; + if(c >= 'A' && c <= 'Z') + { + int idx = c - 'A'; + if(!used[idx]) + { + used[idx] = true; + for(auto [nx, ny] : portals[idx]) + { + if(dist[nx][ny] > cd) { + dist[nx][ny] = cd; + dq.push_front({nx, ny}); + } + } + } + } + + for(auto &d : dirs) + { + int nx = x + d[0], ny = y + d[1]; + if(nx < 0 || nx >= m || ny < 0 || ny >= n) continue; + if(grid[nx][ny] == '#') continue; + if(dist[nx][ny] > cd + 1) { + dist[nx][ny] = cd + 1; + dq.push_back({nx, ny}); + } + } + } + + return -1; + } +}; diff --git a/BFS/3552.Grid-Teleportation-Traversal/Readme.md b/BFS/3552.Grid-Teleportation-Traversal/Readme.md new file mode 100644 index 000000000..14c4e7b4d --- /dev/null +++ b/BFS/3552.Grid-Teleportation-Traversal/Readme.md @@ -0,0 +1,3 @@ +### 3552.Grid-Teleportation-Traversal + +这是一个典型的用deque的BFS。因为题目中有“瞬移”的路径,假设是A到B,那么我们从队列中弹出A之后,不能将B压入队列的尾部,这样会影响找到最短路径的效率。因为A到B之间是不计时间的,我们将B的状态放在队首即可,故需要双端队列做这个容器。 diff --git a/BFS/3568.Minimum-Moves-to-Clean-the-Classroom/3568.Minimum-Moves-to-Clean-the-Classroom.cpp b/BFS/3568.Minimum-Moves-to-Clean-the-Classroom/3568.Minimum-Moves-to-Clean-the-Classroom.cpp new file mode 100644 index 000000000..7a5307a84 --- /dev/null +++ b/BFS/3568.Minimum-Moves-to-Clean-the-Classroom/3568.Minimum-Moves-to-Clean-the-Classroom.cpp @@ -0,0 +1,74 @@ +using state = tuple; +class Solution { +public: + int minMoves(vector& grid, int energy) { + int m = grid.size(), n = grid[0].size(); + + pairstart; + vector>litter_pos; + vector>litter_idx(m, vector(n,-1)); + + for (int i=0; i>>> visited( + m, vector>>( + n, vector>(energy + 1, vector(1 << L, false)))); + + visited[start.first][start.second][energy][0] = true; + + vector>dir={{0,1},{0,-1},{1,0},{-1,0}}; + + queue q; + q.push({start.first, start.second, energy, 0}); + int step = 0; + + while (!q.empty()) + { + int len = q.size(); + while (len--) + { + auto [x,y,e,mask] = q.front(); + q.pop(); + if (mask==(1<=m||ny<0||ny>=n) continue; + + char cell = grid[nx][ny]; + if (cell=='X') continue; + + int newEnergy = e-1; + if (newEnergy < 0) continue; + if (cell == 'R') newEnergy = energy; + + int newMask = mask; + if (grid[nx][ny]=='L') + { + int idx = litter_idx[nx][ny]; + newMask |= (1< o.dist; + } +}; + +class Solution { +public: + double minTime(int n, int k, int m, vector& time, vector& mul) { + vector>dist(1<(m, 1e300)); + priority_queuepq; + dist[0][0] = 0; + pq.push({0, 0, 0.0}); + int END = (1< dist[mask][stage]) continue; + if (mask==END) return d0; + + int rem = (~mask)&END; + for (int sub = rem; sub>0; sub = (sub-1)&rem) { + if (__builtin_popcount(sub)>k) continue; + int mx = 0; + for (int i=0; i o.dist; + } +}; +``` +注意对于运算符<的重载,这样我们定义`priority_queue`的时候,就会自动将dist最小的state排在top。 + +每次从PQ里拿出一组{mask, stage, d},我们需要在mask的补集(rem)里面枚举子集sub。将sub对应的人渡河之后(此时状态更新为`mask2=mask+sub`),再在“所有已经渡河的人”里(即mask2)选一个人i将传开回来,由此得到该回合最终的`mask3=mask2-(1< o.time; + } +}; +class Solution { + vector>next[100005]; +public: + int minTime(int n, vector>& edges) { + for (auto& e:edges) { + int u=e[0], v=e[1], start=e[2], end=e[3]; + next[u].push_back({v, start, end}); + } + + vectorvisited(n, -1); + priority_queuepq; + pq.push({0,0}); + + while (!pq.empty()) { + auto [node, time] = pq.top(); + // cout<s+1)) { + pq.push({v, s+1}); + } + if (time >= s && time <=e && (visited[v]==-1 || visited[v]>time+1)) { + pq.push({v, time+1}); + } + } + } + + return -1; + } +}; diff --git a/BFS/3604.Minimum-Time-to-Reach-Destination-in-Directed-Graph/Readme.md b/BFS/3604.Minimum-Time-to-Reach-Destination-in-Directed-Graph/Readme.md new file mode 100644 index 000000000..cadb887a1 --- /dev/null +++ b/BFS/3604.Minimum-Time-to-Reach-Destination-in-Directed-Graph/Readme.md @@ -0,0 +1,12 @@ +### 3604.Minimum-Time-to-Reach-Destination-in-Directed-Graph + +常规的Dijkstra的模版题。本题优先队里的元素需要定义状态 +```cpp +struct state { + int node, time; + bool operator<(state const& o) const { + return time > o.time; + } +}; +``` +对于出队列的一个状态{node,time},我们可知到达node的最短时间就是time。然后我们检查它周围的路径{v,start,end}。如果time& nums) { + int MAXA = *max_element(nums.begin(), nums.end()); + vectorspf(MAXA+1, 0); + for (int i=2; i<=MAXA;i++) { + if (spf[i]) continue; + for (int j=i; j<=MAXA; j+=i) + if (!spf[j]) spf[j]=i; + } + + vector>prime_to_idx(MAXA+1); + int n = nums.size(); + for (int i=0; i1) { + int p = spf[x]; + prime_to_idx[p].push_back(i); + while (x%p==0) x/=p; + } + } + + const int INF = 1e9; + vectordist(n, INF); + vectorused_prime(MAXA+1, 0); + dequeq; + dist[0] = 0; + q.push_back(0); + + while (!q.empty()) { + int i = q.front(); + q.pop_front(); + int d = dist[i]; + if (i==n-1) + return d; + + if (i+1=0 && dist[i-1]==INF) { + dist[i-1] = d+1; + q.push_back(i-1); + } + int x = nums[i]; + if (x>1 && spf[x]==x) { + if (used_prime[x]) continue; + used_prime[x] = 1; + for (int j: prime_to_idx[x]) { + if (dist[j]==INF) { + dist[j] = d+1; + q.push_back(j); + } + } + } + } + + return 0; + } +}; diff --git a/BFS/3629.Minimum-Jumps-to-Reach-End-via-Prime-Teleportation/Readme.md b/BFS/3629.Minimum-Jumps-to-Reach-End-via-Prime-Teleportation/Readme.md new file mode 100644 index 000000000..67a2810af --- /dev/null +++ b/BFS/3629.Minimum-Jumps-to-Reach-End-via-Prime-Teleportation/Readme.md @@ -0,0 +1,7 @@ +### 3629.Minimum-Jumps-to-Reach-End-via-Prime-Teleportation + +很容易判断,总体框架必然是BFS。 + +此题额外要求预处理nums里的每个质数与其倍数之间的映射关系,存入prime_to_idx中。一个高效的做法是在用埃氏筛判定1到M内的所有质数时,顺便记录下每个自然数的最小质因数(spf)。这样就方便我们对于nums的每个元素x做快速的质因数分解(不断去除以spf[x]),从而建立起它的所有质因子p到x的映射集合。 + +BFS的写法比较常规。从队列里弹出index=i后,考察是否需要将`i+1`, `i-1`以及`prime_to_idx[nums[i]]`(仅当nums[i]是质数时)放入队列。注意对于已经处理过的质数需要略过。 diff --git a/BFS/3690.Split-and-Merge-Array-Transformation/3690.Split-and-Merge-Array-Transformation.cpp b/BFS/3690.Split-and-Merge-Array-Transformation/3690.Split-and-Merge-Array-Transformation.cpp new file mode 100644 index 000000000..6268b03e3 --- /dev/null +++ b/BFS/3690.Split-and-Merge-Array-Transformation/3690.Split-and-Merge-Array-Transformation.cpp @@ -0,0 +1,45 @@ +class Solution { +public: + int minSplitMerge(vector& nums1, vector& nums2) { + if (nums1==nums2) return 0; + int n = nums1.size(); + set>visited; + visited.insert(nums1); + + queue>q; + q.push(nums1); + int step = 0; + + while (!q.empty()) { + int len = q.size(); + step++; + while (len--) { + auto cur = q.front(); + q.pop(); + + for (int L = 0; L < n; L++) + for (int R = L; Rsub(cur.begin()+L, cur.begin()+R+1); + vectorrem; + rem.insert(rem.end(), cur.begin(), cur.begin()+L); + rem.insert(rem.end(), cur.begin()+R+1, cur.end()); + + for (int pos = 0; pos<=rem.size(); pos++) { + vectornxt; + nxt.insert(nxt.end(), rem.begin(), rem.begin()+pos); + nxt.insert(nxt.end(), sub.begin(), sub.end()); + nxt.insert(nxt.end(), rem.begin()+pos, rem.end()); + + if (visited.find(nxt)!=visited.end()) continue; + visited.insert(nxt); + if (nxt==nums2) return step; + q.push(move(nxt)); + } + } + } + } + + return -1; + + } +}; diff --git a/BFS/3690.Split-and-Merge-Array-Transformation/Readme.md b/BFS/3690.Split-and-Merge-Array-Transformation/Readme.md new file mode 100644 index 000000000..c73728c81 --- /dev/null +++ b/BFS/3690.Split-and-Merge-Array-Transformation/Readme.md @@ -0,0 +1,5 @@ +### 3690.Split-and-Merge-Array-Transformation + +本题的数据量不大,数组元素数目很少,可以暴力(配合去重)。因为是求最少变换次数,显然选用BFS。 + +每次从队列里弹出一个数组,遍历所有的subarray作为sub,剩下的拼接为rem。然后在rem的任意位置插入sub,组成新的数组。 diff --git a/Binary_Search/1482.Minimum-Number-of-Days-to-Make-m-Bouquets/1482.Minimum-Number-of-Days-to-Make-m-Bouquets.cpp b/Binary_Search/1482.Minimum-Number-of-Days-to-Make-m-Bouquets/1482.Minimum-Number-of-Days-to-Make-m-Bouquets.cpp index ccced7347..dcb7dc116 100644 --- a/Binary_Search/1482.Minimum-Number-of-Days-to-Make-m-Bouquets/1482.Minimum-Number-of-Days-to-Make-m-Bouquets.cpp +++ b/Binary_Search/1482.Minimum-Number-of-Days-to-Make-m-Bouquets/1482.Minimum-Number-of-Days-to-Make-m-Bouquets.cpp @@ -3,7 +3,7 @@ class Solution { int minDays(vector& bloomDay, int m, int k) { int n = bloomDay.size(); - if (n& banned, int n, long long maxSum) { + banned.erase(std::unique(banned.begin(), banned.end()),banned.end()); sort(banned.begin(), banned.end()); presum.resize(banned.size()); diff --git a/Binary_Search/2560.House-Robber-IV/Readme.md b/Binary_Search/2560.House-Robber-IV/Readme.md index b5d00e82c..f6c071d3e 100644 --- a/Binary_Search/2560.House-Robber-IV/Readme.md +++ b/Binary_Search/2560.House-Robber-IV/Readme.md @@ -12,6 +12,6 @@ dp[i][1] = INT_MIN/2; 考虑第i个房子,如果`house[i]<=c`,我们可以选择抢,也可以选择不抢 ```cpp dp[i][0] = max(dp[i-1][0], dp[i-1][1]); -dp[i][1] = dp[i-1][0]; +dp[i][1] = dp[i-1][0]+1; ``` 由此将所有的dp[i][x]都更新。最后考察dp[n-1][x]能否大于k,即意味着在当前c的设置下,能否实现至少抢k个房子。 diff --git a/Binary_Search/2594.Minimum-Time-to-Repair-Cars/2594.Minimum-Time-to-Repair-Cars.cpp b/Binary_Search/2594.Minimum-Time-to-Repair-Cars/2594.Minimum-Time-to-Repair-Cars.cpp new file mode 100644 index 000000000..cf3e7b734 --- /dev/null +++ b/Binary_Search/2594.Minimum-Time-to-Repair-Cars/2594.Minimum-Time-to-Repair-Cars.cpp @@ -0,0 +1,30 @@ +using LL = long long; +class Solution { +public: + long long repairCars(vector& ranks, int cars) + { + LL left = 0, right = LLONG_MAX; + while (left < right) + { + LL mid = left + (right-left)/2; + if (isOK(mid, ranks, cars)) + right = mid; + else + left = mid+1; + } + return left; + } + + bool isOK(LL t, vector& ranks, int cars) + { + LL count = 0; + for (int r : ranks) + { + count += sqrt(t/r); + if (count >= cars) + return true; + } + return false; + + } +}; diff --git a/Binary_Search/2594.Minimum-Time-to-Repair-Cars/Readme.md b/Binary_Search/2594.Minimum-Time-to-Repair-Cars/Readme.md new file mode 100644 index 000000000..72ae59ab1 --- /dev/null +++ b/Binary_Search/2594.Minimum-Time-to-Repair-Cars/Readme.md @@ -0,0 +1,5 @@ +### 2594.Minimum-Time-to-Repair-Cars + +最基本的二分搜值。猜测一个时间t,看看在这个时间内所有人修车数目的总和是否大于等于cars。是的话试图减小t,否则的话试图增加t,直至收敛。 + +对于给定的t,每个人的修车数量就是`sqrt(t/r)`. diff --git a/Binary_Search/2604.Minimum-Time-to-Eat-All-Grains/2604.Minimum-Time-to-Eat-All-Grains.cpp b/Binary_Search/2604.Minimum-Time-to-Eat-All-Grains/2604.Minimum-Time-to-Eat-All-Grains.cpp new file mode 100644 index 000000000..5e1cfd2bf --- /dev/null +++ b/Binary_Search/2604.Minimum-Time-to-Eat-All-Grains/2604.Minimum-Time-to-Eat-All-Grains.cpp @@ -0,0 +1,55 @@ +class Solution { +public: + int minimumTime(vector& hens, vector& grains) + { + sort(hens.begin(), hens.end()); + sort(grains.begin(), grains.end()); + + int left = 0, right = INT_MAX; + while (left < right) + { + int mid = left + (right-left)/2; + if (isOK(mid, hens, grains)) + right = mid; + else + left = mid+1; + } + return left; + } + + bool isOK(int time, vector& hens, vector& grains) + { + int j = 0; + for (int i=0; iy,这不可能是最优解。 + +有了这个发现之后,接下来似乎还是无从下手,那就不妨二分搜值。显然我们会设定一个时间T,看看所有的母鸡能在此时间内把所有谷子都吃完。或者说,是否存在一种谷子区间的分配,能够在T里被各个母鸡吃到。如果可行,那么尝试降低T,否则我们就提高T,最终收敛到最优解。 + +现在考察这个判定函数。因为每个谷子都要被吃,显然我们就从第0号谷子开始考察:它必然是被第0号母鸡吃掉。假设0号谷子在0号母鸡左边,如果两者离得太远(超过了T),那么整体就返回无解。如果在范围内,那么意味着0号母鸡在移动到0号谷子的过程中遇到的所有谷子都能被吃掉。我们记0号母鸡移动到0号谷子的时间是t,那么母鸡在吃完0号谷子还需要返回再花时间t,此时如果还有剩余T-2t,那么就可以往右走,再多吃一点谷子,注意这一段是单程。由此我们可以确定0号母鸡吃的谷子的总数目,假设是j,那么下一个回合我们就考察第j个谷子和第1号母鸡之间的关系,再考察1号母鸡总共能吃几粒谷子,重复这个逻辑。 + +但是注意,在上面的0号母鸡策略中,其实还有另一种方案,就是先往右走,再折返,再往左边走t的时间保证吃掉0号谷子。这也是可行的。哪种方案更好呢?取决于0号母鸡往右边开拓的范围哪个更远。假设方案1比方案2更好,意味着 +``` +T - t*2 > (T-t) / 2 <=> T > 3*t +``` +也就是说,如果`T>3t`,我们就选取方案1,否则就选取方案2. + +由此我们顺次遍历谷子,将一个区间范围内的谷子归入下一个母鸡,在T的约束下确定这个区间范围。直至看能否把所有的谷子都分配完毕。 diff --git a/Binary_Search/2616.Minimize-the-Maximum-Difference-of-Pairs/2616.Minimize-the-Maximum-Difference-of-Pairs.cpp b/Binary_Search/2616.Minimize-the-Maximum-Difference-of-Pairs/2616.Minimize-the-Maximum-Difference-of-Pairs.cpp new file mode 100644 index 000000000..17fb3e7ba --- /dev/null +++ b/Binary_Search/2616.Minimize-the-Maximum-Difference-of-Pairs/2616.Minimize-the-Maximum-Difference-of-Pairs.cpp @@ -0,0 +1,32 @@ +class Solution { +public: + int minimizeMax(vector& nums, int p) + { + sort(nums.begin(), nums.end()); + int left = 0, right = INT_MAX; + while (left < right) + { + int mid = left + (right-left)/2; + if (isOK(nums, p, mid)) + right = mid; + else + left = mid+1; + } + return left; + } + + bool isOK(vector& nums, int p, int diff) + { + int n = nums.size(); + int count = 0; + for (int i=0; i= p); + } +}; diff --git a/Binary_Search/2616.Minimize-the-Maximum-Difference-of-Pairs/Readme.md b/Binary_Search/2616.Minimize-the-Maximum-Difference-of-Pairs/Readme.md new file mode 100644 index 000000000..34fc49dd2 --- /dev/null +++ b/Binary_Search/2616.Minimize-the-Maximum-Difference-of-Pairs/Readme.md @@ -0,0 +1,7 @@ +### 2616.Minimize-the-Maximum-Difference-of-Pairs + +我们首先容易想到的是将数组排序,这样我们选择的pairs必然都是相邻的元素。任何跳跃选择的pair都必然不会是最优解。接下来我们该如何选择这些pairs呢?此时陷入了困难。我们并不能贪心地找相邻最短的pair,比如这个例子:`1 3 4 6`,我们优先取{3,4}之后,剩下的{1,6}的差距更大了。 + +在正面突破没有思路的时候,不妨试一试反向的“猜答案”。二分搜值在这里恰好是适用的。假设最大间距是x,那么当x越大时,我们就越容易找p对符合条件的pairs(比如当x是无穷大时,pairs可以随意挑);反之当x越小时,就越不容易找到p对符合条件的pairs。以此不断调整x的大小,直至收敛。 + +于是接下来我们就考虑,假设最大间距是x,那么我们如何判定能否找到p对符合条件的pairs呢?为了尽量找到多的pairs,我们必然从小到大把这些元素都看一遍,尽量不浪费。假设最小的四个元素分别是abcd,并且他们彼此之间的间距都小于x,那么我们是否应该取a和b呢?如果取的话,那么可能带来的顾虑就是b就失去了和c配对的机会。不过这个顾虑是不必要的:如果我们选择了b和c,那么同样构造了一对,但a就白白浪费了。即使你可以将a与d配对且间距也小于x,那么也违背了我们之前的直觉,“我们永远只会取相邻的元素配对”。事实上(a,b)(c,d)的方案肯定是优于(b,c)(a,d)的。所以我们的结论就是,如果最小元素和它相邻元素的间距小于x,那么就贪心地配对;否则最小元素只能放弃。依次类推从小到大处理每一个元素,就可以知道我们最多能搞出多少个配对。 diff --git a/Binary_Search/2702.Minimum-Operations-to-Make-Numbers-Non-positive/2702.Minimum-Operations-to-Make-Numbers-Non-positive.cpp b/Binary_Search/2702.Minimum-Operations-to-Make-Numbers-Non-positive/2702.Minimum-Operations-to-Make-Numbers-Non-positive.cpp new file mode 100644 index 000000000..6a530d58b --- /dev/null +++ b/Binary_Search/2702.Minimum-Operations-to-Make-Numbers-Non-positive/2702.Minimum-Operations-to-Make-Numbers-Non-positive.cpp @@ -0,0 +1,29 @@ +class Solution { +public: + int minOperations(vector& nums, int x, int y) + { + sort(nums.rbegin(), nums.rend()); + int left = 0, right = INT_MAX/2; + while (left < right) + { + int mid = left+(right-left)/2; + if (isOK(mid, nums, x, y)) + right = mid; + else + left = mid+1; + } + return left; + } + + bool isOK(int k, vector& nums, int x, int y) + { + int count = 0; + for (int i=0; i k) return false; + } + return true; + } +}; diff --git a/Binary_Search/2702.Minimum-Operations-to-Make-Numbers-Non-positive/Readme.md b/Binary_Search/2702.Minimum-Operations-to-Make-Numbers-Non-positive/Readme.md new file mode 100644 index 000000000..79d01f3e9 --- /dev/null +++ b/Binary_Search/2702.Minimum-Operations-to-Make-Numbers-Non-positive/Readme.md @@ -0,0 +1,5 @@ +### 2702.Minimum-Operations-to-Make-Numbers-Non-positive + +此题很容易知道贪心的策略,肯定是将当前数组里最大的元素减去x,其他元素减去y。然后不断重复处理。但问题是如此暴力的模拟,在时间复杂度上无法接受。 + +此时二分搜值的想法就比较容易。我们尝试判定m次操作是否能将所有元素都降到0以下。关键之处在于我们可以将每次操作拆分为:将全部元素减去y,再挑一个元素减去x-y。那么m次操作必然是将所有元素都减掉了m个y,此外我们还有m次操作将剩余没有变成0的元素减去x-y。我们只要贪心的查看这些操作是否够将所有元素变成0即可。 diff --git a/Binary_Search/2819.Minimum-Relative-Loss-After-Buying-Chocolates/2819.Minimum-Relative-Loss-After-Buying-Chocolates.cpp b/Binary_Search/2819.Minimum-Relative-Loss-After-Buying-Chocolates/2819.Minimum-Relative-Loss-After-Buying-Chocolates.cpp new file mode 100644 index 000000000..52195fcfa --- /dev/null +++ b/Binary_Search/2819.Minimum-Relative-Loss-After-Buying-Chocolates/2819.Minimum-Relative-Loss-After-Buying-Chocolates.cpp @@ -0,0 +1,46 @@ +using LL = long long; +class Solution { + LL presum[100005]; +public: + vector minimumRelativeLosses(vector& prices, vector>& queries) + { + int n = prices.size(); + sort(prices.begin(), prices.end()); + + presum[0] = prices[0]; + for (int i=1; irets; + for (auto& arr: queries) + { + LL k = arr[0], m = arr[1]; + int left = 0, right = m; + while (left < right) + { + int mid = right - (right-left)/2; + if (mid==0 || mid==m) break; + if (prices[mid-1] < 2*k - prices[n-(m-mid)]) + left = mid; + else + right = mid-1; + } + int p = left; + LL ans1 = rangeSum(0, p-1) + 2*k*(m-p) - rangeSum(n-(m-p), n-1); + p++; + LL ans2 = rangeSum(0, p-1) + 2*k*(m-p) - rangeSum(n-(m-p), n-1); + rets.push_back(min(ans1, ans2)); + } + + return rets; + } + + LL rangeSum(int a, int b) + { + if (a>b) return 0LL; + if (a==0) + return presum[b]; + else + return presum[b]-presum[a-1]; + } +}; diff --git a/Binary_Search/2819.Minimum-Relative-Loss-After-Buying-Chocolates/Readme.md b/Binary_Search/2819.Minimum-Relative-Loss-After-Buying-Chocolates/Readme.md new file mode 100644 index 000000000..0ff68f4ea --- /dev/null +++ b/Binary_Search/2819.Minimum-Relative-Loss-After-Buying-Chocolates/Readme.md @@ -0,0 +1,5 @@ +### 2819.Minimum-Relative-Loss-After-Buying-Chocolates + +首先可以得出大致的策略,对于bob而言,要么选价格最便宜的(当价格pk时,代价函数是2k-p). 所以,选择的m件商品,必然在价格轴上一部分选在最左边,另一部分选在最右边。 + +假设我们选t件最便宜的,剩下m-t件是最贵的,那么该如何确定t的个数呢?我们希望选择商品的代价尽量远离峰值(p=k处),所以希望`price[t-1]`与`2k-price[n-(m-t)]`数值上尽量接近。否则因为t对两者影响的此消彼长,一方变低的话,另一方必然更高。所以我们尝试寻找最大的T,使得恰好`price[T-1] < 2k-price[n-(m-T)]`. 接下来,我们尝试T和T+1两个候选值,寻找两者之中能使总代价最优的解。总代价就是t件最便宜的代价`prices[0:t-1]`加上m-t件最贵的代价`2k*(m-t) - prices[n-(m-t): n-1]`. diff --git a/Binary_Search/2836.Maximize-Value-of-Function-in-a-Ball-Passing-Game/2836.Maximize-Value-of-Function-in-a-Ball-Passing-Game.cpp b/Binary_Search/2836.Maximize-Value-of-Function-in-a-Ball-Passing-Game/2836.Maximize-Value-of-Function-in-a-Ball-Passing-Game.cpp new file mode 100644 index 000000000..bd15bd280 --- /dev/null +++ b/Binary_Search/2836.Maximize-Value-of-Function-in-a-Ball-Passing-Game/2836.Maximize-Value-of-Function-in-a-Ball-Passing-Game.cpp @@ -0,0 +1,46 @@ +using LL = long long; +class Solution { +public: + long long getMaxFunctionValue(vector& receiver, long long k) + { + int n = receiver.size(); + int M = ceil(log(k)/log(2)); + vector>dp(n+1, vector(M+1)); + vector>pos(n+1, vector(M+1)); + + for (int i=0; ibits; + for (int i=0; i<=M; i++) + { + if ((k>>i)&1) + bits.push_back(i); + } + + LL ret = 0; + for (int i=0; i> next[10005]; + int count[10005][27]; + int parent[10005]; + int level[10005]; +public: + vector minOperationsQueries(int n, vector>& edges, vector>& queries) + { + for (auto& edge: edges) + { + int a = edge[0], b = edge[1], c = edge[2]; + next[a].push_back({b,c}); + next[b].push_back({a,c}); + } + + vectortemp(27); + dfs(0, 0, -1, temp); + parent[0] = -1; + + vectorrets; + for (auto query: queries) + { + int a = query[0], b = query[1]; + int lca = getLCA(0,a,b); + + vectortemp(27); + for (int i=1; i<=26; i++) + { + temp[i] += count[a][i]; + temp[i] += count[b][i]; + temp[i] -= 2*count[lca][i]; + } + + int sum = 0; + int mx = 0; + for (int i=1; i<=26; i++) + { + sum += temp[i]; + mx = max(mx, temp[i]); + } + + rets.push_back(sum - mx); + } + + return rets; + } + + void dfs(int cur, int l, int p, vector&temp) + { + for (auto& child: next[cur]) + { + if (child.first==p) continue; + int w = child.second; + + temp[w]+=1; + for (int i=1; i<=26; i++) + count[child.first][i] = temp[i]; + + parent[child.first] = cur; + level[child.first] = l+1; + + dfs(child.first, l+1, cur, temp); + temp[w]-=1; + } + } + + int getLCA(int node, int p, int q) + { + while (1) + { + if (level[p]>level[q]) + { + p = parent[p]; + } + else if (level[p]> next[10005]; + int count[10005][27]; + int parent[10005]; + int level[10005]; + int ancestor[10005][18]; +public: + vector minOperationsQueries(int n, vector>& edges, vector>& queries) + { + for (auto& edge: edges) + { + int a = edge[0], b = edge[1], c = edge[2]; + next[a].push_back({b,c}); + next[b].push_back({a,c}); + } + + vectortemp(27); + dfs(0, 0, -1, temp); + parent[0] = -1; + + for (int i=0; irets; + for (auto query: queries) + { + int a = query[0], b = query[1]; + // int lca = getLCA(0,a,b); + int lca = getLCA(a,b); + + vectortemp(27); + for (int i=1; i<=26; i++) + { + temp[i] += count[a][i]; + temp[i] += count[b][i]; + temp[i] -= 2*count[lca][i]; + } + + int sum = 0; + int mx = 0; + for (int i=1; i<=26; i++) + { + sum += temp[i]; + mx = max(mx, temp[i]); + } + + rets.push_back(sum - mx); + } + + return rets; + } + + void dfs(int cur, int l, int p, vector&temp) + { + for (auto& child: next[cur]) + { + if (child.first==p) continue; + int w = child.second; + + temp[w]+=1; + for (int i=1; i<=26; i++) + count[child.first][i] = temp[i]; + + parent[child.first] = cur; + level[child.first] = l+1; + + dfs(child.first, l+1, cur, temp); + temp[w]-=1; + } + } + + int getKthAncestor(int i, int k) + { + int cur = i; + for (int j=0; j<=17; j++) + { + if ((k>>j)&1) + cur = ancestor[cur][j]; + } + return cur; + } + + int getLCA(int a, int b) + { + while (level[a]!=level[b]) + { + if (level[a]q路径上的每种边权数目,即`count[p][j]+count[q][j]-2*count[lca][j]`,我们遍历一下j,就可以知道路径长度以及出现最多次的边权个数,两者之差就是query的答案。 + +那么如何求lca呢?我们需要在DFS的过程中,顺便知道每个节点的深度level[i]以及它的父节点parent[i].这样,我们先将p,q两点中较深的那个上溯到与另一个相同的深度,然后两者再一层一层共同向上追溯直至它们汇合,这个节点就是它们的LCA。这理论上是o(N)的算法。 + +有一个log(N)的LCA算法,就是利用binary lifting. 我们先利用parent的信息,预先计算出ancestor[i][j],表示节点i向上数第2^j层的祖先。这样我们就可以写出时间复杂度是log(n)的getKthAncestor的函数。对于任意的p与q,我们先计算出它们的深度差,用getKthAncestor将较深的那个节点拉至与另一个节点相同。然后用二分搜值,寻找最小的k,使得p与q的getKthAncestor相同,那么这个相同的节点就是它们的LCA。总的时间复杂度仍然是log(n). diff --git a/Binary_Search/2861.Maximum-Number-of-Alloys/2861.Maximum-Number-of-Alloys.cpp b/Binary_Search/2861.Maximum-Number-of-Alloys/2861.Maximum-Number-of-Alloys.cpp new file mode 100644 index 000000000..127a3378d --- /dev/null +++ b/Binary_Search/2861.Maximum-Number-of-Alloys/2861.Maximum-Number-of-Alloys.cpp @@ -0,0 +1,35 @@ +using LL = long long; +class Solution { +public: + int maxNumberOfAlloys(int n, int k, int budget, vector>& composition, vector& stock, vector& cost) + { + int ret = 0; + for (auto& comp : composition) + { + int left = 0, right = INT_MAX/2; + while (left < right) + { + int mid = right-(right-left)/2; + if (isOK(mid, n, budget, comp, stock, cost)) + left = mid; + else + right = mid-1; + } + ret = max(ret, left); + } + + return ret; + } + + bool isOK(int t, int n, int budget, vector&comp, vector& stock, vector& cost) + { + LL total = 0; + for (int i=0; i budget) + return false; + } + return true; + } +}; diff --git a/Binary_Search/2861.Maximum-Number-of-Alloys/Readme.md b/Binary_Search/2861.Maximum-Number-of-Alloys/Readme.md new file mode 100644 index 000000000..1ff0524db --- /dev/null +++ b/Binary_Search/2861.Maximum-Number-of-Alloys/Readme.md @@ -0,0 +1,3 @@ +### 2861.Maximum-Number-of-Alloys + +注意:All alloys must be created with the same machine. 对于每个machine,我们用二分搜值来确定在不超过budget的约束下、最多能生产alloy的个数。最后对所有机器取最大值。 diff --git a/Binary_Search/2972.Count-the-Number-of-Incremovable-Subarrays-II/2972.Count-the-Number-of-Incremovable-Subarrays-II.cpp b/Binary_Search/2972.Count-the-Number-of-Incremovable-Subarrays-II/2972.Count-the-Number-of-Incremovable-Subarrays-II.cpp new file mode 100644 index 000000000..6d04a7d92 --- /dev/null +++ b/Binary_Search/2972.Count-the-Number-of-Incremovable-Subarrays-II/2972.Count-the-Number-of-Incremovable-Subarrays-II.cpp @@ -0,0 +1,36 @@ +using LL = long long; +class Solution { +public: + long long incremovableSubarrayCount(vector& nums) + { + int n = nums.size(); + nums.insert(nums.begin(), INT_MIN); + nums.push_back(INT_MAX); + + int l = 1; + while (l<=n) + { + if (nums[l]0) + { + if (nums[r]>nums[r-1]) r--; + else break; + } + if (r0) + { + if (nums[r]>nums[r-1]) r--; + else break; + } +``` +对于[1:L]里的每一个位置i,我们可以在[R,n]用二分找到恰好大于nums[i]的位置j。那么符合条件的后段的左边界可以是j,j+1,...,n,总共有n+1-j个。 + +但是以上的思考意识强制要求前段、中段、后段都不能为空,而忽略了对应的三种情况:前段为空,中段为空、或者后段为空。需要额外考虑。 + +1. 如果中段为空,那么说明nums整体都是递增的,直接返回nums里的子区间的数目:n(n-1)/2+n. +2. 如果前端为空,或者后端为空,我们可以有一个巧妙的处理,使得之前的逻辑依然适用。在nums前添加一个无穷小(index是0),后面添加一个无穷大(index是n+1)。这样,i的遍历可以从0开始(意味着其实左段为空);而在[R:n+1]区间里进行二分的过程中可能会找到n+1这个位置(即认为后段的左边界是n+1),这其实就意味着允许了后段为空。 diff --git a/Binary_Search/3048.Earliest-Second-to-Mark-Indices-I/3048.Earliest-Second-to-Mark-Indices-I.cpp b/Binary_Search/3048.Earliest-Second-to-Mark-Indices-I/3048.Earliest-Second-to-Mark-Indices-I.cpp new file mode 100644 index 000000000..7349d072e --- /dev/null +++ b/Binary_Search/3048.Earliest-Second-to-Mark-Indices-I/3048.Earliest-Second-to-Mark-Indices-I.cpp @@ -0,0 +1,53 @@ +class Solution { + int n,m; +public: + int earliestSecondToMarkIndices(vector& nums, vector& changeIndices) + { + n = nums.size(); + m = changeIndices.size(); + nums.insert(nums.begin(), 0); + changeIndices.insert(changeIndices.begin(), 0); + + int left=1, right=m; + while (left < right) + { + int mid = left + (right-left)/2; + + if (isOK(mid, nums, changeIndices)) + right = mid; + else + left = mid+1; + } + + if (!isOK(left, nums, changeIndices)) return -1; + else return left; + } + + bool isOK(int m, vector& nums, vector& changeIndices) + { + vectorlast(n+1); + for (int i=1; i<=m; i++) + last[changeIndices[i]]=i; + + for (int i=1; i<=n; i++) + if (last[i]==0) return false; + + int count = 0; + for (int i=1; i<=m; i++) + { + int idx = changeIndices[i]; + + if (i!=last[idx]) + { + count++; + } + else + { + count -= nums[idx]; + if (count < 0) return false; + } + } + + return true; + } +}; diff --git a/Binary_Search/3048.Earliest-Second-to-Mark-Indices-I/Readme.md b/Binary_Search/3048.Earliest-Second-to-Mark-Indices-I/Readme.md new file mode 100644 index 000000000..7a08e8eb3 --- /dev/null +++ b/Binary_Search/3048.Earliest-Second-to-Mark-Indices-I/Readme.md @@ -0,0 +1,7 @@ +### 3048.Earliest-Second-to-Mark-Indices-I + +首先要看出这道题存在单调性,肯定时间越多的话,越有机会将所有元素都清零并标记。由此我们考虑尝试二分搜值。二分法的优点是,不用直接求“最优解”,而是转化为判定“可行解”,难度上会小很多。 + +本题里,我们需要在给定时间s的情况下,问能否将所有元素都清零并标记。对于一个给定的index而言,我们必须在对其“标记”前完成清零,因此我们肯定会将“标记”操作尽量延后,方便腾出更多时间做减一的操作。显然,我们会贪心地将index最后一次出现的时刻做“标记”操作;而如果index出现了不止一次多次,那么除了在最后一次的时刻做“标记”外,其余的时刻都会留作做“减一”操作(但不一定针对nums[idx])。为了顺利能够在最后一次index出现的时候做标记,我们需要保证之前积累的“减一”操作足够多,能够大于等于nums[idx]即可。于是我们只要顺着index出现的顺序,模拟上述的操作:要么积累“减一”操作count(如果不是最后一个出现),要么进行“标记”操作(如果是最后一次操作)。对于后者,能进行“标记”操作的前提是已经对那个index的数进行多次减一至零,故要求`count>=nums[idx]`. + +如果能够顺利地走完changeIndices[1:s]、并且将所有的nums都完成“标记”的话,就说明s秒能够实现目标。 diff --git a/Binary_Search/3049.Earliest-Second-to-Mark-Indices-II/3049.Earliest-Second-to-Mark-Indices-II.cpp b/Binary_Search/3049.Earliest-Second-to-Mark-Indices-II/3049.Earliest-Second-to-Mark-Indices-II.cpp new file mode 100644 index 000000000..f13c3cacc --- /dev/null +++ b/Binary_Search/3049.Earliest-Second-to-Mark-Indices-II/3049.Earliest-Second-to-Mark-Indices-II.cpp @@ -0,0 +1,67 @@ +using LL = long long; +class Solution { + int n,m; +public: + int earliestSecondToMarkIndices(vector& nums, vector& changeIndices) + { + n = nums.size(); + m = changeIndices.size(); + + nums.insert(nums.begin(), 0); + changeIndices.insert(changeIndices.begin(), 0); + + int left=1, right=m; + while (left < right) + { + int mid = left + (right-left)/2; + + if (isOK(mid, nums, changeIndices)) + right = mid; + else + left = mid+1; + } + + if (!isOK(left, nums, changeIndices)) return -1; + else return left; + } + + bool isOK(int t, vector&nums, vector changeIndices) + { + if (tfirst(n+1, 0); + for (int i=1; i<=t; i++) + { + if (first[changeIndices[i]]==0 && nums[changeIndices[i]]!=0) + first[changeIndices[i]]=i; + else + changeIndices[i] = 0; + } + + LL total = accumulate(nums.begin(), nums.end(), 0ll); + + multisetresets; + for (int i=t; i>=1; i--) + { + int idx = changeIndices[i]; + + if (idx == 0) continue; + + int marks = (t-i+1) - (resets.size() + 1); + if (resets.size() +1 > marks) + { + resets.insert(nums[idx]); + resets.erase(resets.begin()); + } + else + { + resets.insert(nums[idx]); + } + } + + LL total_clear = 0; + for (int x: resets) total_clear+=x; + + return total_clear + (t-n-resets.size()) >= total; + } +}; diff --git a/Binary_Search/3049.Earliest-Second-to-Mark-Indices-II/Readme.md b/Binary_Search/3049.Earliest-Second-to-Mark-Indices-II/Readme.md new file mode 100644 index 000000000..a29d5808e --- /dev/null +++ b/Binary_Search/3049.Earliest-Second-to-Mark-Indices-II/Readme.md @@ -0,0 +1,11 @@ +### 3049.Earliest-Second-to-Mark-Indices-II + +首先,容易看出此题的答案具有单调性。时间越长,就越容易有清零的机会,也就越容易实现目标。所以我们在最外层套用二分搜值的框架,将求“最优解”的问题,转化为判定“可行解”的问题。 + +假设给出T秒的时刻,如何判定是否可行呢?我们发现,“清零”操作的性价比是非常高的,如果对于某个index有机会做“清零”操作,我们必然这样做(除非nums[idx]本身就是零)。如果对于某个index,它在时间序列里出现了多次,我们会在哪个时候去清零呢?相对而言我们尽早清零是最优的选择,因为如果你晚些时候去做清零操作,可能存在一个风险:后续没有足够的机会取做“标记”操作了。由此,我们可以在时间序列changeIndices里面,预处理得到哪些时刻我们是在做“清零”。 + +至此,我们知道哪些时候做“清零”,其余的时候基本首选就是做“减一”,而唯一的制约因素就是要在最后留有足够“标记”的机会。当然并不是无脑的选最后n秒都做“标记”,因为有些“清零”可能在很靠后的时刻才会发生。怎么制定策略呢?这就需要从后往前去安排。 + +我们维护一个multiset叫做resets,里面存放那些确定要进行清零操作的nums的数值。当我们从后往前遍历的时候,判定时刻i是否能够进行“清零”,有以下两个条件:1.时刻i本身正是之前已经“计划”进行清零的时刻。2.假设i时刻进行清零的话,要保证在剩余的[i:t]的时间里,进行清零的数量(即multiset里面的元素个数)要小于剩余时间的一半,这样才能保证这些被清零的元素有机会被“标记”。如果不满足条件怎么办呢,这意味着我们不能增加这个清零名额,但是可以“调换”一个清零名额,将resets里面腾出一个来转给当前的index做清零。我们这样做有什么好处呢?这是因为也许可以减少“减一”操作的次数。假设当前时刻的元素如果做清零的话效果是-5,而resets里面有一个元素做清零其效果是-3,那么显然我们需要将resets里面做清零的元素拿出来留给当前元素。这叫做“反悔贪心”。 + +最终从后往前走完一遍之后,我们就知道哪些元素是真正需要被实施“清零”的。刨去这些之外,最后的n个时刻显然就是做“标记”操作的。这个安排保证了所有被清零的元素都能够得到“标记”。剩下的时刻就是应该做“减一”操作:我们必须要求所有“减一”操作的效果,加上“清零”操作的效果,最终一定是要大于nums里元素的总和。满足这些之后,才能判定目标在时间t内可行。 diff --git a/Binary_Search/3097.Shortest-Subarray-With-OR-at-Least-K-II/3097.Shortest-Subarray-With-OR-at-Least-K-II.cpp b/Binary_Search/3097.Shortest-Subarray-With-OR-at-Least-K-II/3097.Shortest-Subarray-With-OR-at-Least-K-II.cpp new file mode 100644 index 000000000..8df5624a5 --- /dev/null +++ b/Binary_Search/3097.Shortest-Subarray-With-OR-at-Least-K-II/3097.Shortest-Subarray-With-OR-at-Least-K-II.cpp @@ -0,0 +1,45 @@ +class Solution { +public: + int minimumSubarrayLength(vector& nums, int k) + { + int n = nums.size(); + int left = 1, right = n; + while (left < right) + { + int mid = left + (right-left)/2; + if (isOK(nums, k, mid)) + right = mid; + else + left = mid+1; + } + if (!isOK(nums, k, left)) return -1; + else return left; + } + + bool isOK(vector&nums, int k, int len) + { + vectorcount(31); + for (int i=0; i>j)&1); + } + + for (int i=len-1; i>j)&1); + + int sum = 0; + for (int j=0; j<31; j++) + if (count[j]>0) sum += (1<= k) return true; + + for (int j=0; j<31; j++) + count[j] -= ((nums[i-len+1]>>j)&1); + } + + return false; + } +}; diff --git a/Binary_Search/3097.Shortest-Subarray-With-OR-at-Least-K-II/Readme.md b/Binary_Search/3097.Shortest-Subarray-With-OR-at-Least-K-II/Readme.md new file mode 100644 index 000000000..c9088556d --- /dev/null +++ b/Binary_Search/3097.Shortest-Subarray-With-OR-at-Least-K-II/Readme.md @@ -0,0 +1,5 @@ +### 3097.Shortest-Subarray-With-OR-at-Least-K-II + +对于bitwise OR的操作,最大的特点是,OR的对象越多,答案越大。于是本题的答案显然具有单调性,越长的subarray越容易得到超过K的结果。所以我们只需要二分搜索长度即可。 + +于是本题就转化成了,对于一个固定长度L,判断是否存在这样长度的滑窗,使得里面元素的bitwise OR的结果大于等于K。我们只需要在滑窗移动的过程中,记录每个bit位上出现过多少次1即可。只要存在至少一个1,那么bitwise OR的结果在该位上就是1. diff --git a/Binary_Search/3399.Smallest-Substring-With-Identical-Characters-II/3399.Smallest-Substring-With-Identical-Characters-II.cpp b/Binary_Search/3399.Smallest-Substring-With-Identical-Characters-II/3399.Smallest-Substring-With-Identical-Characters-II.cpp new file mode 100644 index 000000000..5a0c7928d --- /dev/null +++ b/Binary_Search/3399.Smallest-Substring-With-Identical-Characters-II/3399.Smallest-Substring-With-Identical-Characters-II.cpp @@ -0,0 +1,61 @@ +class Solution { +public: + int minLength(string s, int numOps) + { + vectorarr; + vectornums; + for (auto ch: s) nums.push_back(ch-'0'); + + int n = s.size(); + for (int i=0; iarr, vector&nums, int len, int numOps) + { + if (len==1) + { + int count = 0; + for (int i=0; inumOps) + return false; + } + return true; + } +}; diff --git a/Binary_Search/3399.Smallest-Substring-With-Identical-Characters-II/Readme.md b/Binary_Search/3399.Smallest-Substring-With-Identical-Characters-II/Readme.md new file mode 100644 index 000000000..b32e12466 --- /dev/null +++ b/Binary_Search/3399.Smallest-Substring-With-Identical-Characters-II/Readme.md @@ -0,0 +1,18 @@ +### 3399.Smallest-Substring-With-Identical-Characters-II + +容易发现,只要操作次数越多,就越容易将最长的identical-chracter sbustring长度降下来,所以很明显适合二分搜值的框架。 + +于是问题转变成给定一个len,问是否能在numOps次flip操作内,使得s里不存在超过长度len的identical substring。 + +我们将原字符串里进行预处理,分割为一系列由相同字符组成的子串。对于任意一段长度为x的子串,我们至少需要做多少次flip呢?很明显,贪心思想就可以得到最优解,即每隔len个字符,我们就做一次flip。假设最少做t次flip,我们需要满足 +``` +x <= (len+1) * t + len +``` +才能保证x里面不会有超过长度为len的identical substring。于是求得`t>=(x-k)/(k+1)`。不等式右边是小数时,取上界整数。 + +但是此题有一个坑。如果len是1的话,那么当x是偶数时,会给下一段x带来困扰。比如说s=00001111,我们在处理第一段0000时,会得到贪心的做法做两次flip使得其变成0101。我们发现这样的话下一段的1111其实需要处理的长度是5,给算法带来了极大的不便。比较简单的处理方法就是对len=1的情况特别处理,跳出之前的思维模式,只要考虑将s强制转换为01相间的字符串,计算需要做的flip即可。 + +那为什么len是2的时候我们就不用担心呢?距离s=000111,如果按照贪心的做法,对于第一段000我们需要做一次flip使得其变成001,似乎依然会影响到下一段的111. 但事实上,对于一段我们不需要变换成001,可以将最右端的1随意往左调动一下,变换成010。结果就是flip的次数不变的前提下,依然可以保证不会出现长度超过2的identical substring。注意,回顾一下二分搜值的框架,我们不要求一定要构造出长度为2的identical substring。 + +同理当len=3时,也不会出现类似影响下一段的困扰。之前的计算t的表达式依然可以适用。 + diff --git a/Binary_Search/3449.Maximize-the-Minimum-Game-Score/3449.Maximize-the-Minimum-Game-Score.cpp b/Binary_Search/3449.Maximize-the-Minimum-Game-Score/3449.Maximize-the-Minimum-Game-Score.cpp new file mode 100644 index 000000000..eeea34179 --- /dev/null +++ b/Binary_Search/3449.Maximize-the-Minimum-Game-Score/3449.Maximize-the-Minimum-Game-Score.cpp @@ -0,0 +1,54 @@ +using LL = long long; +class Solution { + int n; +public: + long long maxScore(vector& points, int m) + { + n = points.size(); + LL left = 0, right = 1e15; + while (left < right) + { + LL mid = right - (right-left)/2; + if (checkOK(points, m, mid)) + left = mid; + else + right = mid-1; + } + return left; + } + + bool checkOK(vector& points, LL M, LL P) + { + LL count = 1; + LL cur = points[0]; + + for (int i=0; i=P) return true; + LL d = (P-cur-1) / points[i] + 1; + return count+d*2 <= M; + } + + if (cur>=P) + { + count++; + if (count > M) return false; + cur = points[i+1]; + } + else + { + LL d = (P-cur-1) / points[i] + 1; + if (i==n-2 && count+d*2<=M && points[i+1]*d>=P) + return true; + + count += 2*d+1; + if (count > M) return false; + cur = points[i+1] * (d+1); + } + } + + return true; + } +}; diff --git a/Binary_Search/3449.Maximize-the-Minimum-Game-Score/Readme.md b/Binary_Search/3449.Maximize-the-Minimum-Game-Score/Readme.md new file mode 100644 index 000000000..53d35b1f8 --- /dev/null +++ b/Binary_Search/3449.Maximize-the-Minimum-Game-Score/Readme.md @@ -0,0 +1,11 @@ +### 3449.Maximize-the-Minimum-Game-Score + +考虑到m的范围异常的大,本题极有可能是二分搜值。我们猜测一个值X,然后检验是否能在m次移动后,使得所有的元素都大于等于X。 + +移动的策略似乎很明显可以贪心。当我在0号位置的时候,如果还没有实现得分大于X,必然会通过先朝右再朝左的反复横跳d次,直至满足0号位置大于等于X。为什么只选择在0号和1号位置的反复横跳而不是更大的幅度?感觉没有必要。如果更大幅度的反复横跳,不仅在0号位置和1号位置上各自增加d次赋分,而且会在2号及之后的位置上也增加d次赋分,但这些赋分是否值得呢?不见得。因此,我们只需要老老实实每次做幅度为1的来回横跳即可。 + +综上,我们的算法是:当我们来到i时,查看在该位置是否已经超过了预期得分。如果没有,那就计算还需要几次赋分(假设记作d次)。然后就再做`=>(i+1)=>i`的d次反复移动。在i位置上满足之后,再移动依次到i+1的位置上,此时注意我们已经在i+1的位置上得到了`points[i+1]*(d+1)`的分数。然后重复上述的过程。 + +需要特别注意的边界逻辑有两个地方: +1. 如果走到了最后一个位置,仍没有超过预期得分,那么只能进行“往左再往右”的反复横跳。 +2. 如果走到了倒数第二个位置,经过几次横跳之后,发现在此位置和下一个位置都已经满足了得分预期,那么最后一步可以不用再走了。 diff --git a/Binary_Search/3464.Maximize-the-Distance-Between-Points-on-a-Square/3464.Maximize-the-Distance-Between-Points-on-a-Square.cpp b/Binary_Search/3464.Maximize-the-Distance-Between-Points-on-a-Square/3464.Maximize-the-Distance-Between-Points-on-a-Square.cpp new file mode 100644 index 000000000..c8cebf22b --- /dev/null +++ b/Binary_Search/3464.Maximize-the-Distance-Between-Points-on-a-Square/3464.Maximize-the-Distance-Between-Points-on-a-Square.cpp @@ -0,0 +1,73 @@ +using ll = long long; +class Solution { + vectorarr; + int next[15000]; + int n; + ll side; +public: + ll pos(int j) { + if (j= i+n) { + flag = false; + break; + } + } + if (pos(i)-pos(cur%n)>& points, int k) { + this->n = points.size(); + this->side = side; + for (auto& p: points) { + if (p[0]==0) + arr.push_back(p[1]); + else if (p[1]==side) + arr.push_back(side+p[0]); + else if (p[0]==side) + arr.push_back(2ll*side+side-p[1]); + else if (p[1]==0) + arr.push_back(3ll*side+side-p[0]); + } + + sort(arr.begin(), arr.end()); + + int low = 0, high = side; + while (low < high) { + int mid = high - (high-low)/2; + if (isOK(mid, k)) + low = mid; + else + high = mid-1; + } + return low; + } +}; diff --git a/Binary_Search/3464.Maximize-the-Distance-Between-Points-on-a-Square/Readme.md b/Binary_Search/3464.Maximize-the-Distance-Between-Points-on-a-Square/Readme.md new file mode 100644 index 000000000..8d1439d80 --- /dev/null +++ b/Binary_Search/3464.Maximize-the-Distance-Between-Points-on-a-Square/Readme.md @@ -0,0 +1,22 @@ +### 3464.Maximize-the-Distance-Between-Points-on-a-Square + +我们沿着原点顺时针将所有的点放入一个数组,数组元素是每个点与原点的距离(沿着边行走)。注意题目中k大于等于4,说明点和点之间的最小曼哈顿距离不可能大于side。否则四个点绕一圈,总距离就大于四倍边长了。 + +我们可以尝试二分搜索答案。假设猜测最小间隔距离是d,那么我们可以将任意一点作为起点,以d为极限间隔找到下一个点,以此类推找到k个点。如果第k个点没有“套圈”起点,并且离起点的距离依然大于d,那么就是一个符合条件的方案。因为k很小,我们遍历所有点作为起点都尝试一遍,时间复杂度为o(nk),加上二分搜索的框架,总的时间复杂度是可以接受。 + +更具体的做法是,我们先用双指针,求得每个点i右边距离恰好不超过d的点next[i]。注意i的编号范围是0到n-1,如果i是接近套圈靠近原点的位置,next[i]的位置也可能会越过原点。故我们定义next[i]的范围是0到2n-1。 + +假设我们从p开始,做k次跨度为d跳转时,应该写成这样: +```cpp +for (int t=0; t=n),那么它的下一个超出了next的定义域,故我们需要用`next[p%n]`。同时因为p的下一个必然也是越过原点的,故我们需要再加n。 + +在任何时刻,如果p点套圈了当初的起点(即p>=start+n),这说明无法实现在四周分布k个点的要求(因为第k个位置与第一个位置重合)。此外,即使p没有套圈起点,但是距离与起点少于d,也是说明无法实现要求。其余的情况,都说明有解。 + +注意,本题是一定有解的,故二分搜索的收敛解就是最优解。 diff --git a/Binary_Search/3534.Path-Existence-Queries-in-a-Graph-II/3534.Path-Existence-Queries-in-a-Graph-II.cpp b/Binary_Search/3534.Path-Existence-Queries-in-a-Graph-II/3534.Path-Existence-Queries-in-a-Graph-II.cpp new file mode 100644 index 000000000..2b66e7612 --- /dev/null +++ b/Binary_Search/3534.Path-Existence-Queries-in-a-Graph-II/3534.Path-Existence-Queries-in-a-Graph-II.cpp @@ -0,0 +1,69 @@ +using ll = long long; +const int MAXN = 100000; +const int LOGN = 17; +class Solution { + int up[MAXN][LOGN+1]; +public: + ll stepUp(int u, int k) { + for (int i=LOGN; i>=0; i--) { + if ((k>>i)&1) { + u = up[u][i]; + } + } + return u; + } + + vector pathExistenceQueries(int n, vector& nums, int maxDiff, vector>& queries) { + vector>arr; + for (int i=0; iidx; + for (int i=0; irets; + for (auto& q: queries) { + if (q[0]==q[1]) { + rets.push_back(0); + continue; + } + int u = idx[q[0]], v = idx[q[1]]; + if (u>v) swap(u,v); + + int low = 1, high = 1e5; + while (low < high) { + int mid = low + (high-low)/2; + int k = stepUp(u, mid); + if (arr[k].first >= arr[v].first) + high = mid; + else + low = mid+1; + } + int k = stepUp(u, low); + if (arr[k].first >= arr[v].first) + rets.push_back(low); + else + rets.push_back(-1); + } + + return rets; + } +}; diff --git a/Binary_Search/3534.Path-Existence-Queries-in-a-Graph-II/Readme.md b/Binary_Search/3534.Path-Existence-Queries-in-a-Graph-II/Readme.md new file mode 100644 index 000000000..07f411383 --- /dev/null +++ b/Binary_Search/3534.Path-Existence-Queries-in-a-Graph-II/Readme.md @@ -0,0 +1,13 @@ +### 3534.Path-Existence-Queries-in-a-Graph-II + +我们将所有的数按照从小到大的顺序排列之后,本题的问题就是:给出任意两点ux, x-y, y->z, ... 如果最终的位置能够超越v,那么答案就是肯定。反之,如果问{u,v}之间最少用多少步可以实现跨越,那么显然我们可以用二分搜值再验证,从而逼近最优解。 + +对于验证的过程,我们如果线性地去模拟k次跳跃,时间是不够的。因此我们会用到binary lifting(倍增)算法。不仅计算x和它一步所能跳跃的最远点y之间的路径,而且还预处理`up[x][k]`,表示从x点往右跳跃`2^k`步所能到达的最远位置,其中k最大值是17即可。在准备好了up数组之后,二分搜值的验证过程只需要log(N)次的跳转即可。 + + + + diff --git a/Binary_Search/3553.Minimum-Weighted-Subgraph-With-the-Required-Paths-II/3553.Minimum-Weighted-Subgraph-With-the-Required-Paths-II.cpp b/Binary_Search/3553.Minimum-Weighted-Subgraph-With-the-Required-Paths-II/3553.Minimum-Weighted-Subgraph-With-the-Required-Paths-II.cpp new file mode 100644 index 000000000..440611fad --- /dev/null +++ b/Binary_Search/3553.Minimum-Weighted-Subgraph-With-the-Required-Paths-II/3553.Minimum-Weighted-Subgraph-With-the-Required-Paths-II.cpp @@ -0,0 +1,80 @@ +using ll = long long; +const int MAXN = 100000; +const int LOGN = 17; +class Solution { +public: + vector> adj[MAXN]; + int up[MAXN][LOGN+1]; + int depth[MAXN]; + ll distRoot[MAXN]; + + void dfs(int cur, int parent) + { + up[cur][0] = parent; + for(auto &[v,w]: adj[cur]) + { + if(v == parent) continue; + depth[v] = depth[cur] + 1; + distRoot[v] = distRoot[cur] + w; + dfs(v, cur); + } + } + + int lca(int a, int b) + { + if(depth[a] < depth[b]) swap(a,b); + int diff = depth[a] - depth[b]; + for(int k = 0; k <= LOGN; k++){ + if(diff & (1<= 0; k--){ + if(up[a][k] != up[b][k]){ + a = up[a][k]; + b = up[b][k]; + } + } + return up[a][0]; + } + + ll dist(int a, int b) + { + int c = lca(a,b); + return distRoot[a] + distRoot[b] - 2*distRoot[c]; + } + + vector minimumWeight(vector>& edges, vector>& queries) + { + int n = edges.size()+1; + + for (int i = 0; i < n-1; i++) + { + int u = edges[i][0], v = edges[i][1], w = edges[i][2]; + adj[u].push_back({v,w}); + adj[v].push_back({u,w}); + } + + depth[0] = 0; + distRoot[0] = 0; + dfs(0, 0); + + for(int k = 1; k <= LOGN; k++) { + for(int v = 0; v < n; v++) { + up[v][k] = up[up[v][k-1]][k-1]; + } + } + + vectorrets; + for (auto& q: queries) + { + int u = q[0], v = q[1], w = q[2]; + ll d_uv = dist(u,v); + ll d_vw = dist(v,w); + ll d_uw = dist(u,w); + ll ans = (d_uv + d_vw + d_uw) / 2; + rets.push_back(ans); + } + + return rets; + } +}; diff --git a/Binary_Search/3553.Minimum-Weighted-Subgraph-With-the-Required-Paths-II/Readme.md b/Binary_Search/3553.Minimum-Weighted-Subgraph-With-the-Required-Paths-II/Readme.md new file mode 100644 index 000000000..f210532f1 --- /dev/null +++ b/Binary_Search/3553.Minimum-Weighted-Subgraph-With-the-Required-Paths-II/Readme.md @@ -0,0 +1,9 @@ +### 3553.Minimum-Weighted-Subgraph-With-the-Required-Paths-II + +本题的第一个知识点是:在一棵树里,联通u,v,w三个节点的最小子树的权重和,就是`[dist(u,v)+dist(u,w)+dist(v,w)]/2`. + +本体的第二个知识点是:在一棵树里,联通x,y两点的路径长度,等于`dist(r,x)+dist(r,y)-2*dist(r,c)`,其中r是整棵树的根节点,c是x和y的LCA(lowest common ancester)。 + +任意一点到距离根节点的距离dist(r,x)可以通过DFS得到。于是本题的关键点就是求任意两点的LCA,于是就是一个binary list经典题。 + +我们需要处理得到一个数组up[v][k],表示节点v往上(朝根节点方向)走2^k步能够得到的位置。转移方程就是`up[v][k] = up[up[v][k-1]][k-1]`. 边界条件就是对于一对父子节点a->b,有`up[b][0]=a`. diff --git a/Binary_Search/3559.Number-of-Ways-to-Assign-Edge-Weights-II/3559.Number-of-Ways-to-Assign-Edge-Weights-II.cpp b/Binary_Search/3559.Number-of-Ways-to-Assign-Edge-Weights-II/3559.Number-of-Ways-to-Assign-Edge-Weights-II.cpp new file mode 100644 index 000000000..fd59af59b --- /dev/null +++ b/Binary_Search/3559.Number-of-Ways-to-Assign-Edge-Weights-II/3559.Number-of-Ways-to-Assign-Edge-Weights-II.cpp @@ -0,0 +1,93 @@ +using ll = long long; +const int MAXN = 100005; +const int LOGN = 17; +class Solution { +public: + vector> adj[MAXN]; + int up[MAXN][LOGN+1]; + int depth[MAXN]; + ll distRoot[MAXN]; + + void dfs(int cur, int parent) + { + up[cur][0] = parent; + for(auto &[v,w]: adj[cur]) + { + if(v == parent) continue; + depth[v] = depth[cur] + 1; + distRoot[v] = distRoot[cur] + w; + dfs(v, cur); + } + } + + int lca(int a, int b) + { + if(depth[a] < depth[b]) swap(a,b); + int diff = depth[a] - depth[b]; + for(int k = 0; k <= LOGN; k++){ + if(diff & (1<= 0; k--){ + if(up[a][k] != up[b][k]){ + a = up[a][k]; + b = up[b][k]; + } + } + return up[a][0]; + } + + ll dist(int a, int b) + { + int c = lca(a,b); + return distRoot[a] + distRoot[b] - 2*distRoot[c]; + } + + ll stepUp(int u, int k) { + for (int i=LOGN; i>=0; i--) { + if ((k>>i)&1) { + u = up[u][i]; + } + } + return u; + } + + vector assignEdgeWeights(vector>& edges, vector>& queries) { + int n = edges.size()+1; + + for (auto& edge: edges) + { + int u = edge[0], v = edge[1], w = 1; + adj[u].push_back({v,w}); + adj[v].push_back({u,w}); + } + + depth[1] = 0; + distRoot[1] = 0; + dfs(1, 1); + + for(int k = 1; k <= LOGN; k++) { + for(int v = 1; v <= n; v++) { + up[v][k] = up[up[v][k-1]][k-1]; + } + } + + vectorpower(n+1); + ll M = 1e9+7; + power[0] = 1; + for (int i=1; i<=n; i++) + power[i] = power[i-1]*2%M; + + vectorrets; + for (auto&q: queries) { + int u = q[0], v = q[1]; + int d = dist(u,v); + if (d==0) + rets.push_back(0); + else + rets.push_back(power[d-1]); + } + + return rets; + } +}; diff --git a/Binary_Search/3559.Number-of-Ways-to-Assign-Edge-Weights-II/Readme.md b/Binary_Search/3559.Number-of-Ways-to-Assign-Edge-Weights-II/Readme.md new file mode 100644 index 000000000..c24856884 --- /dev/null +++ b/Binary_Search/3559.Number-of-Ways-to-Assign-Edge-Weights-II/Readme.md @@ -0,0 +1,10 @@ +### 3559.Number-of-Ways-to-Assign-Edge-Weights-II + +我们很容易看出,可以用binary lifting高效地求出任意两点之间的edge的个数d。显然,每段edge可以赋值1或者2,因此总共会有2^d种组合。其中有多少种方法能使得总路径长度恰好是奇数呢?结论很简单,就是它们的一半,即2^(d-1)种。 + +我们可以用动态规划来推论一下。dp1[i]表示i条边组成的总长度为奇数的组合数,dp2[i]表示i条边组成的总长度为偶数的组合数。我们的转移方程是 +``` +dp1[i] = dp2[i-1]+dp1[i-1]; +dp2[i] = dp1[i-1]+dp2[i-1]; +``` +初始条件是`dp1[1]=dp2[1]=1`,显然会有对任意的i,都有`dp1[i]=dp2[i]`。故i条边组成的总长度为偶数和奇数的组合数一定相等。 diff --git a/Binary_Search/3585.Find-Weighted-Median-Node-in-Tree/3585.Find-Weighted-Median-Node-in-Tree.cpp b/Binary_Search/3585.Find-Weighted-Median-Node-in-Tree/3585.Find-Weighted-Median-Node-in-Tree.cpp new file mode 100644 index 000000000..15e4011a6 --- /dev/null +++ b/Binary_Search/3585.Find-Weighted-Median-Node-in-Tree/3585.Find-Weighted-Median-Node-in-Tree.cpp @@ -0,0 +1,110 @@ +using ll = long long; +const int MAXN = 100000; +const int LOGN = 17; + +class Solution { +public: + vector> adj[MAXN]; + int up[MAXN][LOGN+1]; + int depth[MAXN]; + ll distRoot[MAXN]; + + void dfs(int cur, int parent) + { + up[cur][0] = parent; + for(auto &[v,w]: adj[cur]) + { + if(v == parent) continue; + depth[v] = depth[cur] + 1; + distRoot[v] = distRoot[cur] + w; + dfs(v, cur); + } + } + + int lca(int a, int b) + { + if(depth[a] < depth[b]) swap(a,b); + int diff = depth[a] - depth[b]; + for(int k = 0; k <= LOGN; k++){ + if(diff & (1<= 0; k--){ + if(up[a][k] != up[b][k]){ + a = up[a][k]; + b = up[b][k]; + } + } + return up[a][0]; + } + + int stepUp(int u, int k) { + for (int i=16; i>=0; i--) { + if ((k>>i)&1) { + u = up[u][i]; + } + } + return u; + } + + ll dist(int a, int b) + { + int c = lca(a,b); + return distRoot[a] + distRoot[b] - 2*distRoot[c]; + } + + vector findMedian(int n, vector>& edges, vector>& queries) { + for (int i = 0; i < n-1; i++) + { + int u = edges[i][0], v = edges[i][1], w = edges[i][2]; + adj[u].push_back({v,w}); + adj[v].push_back({u,w}); + } + + depth[0] = 0; + distRoot[0] = 0; + dfs(0, 0); + + for(int k = 1; k <= LOGN; k++) { + for(int v = 0; v < n; v++) { + up[v][k] = up[up[v][k-1]][k-1]; + } + } + + vectorrets; + for (auto& q: queries) + { + int u = q[0], v = q[1]; + int c = lca(u,v); + ll total = dist(u,c)+dist(c,v); + + int step1 = depth[u]-depth[c]; + int step2 = depth[v]-depth[c]; + + int low = 0, high = step1+step2; + int k; + while (low < high) { + int mid = low + (high-low)/2; + ll d; + if (mid <= step1) { + k = stepUp(u, mid); + d = distRoot[u] - distRoot[k]; + } else { + k = stepUp(v, step2 - (mid-step1)); + d = total - (distRoot[v] - distRoot[k]); + } + if (d >= total*0.5) + high = mid; + else + low = mid+1; + } + int step = low; + if (step<=step1) + rets.push_back(stepUp(u, step)); + else + rets.push_back(stepUp(v, step2-(step-step1))); + } + + return rets; + } +}; diff --git a/Binary_Search/3585.Find-Weighted-Median-Node-in-Tree/Readme.md b/Binary_Search/3585.Find-Weighted-Median-Node-in-Tree/Readme.md new file mode 100644 index 000000000..87b7b21b1 --- /dev/null +++ b/Binary_Search/3585.Find-Weighted-Median-Node-in-Tree/Readme.md @@ -0,0 +1,7 @@ +### 3585.Find-Weighted-Median-Node-in-Tree + +对于任何一个query,我们只需要找到u到v路径(途中经过LCA的点记作c),假设路径的总步长是d,路径的总权重和是total。我们只需要在[0,d]之间进行二分搜索一个合适的步数k:即从u走k步,恰好走过的路径长度超过total的一半。 + +注意,我们在二分搜索对k进行判定的时候,需要分类讨论k是否在u到c的路径上,还是c到v的路径上。即看是否`dist(u,c) >= total * 0.5`. 如果k是在u到c的路径上,那么经过的路径长度就是dist(u,k)。如果k是在c到v的路径上,那么经过的路径长度就是dist(u,c)+dist(c,k)。 + +根据binary lifting的算法,树里任意两个节点之间的距离都可以用log(n)的时间求解。 diff --git a/Binary_Search/3639.Minimum-Time-to-Activate-String/3639.Minimum-Time-to-Activate-String.cpp b/Binary_Search/3639.Minimum-Time-to-Activate-String/3639.Minimum-Time-to-Activate-String.cpp new file mode 100644 index 000000000..cf17556d5 --- /dev/null +++ b/Binary_Search/3639.Minimum-Time-to-Activate-String/3639.Minimum-Time-to-Activate-String.cpp @@ -0,0 +1,43 @@ +using ll = long long; +class Solution { +public: + int minTime(string s, vector& order, int k) { + int n = s.size(); + vectorisStar(n, false); + ll T = ll(n)*(n+1)/2; + if (k>T) return -1; + + auto check = [&](int mid) { + fill(isStar.begin(), isStar.end(), false); + for (int i=0; i<=mid; i++) + isStar[order[i]] = true; + + ll sumNon = 0; + for (int i=0; i= k; + }; + + int lo = 0, hi = n-1, ans = -1; + while (lo < hi) { + int mid = lo + (hi-lo)/2; + if (check(mid)) { + hi = mid; + } else { + lo = mid+1; + } + } + if (check(hi)) return hi; + else return -1; + } +}; diff --git a/Binary_Search/3639.Minimum-Time-to-Activate-String/Readme.md b/Binary_Search/3639.Minimum-Time-to-Activate-String/Readme.md new file mode 100644 index 000000000..7f198d6f2 --- /dev/null +++ b/Binary_Search/3639.Minimum-Time-to-Activate-String/Readme.md @@ -0,0 +1,5 @@ +### 3639.Minimum-Time-to-Activate-String + +非常明显的二分搜值。假设运行到某个时刻t,那么我们就得到一个包含若干星号的字符串。我们需要考察该字符串里至少包含一个星号的substring的个数是否超过k。超过的话,就可以尝试减少k,否则需要增加k。 + +计算“至少包含一个星号的substring的个数”,等效于反向计算“没有任何星号的substring的个数”,并且后者更容易计算。对于任何一段连续的、不包含任何星号的子串长度p,那么就有p*(p+1)/2个子串符合条件。我们分割原始字符串为若干段“没有任何星号的区间”,分别计算再相加即可。 diff --git a/Binary_Search/3677.Count-Binary-Palindromic-Numbers/3677.Count-Binary-Palindromic-Numbers.cpp b/Binary_Search/3677.Count-Binary-Palindromic-Numbers/3677.Count-Binary-Palindromic-Numbers.cpp new file mode 100644 index 000000000..f4c66322c --- /dev/null +++ b/Binary_Search/3677.Count-Binary-Palindromic-Numbers/3677.Count-Binary-Palindromic-Numbers.cpp @@ -0,0 +1,57 @@ +using LL = long long; +class Solution { +public: + LL reverseBits(LL x) { + LL r = 0; + while (x>0) { + r = r*2+(x&1); + x>>=1; + } + return r; + } + + LL build(LL half, int L) { + int h = (L+1)/2; + int k = L-h; + if (L%2==0) { + return (half<>1); + } + } + + int countBinaryPalindromes(long long n) { + if (n==0) return 1; + int maxLen = floor(log2(n)) + 1; + + LL ret = 1; + for (int L=1; Lmx`的情况,所以需要对收敛的解做二次验证。 + diff --git a/Binary_Search/3691.Maximum-Total-Subarray-Value-II/3691.Maximum-Total-Subarray-Value-II_v1.cpp b/Binary_Search/3691.Maximum-Total-Subarray-Value-II/3691.Maximum-Total-Subarray-Value-II_v1.cpp new file mode 100644 index 000000000..7e8081db8 --- /dev/null +++ b/Binary_Search/3691.Maximum-Total-Subarray-Value-II/3691.Maximum-Total-Subarray-Value-II_v1.cpp @@ -0,0 +1,65 @@ +using LL = long long; +class Solution { +public: + long long maxTotalValue(vector& nums, int k) { + int n = nums.size(), K = log2(n) + 1; + + int mn[50005][35]; memset(mn, 0x3f, sizeof(mn)); + int mx[50005][35]; memset(mx, 0xcf, sizeof(mx)); + for (int i = 0; i < n; i++) mn[i][0] = mx[i][0] = nums[i]; + for (int k = 1; k <= K; k++) { + for (int i = 0; i+(1<= k) + lo = mid; + else + hi = mid-1; + } + LL th = lo; + + LL ret_g = 0; + LL count_g = 0; + for (int i=0; ith) + r = mid; + else + l = mid+1; + } + if (GetDiff(i, r) > th) { + count_g += n-r; + for (int j=r; j=d`,于是`n-j`就是以i为左端点、且MaxDiff大于等于d的区间数量。之后左端点i右移一位,右端点依然是单调移动。 + +OK,此时我们得到了第k大的MaxDiff是`th`。考虑到k不超过1e5,我们可以将这些区间都枚举出来。 + +对于每个以i为左端点的区间,我们可以再次用二分搜值来接确定第一个`MaxDiff>th`的位置j。之后右端点从j到n的每一个位置,我们都可以将GetDiff(i,j)收入结果之中。如前所述,这样的枚举是可行的。 + +在上述的枚举过程中,如果我们加入了count_g个`MaxDiff>th`的区间,那么还有`k-count_g`区间未添加,而它们对应都恰好是`MaxDiff==th`,所以直接将`th*(k-count_g)`加入最终结果即可。 + + + + diff --git a/Bit_Manipulation/1521.Find-a-Value-of-a-Mysterious-Function-Closest-to-Target/1521.Find-a-Value-of-a-Mysterious-Function-Closest-to-Target.cpp b/Bit_Manipulation/1521.Find-a-Value-of-a-Mysterious-Function-Closest-to-Target/1521.Find-a-Value-of-a-Mysterious-Function-Closest-to-Target.cpp index f5d4fac4f..d63214a53 100644 --- a/Bit_Manipulation/1521.Find-a-Value-of-a-Mysterious-Function-Closest-to-Target/1521.Find-a-Value-of-a-Mysterious-Function-Closest-to-Target.cpp +++ b/Bit_Manipulation/1521.Find-a-Value-of-a-Mysterious-Function-Closest-to-Target/1521.Find-a-Value-of-a-Mysterious-Function-Closest-to-Target.cpp @@ -1,18 +1,22 @@ class Solution { public: - int closestToTarget(vector& arr, int target) { - unordered_sets; + int closestToTarget(vector& arr, int target) + { + setSet, temp; int ret = INT_MAX; - for (int i=0; is2; - for (auto x: s) - s2.insert(x&arr[i]); - s2.insert(arr[i]); - for (auto x: s2) - ret = min(ret, abs(x-target)); - s = s2; + for (auto y: Set) + temp.insert(y&x); + temp.insert(x); + + for (int y: temp) + ret = min(ret, abs(y-target)); + + Set = temp; + temp.clear(); } + return ret; } }; diff --git a/Bit_Manipulation/260.Single-Number-III/260.Single-Number-III.cpp b/Bit_Manipulation/260.Single-Number-III/260.Single-Number-III.cpp index d5a308b60..bbfea14a2 100644 --- a/Bit_Manipulation/260.Single-Number-III/260.Single-Number-III.cpp +++ b/Bit_Manipulation/260.Single-Number-III/260.Single-Number-III.cpp @@ -2,9 +2,9 @@ class Solution { public: vector singleNumber(vector& nums) { - int s = 0; + long long s = 0; for (auto n:nums) s = s^n; // i.e. a^b - int t = s^(s&(s-1)); // only keep the rightmost set bit + long long t = s^(s&(s-1)); // only keep the rightmost set bit int a = 0, b = 0; for (auto n:nums) { diff --git a/Bit_Manipulation/2680.Maximum-OR/2680.Maximum-OR.cpp b/Bit_Manipulation/2680.Maximum-OR/2680.Maximum-OR.cpp new file mode 100644 index 000000000..60d85e143 --- /dev/null +++ b/Bit_Manipulation/2680.Maximum-OR/2680.Maximum-OR.cpp @@ -0,0 +1,38 @@ +using LL = long long; +class Solution { +public: + long long maximumOr(vector& nums, int k) + { + vectorcount(32); + + for (int i = 0; i< nums.size(); i++) + { + for (int j=0; j<=31; j++) + { + if ((nums[i]>>j)&1) + count[j]++; + } + } + + LL ret = 0; + for (int i = 0; i< nums.size(); i++) + { + auto temp = count; + for (int j=0; j<=31; j++) + { + if ((nums[i]>>j)&1) + temp[j]--; + } + LL ans = 0; + for (int j=0; j<=31; j++) + { + if (temp[j]>0) + ans += (1<0) + { + if (x%2==0) + ret.push_back('4'); + else + ret.push_back('7'); + x/=2; + } + ret.pop_back(); + reverse(ret.begin(), ret.end()); + return ret; + } +}; diff --git a/Bit_Manipulation/2802.Find-The-K-th-Lucky-Number/Readme.md b/Bit_Manipulation/2802.Find-The-K-th-Lucky-Number/Readme.md new file mode 100644 index 000000000..93eb946a7 --- /dev/null +++ b/Bit_Manipulation/2802.Find-The-K-th-Lucky-Number/Readme.md @@ -0,0 +1,13 @@ +### 2802.Find-The-K-th-Lucky-Number + +这是一个常见的技巧。Lucky Number仅由两个digit组成,所以它是"4"与"7",还是“0”与“1”,没有本质区别。我们索性就利用二进制数来构造第k大的01序列。 + +因为任何二进制数都没有先导零,第一位总是1。所以我们排除所有二进制数的第一个bit 1,剩余的bit位恰好就构成了递增的01序列。举例如下: +``` +2: 10 -> 0 +3: 11 -> 1 +4: 100 -> 00 +5: 101 -> 01 +6: 110 -> 10 +``` +我们从2开始枚举自然数,得到其二进制表达式,去掉先导1,剩余的部分就是递增的01序列。我们将其替换为“4”“7”序列即可。 diff --git a/Bit_Manipulation/2992.Number-of-Self-Divisible-Permutations/2992.Number-of-Self-Divisible-Permutations.cpp b/Bit_Manipulation/2992.Number-of-Self-Divisible-Permutations/2992.Number-of-Self-Divisible-Permutations.cpp new file mode 100644 index 000000000..a281fd721 --- /dev/null +++ b/Bit_Manipulation/2992.Number-of-Self-Divisible-Permutations/2992.Number-of-Self-Divisible-Permutations.cpp @@ -0,0 +1,20 @@ +class Solution { + int dp[13][4096]; +public: + int selfDivisiblePermutationCount(int n) + { + int state = 0; + dp[0][0] = 1; + for (int i=1; i<=n; i++) + for (int state = 0; state<(1<>(d-1))&1)==0) continue; + dp[i][state] += dp[i-1][state-(1<<(d-1))]; + } + } + return dp[n][(1<>(d-1))&1)==0) continue; + dp[i][state] += dp[i-1][state-(1<<(d-1))]; +} +``` +最终返回dp[n][(1<& coins, int k) + { + LL left = 1, right = 51e9; + while (left < right) + { + LL mid = left+(right-left)/2; + if (countNumber(mid, coins) >= k) + right = mid; + else + left = mid+1; + } + return left; + } + + LL countNumber(LL M, vector& coins) + { + int m = coins.size(); + + LL ret = 0; + int sign = 1; + + for (int k=1; k<=m; k++) + { + LL sum = 0; + int state = (1 << k) - 1; + while (state < (1 << m)) + { + LL LCM = 1; + for (int i=0; i>i)&1) + LCM = lcm(LCM, coins[i]); + } + sum += M / LCM; + + int c = state & - state; + int r = state + c; + state = (((r ^ state) >> 2) / c) | r; + } + + ret += sum * sign; + sign *= -1; + } + + return ret; + } +}; diff --git a/Bit_Manipulation/3116.Kth-Smallest-Amount-With-Single-Denomination-Combination/Readme.md b/Bit_Manipulation/3116.Kth-Smallest-Amount-With-Single-Denomination-Combination/Readme.md new file mode 100644 index 000000000..78f967265 --- /dev/null +++ b/Bit_Manipulation/3116.Kth-Smallest-Amount-With-Single-Denomination-Combination/Readme.md @@ -0,0 +1,9 @@ +### 3116.Kth-Smallest-Amount-With-Single-Denomination-Combination + +本题就是求第k个能被coins里任意一个元素整除的自然数。 + +因为我们无法枚举每个符合条件的自然数直至第k个,我们只能用二分搜值的框架。猜测一个M,计算M以内符合条件的自然数有多少,多了就降低M,少了就抬升M,直至找到恰好的M。 + +那么如何计算M以内、能被coins里任意一个元素整除的自然数的个数呢?显然我们会用容斥原理。`能被任意一个元素整除的个数 = sum(能被每一个元素整除的个数) - sum(能被每两个元素同时整除的个数) + sum(能被每三个元素同时整除的个数) - sum(能被每四个元素同时整除的个数) + ...` + +举个例子,能被a,b,c三个元素同时整除的个数,是 `M / lcm(a,b,c)`,其中lcm是三者的最小公倍数。如果想在m个元素里,枚举任意三个元素的组合,那么我们会用gospher's hack,高效枚举中一个长度为m的二进制数里含有三个bit 1的mask。 diff --git a/Bit_Manipulation/3133.Minimum-Array-End/3133.Minimum-Array-End.cpp b/Bit_Manipulation/3133.Minimum-Array-End/3133.Minimum-Array-End.cpp new file mode 100644 index 000000000..c9f7746bb --- /dev/null +++ b/Bit_Manipulation/3133.Minimum-Array-End/3133.Minimum-Array-End.cpp @@ -0,0 +1,44 @@ +using LL = long long; +class Solution { +public: + long long minEnd(int n, int x) + { + LL m = n-1; + vectornum; + while (m>0) + { + num.push_back(m%2); + m/=2; + } + + vectorbits; + while (x>0) + { + bits.push_back(x%2); + x/=2; + } + + int j = 0; + for (int i=0; i=0; j--) + ret = ret*2+bits[j]; + + return ret; + + } +}; diff --git a/Bit_Manipulation/3133.Minimum-Array-End/Readme.md b/Bit_Manipulation/3133.Minimum-Array-End/Readme.md new file mode 100644 index 000000000..e5a924d50 --- /dev/null +++ b/Bit_Manipulation/3133.Minimum-Array-End/Readme.md @@ -0,0 +1,9 @@ +### 3133.Minimum-Array-End + +本题要求构造一个严格递增的、长度为n的序列,使得其bitwise AND的结果是x。问序列的最大元素可以是多少。 + +因为对一个序列做bitwise AND操作,最终结果不可能大于其中最小的元素。所以首元素不可能比x更小,否则总的bitwise AND不可能是x。最理想的情况就是将x作为序列的首元素,序列的其余元素都比x大。 + +为了保证bitwise AND最终结果是x,对于x里属于1的那些bit位,序列里的任何元素在这些二进制位置都不能是0。否则最终答案在该位置上成为了0,必然比x小。我们唯一能自由操作的就是那些属于0的bit位。 + +总结算法:我们将x进行二进制分解。保持那些属于1的bit位不变。这样我们可以构造长度为n的最小序列:0,1,2,...,n-1。其中将最大元素n-1进行二进制拆解,但是填充在x的那些属于0的bit位上。这样就得到了答案。 diff --git a/Bit_Manipulation/3171.Find-Subarray-With-Bitwise-AND-Closest-to-K/3171.Find-Subarray-With-Bitwise-AND-Closest-to-K_v1.cpp b/Bit_Manipulation/3171.Find-Subarray-With-Bitwise-AND-Closest-to-K/3171.Find-Subarray-With-Bitwise-AND-Closest-to-K_v1.cpp new file mode 100644 index 000000000..2793c6d98 --- /dev/null +++ b/Bit_Manipulation/3171.Find-Subarray-With-Bitwise-AND-Closest-to-K/3171.Find-Subarray-With-Bitwise-AND-Closest-to-K_v1.cpp @@ -0,0 +1,21 @@ +class Solution { +public: + int minimumDifference(vector& nums, int k) + { + unordered_setSet, temp; + int ret = INT_MAX; + for (int x: nums) + { + for (int y: Set) + temp.insert(y | x); + temp.insert(x); + + for (int y: temp) + ret = min(ret, abs(y-k)); + + Set = temp; + temp.clear(); + } + return ret; + } +}; diff --git a/Bit_Manipulation/3171.Find-Subarray-With-Bitwise-AND-Closest-to-K/3171.Find-Subarray-With-Bitwise-AND-Closest-to-K_v2.cpp b/Bit_Manipulation/3171.Find-Subarray-With-Bitwise-AND-Closest-to-K/3171.Find-Subarray-With-Bitwise-AND-Closest-to-K_v2.cpp new file mode 100644 index 000000000..db5e8b020 --- /dev/null +++ b/Bit_Manipulation/3171.Find-Subarray-With-Bitwise-AND-Closest-to-K/3171.Find-Subarray-With-Bitwise-AND-Closest-to-K_v2.cpp @@ -0,0 +1,94 @@ +class SegmentTree { +private: + vector tree; + int n; + + void build(vector& nums, int node, int start, int end) + { + if (start == end) { + tree[node] = nums[start]; + } else { + int mid = (start + end) / 2; + build(nums, 2 * node, start, mid); + build(nums, 2 * node + 1, mid + 1, end); + tree[node] = tree[2 * node] & tree[2 * node + 1]; + } + } + + void update(int node, int start, int end, int L, int R, int val) + { + if (R < start || end < L) { + return; + } + if (L <= start && end <= R) { + tree[node] = val; + return; + } + int mid = (start + end) / 2; + update(2 * node, start, mid, L, R, val); + update(2 * node + 1, mid + 1, end, L, R, val); + tree[node] = tree[2 * node] & tree[2 * node + 1]; + } + + int query(int node, int start, int end, int L, int R) + { + if (R < start || end < L) { + return INT_MAX; // Identity for AND operation (all bits set) + } + if (L <= start && end <= R) { + return tree[node]; + } + int mid = (start + end) / 2; + int leftAnd = query(2 * node, start, mid, L, R); + int rightAnd = query(2 * node + 1, mid + 1, end, L, R); + return leftAnd & rightAnd; + } + +public: + SegmentTree(vector& nums) { + n = nums.size(); + tree.resize(4 * n, 0); + build(nums, 1, 0, n - 1); + } + + void rangeUpdate(int L, int R, int val) { + update(1, 0, n - 1, L, R, val); + } + + int rangeAnd(int L, int R) { + return query(1, 0, n - 1, L, R); + } +}; + +class Solution { +public: + int minimumDifference(vector& nums, int k) + { + int n = nums.size(); + SegmentTree segTree(nums); + int ret = INT_MAX; + + for (int i = 0; i < n; ++i) + { + int low = i, high = n - 1; + while (low < high) + { + int mid = high - (high - low)/2; + if (segTree.rangeAnd(i, mid) >= k) + low = mid; + else + high = mid-1; + } + + int ret1 = abs(segTree.rangeAnd(i, low) - k); + int ret2 = INT_MAX; + if (low+1& nums, int k) + { + map mp, temp; + long long ans = 0; + for(int x: nums) + { + for(auto& [k,v]: mp) + temp[k & x] += v; + temp[x]++; + + if(temp.find(k) != temp.end()) + ans += temp[k]; + + mp = temp; + temp.clear(); + } + return ans; + } +}; diff --git a/Bit_Manipulation/3209.Number-of-Subarrays-With-AND-Value-of-K/3209.Number-of-Subarrays-With-AND-Value-of-K_v2.cpp b/Bit_Manipulation/3209.Number-of-Subarrays-With-AND-Value-of-K/3209.Number-of-Subarrays-With-AND-Value-of-K_v2.cpp new file mode 100644 index 000000000..ba13f016e --- /dev/null +++ b/Bit_Manipulation/3209.Number-of-Subarrays-With-AND-Value-of-K/3209.Number-of-Subarrays-With-AND-Value-of-K_v2.cpp @@ -0,0 +1,108 @@ +using LL = long long; +class SegmentTree { +private: + vector tree; + int n; + + void build(vector& nums, int node, int start, int end) + { + if (start == end) { + tree[node] = nums[start]; + } else { + int mid = (start + end) / 2; + build(nums, 2 * node, start, mid); + build(nums, 2 * node + 1, mid + 1, end); + tree[node] = tree[2 * node] & tree[2 * node + 1]; + } + } + + void update(int node, int start, int end, int L, int R, int val) + { + if (R < start || end < L) { + return; + } + if (L <= start && end <= R) { + tree[node] = val; + return; + } + int mid = (start + end) / 2; + update(2 * node, start, mid, L, R, val); + update(2 * node + 1, mid + 1, end, L, R, val); + tree[node] = tree[2 * node] & tree[2 * node + 1]; + } + + int query(int node, int start, int end, int L, int R) + { + if (R < start || end < L) { + return INT_MAX; // Identity for AND operation (all bits set) + } + if (L <= start && end <= R) { + return tree[node]; + } + int mid = (start + end) / 2; + int leftAnd = query(2 * node, start, mid, L, R); + int rightAnd = query(2 * node + 1, mid + 1, end, L, R); + return leftAnd & rightAnd; + } + +public: + SegmentTree(vector& nums) { + n = nums.size(); + tree.resize(4 * n, 0); + build(nums, 1, 0, n - 1); + } + + void rangeUpdate(int L, int R, int val) { + update(1, 0, n - 1, L, R, val); + } + + int rangeAnd(int L, int R) { + return query(1, 0, n - 1, L, R); + } +}; + +class Solution { +public: + long long countSubarrays(vector& nums, int k) + { + int n = nums.size(); + SegmentTree segTree(nums); + LL ret = 0; + + for (int i=0; ik) + left = mid+1; + else + right = mid; + } + if (segTree.rangeAnd(i,left)==k) + a = left; + + left = i, right = n-1; + while (left < right) + { + int mid = right-(right-left)/2; + if (segTree.rangeAnd(i,mid)compute(vectornums) { + int n = nums.size(); + unordered_mapMap; + Map[0] = 0; + for (int mask = 0; mask < (1<>i)&1) { + x^=nums[i]; + cnt++; + } + } + if (!Map.count(x) || cnt>Map[x]) + Map[x] = cnt; + } + return Map; + } + + int minRemovals(vector& nums, int target) { + int n = nums.size(); + + vectorleft(nums.begin(), nums.begin()+n/2); + vectorright(nums.begin()+n/2, nums.end()); + + unordered_mapL = compute(left); + unordered_mapR = compute(right); + + int ret = -1; + for (auto [x1, c1]: L) { + int x2= target^x1; + if (R.count(x2)) { + ret = max(ret, c1+R[x2]); + } + } + + if (ret==-1) return -1; + else return n-ret; + } +}; diff --git a/Bit_Manipulation/3877.Minimum-Removals-to-Achieve-Target-XOR/3877.Minimum-Removals-to-Achieve-Target-XOR_v2.cpp b/Bit_Manipulation/3877.Minimum-Removals-to-Achieve-Target-XOR/3877.Minimum-Removals-to-Achieve-Target-XOR_v2.cpp new file mode 100644 index 000000000..6641e4f9a --- /dev/null +++ b/Bit_Manipulation/3877.Minimum-Removals-to-Achieve-Target-XOR/3877.Minimum-Removals-to-Achieve-Target-XOR_v2.cpp @@ -0,0 +1,21 @@ + class Solution { + int dp[42][100006]; +public: + int minRemovals(vector& nums, int target) { + int n = nums.size(); + nums.insert(nums.begin(), 0); + for (int v=0; v<100000; v++) + dp[0][v] = INT_MIN/2; + dp[0][0] = 0; + + for (int i=1; i<=n; i++) + for (int v=0; v<(1<<14); v++) { + dp[i][v] = max(dp[i-1][v], dp[i-1][v^nums[i]]+1); + } + + if (dp[n][target] < 0) + return -1; + else + return n - dp[n][target]; + } +}; diff --git a/Bit_Manipulation/3877.Minimum-Removals-to-Achieve-Target-XOR/Readme.md b/Bit_Manipulation/3877.Minimum-Removals-to-Achieve-Target-XOR/Readme.md new file mode 100644 index 000000000..5a6c80a77 --- /dev/null +++ b/Bit_Manipulation/3877.Minimum-Removals-to-Achieve-Target-XOR/Readme.md @@ -0,0 +1,18 @@ +### 3877.Minimum-Removals-to-Achieve-Target-XOR + +#### 解法1 + +本题要求在nums数组里找一个最长的子序列,使得其XOR Sum等于need^target。 + +任意子序列的个数是2^n。考虑到n=40,穷举的话是不可行的。但是如果n=20的话,穷举的数目恰好是2^20属于1e6数量级。这就提示我们可以用两遍穷举,然后meet in the middle的策略。 + +我们将nums分为前后两个部分。对于每一半,我们各自穷举所有的mask,计算出对应XOR Sum数值和其mask的长度,装入一个Hash表。Hash表里永远更新为最长的长度。最后我们枚举前一个Map里的[key1,val1],然后在后一个Map里寻找对应的`key2=target^key1`,那么`val1+val2`就是一个可能的答案。 + +#### 解法2 + +考虑到nums的数值不超过1e4,那么所有XOR Sum的种类也不会超过1e4. 我们可以用常规的DP。令dp[i][v]表示对于前缀nums[1:i],Xor Sum等于v时的最长子序列长度。状态转移的依据就是当前元素的“选或不选”两种决策: +``` +dp[i][v] <= dp[i-1][v^nums[i]]+1 +dp[i][v] <= dp[i-1][v] +``` +最终返回dp[n][target]. diff --git a/DFS/037.Sudoku-Solver/037.Sudoku-Solver.cpp b/DFS/037.Sudoku-Solver/037.Sudoku-Solver.cpp index 2c831d805..24c97459d 100644 --- a/DFS/037.Sudoku-Solver/037.Sudoku-Solver.cpp +++ b/DFS/037.Sudoku-Solver/037.Sudoku-Solver.cpp @@ -11,7 +11,7 @@ class Solution { if (j==9) return DFS(board, i+1, 0); if (board[i][j]!='.') return DFS(board, i, j+1); - for (int k='1'; k<='9'; k++) + for (char k='1'; k<='9'; k++) { if (!isValid(board, i, j, k)) continue; board[i][j]=k; diff --git a/DFS/1815.Maximum-Number-of-Groups-Getting-Fresh-Donuts/Readme.md b/DFS/1815.Maximum-Number-of-Groups-Getting-Fresh-Donuts/Readme.md index ba980904b..47e84d952 100644 --- a/DFS/1815.Maximum-Number-of-Groups-Getting-Fresh-Donuts/Readme.md +++ b/DFS/1815.Maximum-Number-of-Groups-Getting-Fresh-Donuts/Readme.md @@ -20,7 +20,7 @@ return ret + bonus; } ``` -上面的```dfs(count, presum, i)```表示我们已经选择了i-1个groups(它们的前缀和是presum、已有的得分是prescore),我们从剩下的groups挑选一个安排在第i个。选哪个好呢?我们不知道,必须每种可能都尝试一次,结合相应的```dfs(..., i+1)```来判断。这里需要注意的是,如果此时的presum恰好被batch整除,那么说明无论第i个元素取谁,我们都可以得到1分,所以下次递归的时候perscore可以增加1。 +上面的```dfs(count, presum, i)```表示我们已经选择了i-1个groups(它们的前缀和是presum,哪些被选择了记录在count里),我们从剩下的groups挑选一个安排在第i个。选哪个好呢?我们不知道,必须每种可能都尝试一次,结合相应的```dfs(..., i+1)```来判断。这里需要注意的是,如果此时的presum恰好被batch整除,那么说明无论第i个元素取谁,我们都可以得到1分。所以返回的答案就是下轮递归 里的最大值,再加本轮的1。 以上的解法自然会TLE,原因是什么呢?显然是没有记忆化。我们可以发现,dfs函数中,其实只要确定了当前的count(即未被安排的groups),其他的参数presum本质上就是确定了的。所以记忆化的key其实就是count。但是count是一个数组,如何将转化为一个方便的key呢?和状态压缩相同的原因。因为count[i]最多30个,用五个bit就能表示(0~32)。batch最多是9,所以总共45位的二进制数就可以表述count数组。这就要求这个key是long long类型。 diff --git a/DFS/2597.The-Number-of-Beautiful-Subsets/2597.The-Number-of-Beautiful-Subsets.cpp b/DFS/2597.The-Number-of-Beautiful-Subsets/2597.The-Number-of-Beautiful-Subsets.cpp new file mode 100644 index 000000000..a1fe1ffac --- /dev/null +++ b/DFS/2597.The-Number-of-Beautiful-Subsets/2597.The-Number-of-Beautiful-Subsets.cpp @@ -0,0 +1,29 @@ +class Solution { +public: + int beautifulSubsets(vector& nums, int k) + { + return dfs(0, 0, nums, k) - 1; + } + + int dfs(int cur, int state, vector& nums, int k) + { + if (cur==nums.size()) return 1; + + int flag = 1; + for (int i=0; i>i)&1 && (nums[i]+k==nums[cur] || nums[i]-k==nums[cur])) + { + flag = 0; + break; + } + } + + int choose = dfs(cur+1, state+(1<& nums, int k) + { + unordered_mapcount; + for (int x:nums) + count[x]+=1; + + unordered_map>>Map; + for (auto [val,count]:count) + Map[val%k].push_back({val, count}); + + int ret = 1; + for (auto& [r,arr]: Map) + { + sort(arr.begin(), arr.end()); + + int take = 0, notake = 1; + for (int i=0; i>Map; +public: + int specialPerm(vector& nums) + { + n = nums.size(); + + for (int i=0; i>q)&1) continue; + ret += dfs(i+1, q, state+(1<& words) + { + return words[0].size() + dfs(1, words[0][0]-'a', words[0].back()-'a', words); + } + + // the minimum length to be added if we construct the first i words with start & end + int dfs(int i, int start, int end, vector& words) + { + if (i==words.size()) return 0; + if (memo[i][start][end]!=0) return memo[i][start][end]; + + int a = words[i][0]-'a', b = words[i].back()-'a'; + int len = words[i].size(); + int ret = INT_MAX/2; + + if (start==a && end==b) + { + // it does not matter we put words[i] at the beginning or at the end; + ret = len - (a==b) + dfs(i+1, start, end, words); + } + else + { + // place words[i] at the end + if (end==a) + ret = min(ret, len-1 + dfs(i+1, start, b, words)); + else + ret = min(ret, len + dfs(i+1, start, b, words)); + + // place words[i] at the beginning + if (start==b) + ret = min(ret, len-1 + dfs(i+1, a, end, words)); + else + ret = min(ret, len + dfs(i+1, a, end, words)); + } + + memo[i][start][end] = ret; + return ret; + } +}; diff --git a/DFS/2746.Decremental-String-Concatenation/Readme.md b/DFS/2746.Decremental-String-Concatenation/Readme.md new file mode 100644 index 000000000..7c9a8e64b --- /dev/null +++ b/DFS/2746.Decremental-String-Concatenation/Readme.md @@ -0,0 +1,31 @@ +### 2746.Decremental-String-Concatenation + +考虑到n的数量不大,估计可以暴力搜索。顺次遍历每一个单词,我们只需要考察将其加在已有str的前面还是后面两种决策。这看上去复杂度会有2^50,但是我们事实我们并不需要枚举这么多状态。假设前两个单词{abc,aec},那么这两个单词的拼接方式对于后续的选择而言没有不同,因为都是`a****c`。我们能否压缩长度的关键,其实只需要关注str的第一个和最后一个字符即可。于是我们实际需要枚举的状态最多只有50*26*26种。 + +由此我们可以定义递归函数`int dfs(int i, int start, int end)`,表示the minimum length to be added if we construct the first i words with start & end. 也就是说,当前i个单词构造出来的str以start开头、end结尾时,我们需要考虑如何使用words[i]:很明显两种方案,放在前面或者放在后面。此时我们就可以根据start/end与words[i]的首尾字符,进行递归处理: +```cpp +int a = words[i][0]-'a', b = words[i].back()-'a'; +// 放后面 +if (end==a) + ret = min(ret, len-1 + dfs(i+1, start, b, words)); +else + ret = min(ret, len + dfs(i+1, start, b, words)); + +// 放前面 +if (start==b) + ret = min(ret, len-1 + dfs(i+1, a, end, words)); +else + ret = min(ret, len + dfs(i+1, a, end, words)); +``` +最终的答案就是初始调用的`words[0].size() + dfs(1, words[0][0], words[0].back())`,因为对于words[0]我们只有唯一的构造形式。 + +另外,我们必然要用记忆化来避免相同参数的dfs重复调用。 + +更新:为了过更严格的case,需要再加一个优化的技巧 +```cpp +if (start==a && end==b) +{ + // it does not matter we put words[i] at the beginning or at the end; + ret = len - (a==b) + dfs(i+1, start, end, words); +} +``` diff --git a/DFS/2842.Count-K-Subsequences-of-a-String-With-Maximum-Beauty/2842.Count-K-Subsequences-of-a-String-With-Maximum-Beauty.cpp b/DFS/2842.Count-K-Subsequences-of-a-String-With-Maximum-Beauty/2842.Count-K-Subsequences-of-a-String-With-Maximum-Beauty.cpp new file mode 100644 index 000000000..88c46b8f1 --- /dev/null +++ b/DFS/2842.Count-K-Subsequences-of-a-String-With-Maximum-Beauty/2842.Count-K-Subsequences-of-a-String-With-Maximum-Beauty.cpp @@ -0,0 +1,48 @@ +using LL = long long; +LL M = 1e9+7; +class Solution { + int k; + int beauty = 0; + LL global = 0; +public: + void dfs(int curPos, int picked, int curBeauty, LL ret, vector&count) + { + if (curBeauty > beauty) return; + if (picked > k) return ; + + if (curBeauty == beauty && picked == k) + { + global = (global+ret)%M; + return; + } + + if (curBeauty + accumulate(count.begin()+curPos, count.end(), 0) < beauty) return; + + for (int i=curPos; ik = k; + unordered_mapMap; + for (auto ch: s) + Map[ch]+=1; + + vectorcount; + for (auto [k,v]: Map) + count.push_back(v); + + sort(count.rbegin(), count.rend()); + if (count.size() >& grid) + { + dfs(0, 0, grid); + return global; + } + + void dfs(int cur, int moves, vector>& grid) + { + if (moves >= global) return; + + if (cur==9) + { + global = min(global, moves); + return; + } + + int i = cur/3; + int j = cur%3; + if (grid[i][j]!=0) + { + dfs(cur+1, moves, grid); + return; + } + + for (int x=0; x<3; x++) + for (int y=0; y<3; y++) + { + if (grid[x][y]<=1) continue; + grid[x][y] -= 1; + grid[i][j] += 1; + dfs(cur+1, moves+abs(x-i)+abs(y-j), grid); + grid[x][y] += 1; + grid[i][j] -= 1; + } + } +}; diff --git a/DFS/2850.Minimum-Moves-to-Spread-Stones-Over-Grid/Readme.md b/DFS/2850.Minimum-Moves-to-Spread-Stones-Over-Grid/Readme.md new file mode 100644 index 000000000..282e71043 --- /dev/null +++ b/DFS/2850.Minimum-Moves-to-Spread-Stones-Over-Grid/Readme.md @@ -0,0 +1,13 @@ +### 2850.Minimum-Moves-to-Spread-Stones-Over-Grid + +本题的关键点在于判断出时间复杂度,可以用DFS无脑搜索。 + +假设只有一个空格,需要从其他八个格子转移一个过去,那么就有8^1种可能。 + +假设有两个空格,需要从其他七个格子分别转移一个过去,那么就有7^2种可能。 + +假设有三个空格,需要从其他六个格子分别转移一个过去,那么就有6^3种可能。 + +以此类推,5^4, 4^5, 3^6, 2^7, 1^8,其实数值都不大。 + +所以无脑搜索每个空格的转移策略即可。 diff --git a/DFS/3213.Construct-String-with-Minimum-Cost/3213.Construct-String-with-Minimum-Cost.cpp b/DFS/3213.Construct-String-with-Minimum-Cost/3213.Construct-String-with-Minimum-Cost.cpp new file mode 100644 index 000000000..71fc75c63 --- /dev/null +++ b/DFS/3213.Construct-String-with-Minimum-Cost/3213.Construct-String-with-Minimum-Cost.cpp @@ -0,0 +1,62 @@ +class TrieNode +{ + public: + TrieNode* next[26]; + int cost; + TrieNode() + { + for (int i=0; i<26; i++) + next[i] = NULL; + cost = -1; + } +}; + +class Solution { + TrieNode* root = new TrieNode(); + vectormemo; +public: + int minimumCost(string target, vector& words, vector& costs) + { + memo = vector(target.size(), -1); + + for (int i=0; inext[ch-'a']==NULL) + node->next[ch-'a'] = new TrieNode(); + node = node->next[ch-'a']; + } + if (node->cost==-1) + node->cost = costs[i]; + else + node->cost = min(node->cost, costs[i]); + } + + int ret = dfs(target, 0); + if (ret == INT_MAX/2) return -1; + else return ret; + } + + int dfs(string& target, int cur) + { + if (cur==target.size()) return 0; + if (memo[cur] != -1) return memo[cur]; + + int ans = INT_MAX/2; + TrieNode* node = root; + for (int i=cur; inext[target[i]-'a']==NULL) + break; + node = node->next[target[i]-'a']; + if (node->cost!=-1) + ans = min(ans, node->cost + dfs(target, i+1)); + } + + memo[cur] = ans; + + return ans; + } +}; diff --git a/DFS/3213.Construct-String-with-Minimum-Cost/Readme.md b/DFS/3213.Construct-String-with-Minimum-Cost/Readme.md new file mode 100644 index 000000000..1efcc2929 --- /dev/null +++ b/DFS/3213.Construct-String-with-Minimum-Cost/Readme.md @@ -0,0 +1,13 @@ +### 3213.Construct-String-with-Minimum-Cost + +此题似乎并没有什么特别好的办法。似乎只能暴力搜索,穷举target里的每一段是否适配某些word。 + +为了高效判定一段字符串是否出现在某些给定的words里,显然我们会先将所有word构造成一棵字典树。将word加入字典树的时候,记得在结尾节点附上该word的cost。如果有多个相同的word,我们只保留最小的cost。 + +我们定义dfs(i)表示target从位置i开始到结尾这段后缀成功分解所能得到的最小代价。我们就有: +```cpp +dfs(i) = min{cost of target[i:j] + dfs(j+1)}; for (int j=i; j>dir; + int memo[501][501][4][2]; +public: + int lenOfVDiagonal(vector>& grid) + { + int m = grid.size(), n = grid[0].size(); + int ret = 0; + dir = {{-1,1},{1,1},{1,-1},{-1,-1}}; + + for (int i=0; i=0 && i=0 && j>& grid, int x, int y, int k, int t) + { + if (memo[x][y][k][t]!=0) return memo[x][y][k][t]; + + int m = grid.size(), n = grid[0].size(); + int ret = 1; + + int i = x+dir[k].first, j = y+dir[k].second; + + if (inbound(i,j,m,n) && canContinue(grid[x][y], grid[i][j])) + ret = max(ret, 1 + dfs(grid,i,j,k,t)); + + if (t==1) + { + int kk=(k+1)%4; + i = x+dir[k].first, j = y+dir[k].second; + if (inbound(i,j,m,n) && canContinue(grid[x][y], grid[i][j])) + ret = max(ret, 1 + dfs(grid,i,j,kk,0)); + } + memo[x][y][k][t] = ret; + return ret; + } +}; diff --git a/DFS/3459.Length-of-Longest-V-Shaped-Diagonal-Segment/Readme.md b/DFS/3459.Length-of-Longest-V-Shaped-Diagonal-Segment/Readme.md new file mode 100644 index 000000000..4d9e572bd --- /dev/null +++ b/DFS/3459.Length-of-Longest-V-Shaped-Diagonal-Segment/Readme.md @@ -0,0 +1,9 @@ +### 3459.Length-of-Longest-V-Shaped-Diagonal-Segment + +很常规的深度优先搜索。每个格子、每个方向只会进入一次。所以最多有`500*500*4=1e6`种状态。再加上有一次转弯的机会,所以2e6种状态是可以遍历和存储下来的。 + +定义dfs(x,y,k,t)表示以k的方向进入(x,y)的格子、且还有t次转弯机会时,还能走的最长路径。如果t==0,那么只能按照k的方向进入下一个(i1,j1);否则还可以考察按照k+1的方向进入下一个(i2,j2). + +注意进入的下一个各自(i,j)和(x,y)要满足数值上的约束,否则即可停止往下搜索。 + +此外,本题的记忆化根据四个参数进行记忆化也是必须的。 diff --git a/DFS/3593.Minimum-Increments-to-Equalize-Leaf-Paths/3593.Minimum-Increments-to-Equalize-Leaf-Paths.cpp b/DFS/3593.Minimum-Increments-to-Equalize-Leaf-Paths/3593.Minimum-Increments-to-Equalize-Leaf-Paths.cpp new file mode 100644 index 000000000..ce6b54d7a --- /dev/null +++ b/DFS/3593.Minimum-Increments-to-Equalize-Leaf-Paths/3593.Minimum-Increments-to-Equalize-Leaf-Paths.cpp @@ -0,0 +1,37 @@ +class Solution { + vectoradj[100005]; +public: + pairdfs(int u, int p, vector&cost) { + long long maxPath = 0; + int totalChanged = 0; + vectorpaths; + + for (int v: adj[u]) { + if (v==p) continue; + auto [path, changed] = dfs(v, u, cost); + maxPath = max(maxPath, path); + totalChanged += changed; + paths.push_back(path); + } + + int count = 0; + for (long long p: paths) { + if (p < maxPath) + count++; + } + + return {cost[u]+maxPath, totalChanged + count}; + } + + int minIncrease(int n, vector>& edges, vector& cost) { + for (auto& edge: edges) { + int u = edge[0], v = edge[1]; + adj[u].push_back(v); + adj[v].push_back(u); + } + + auto [_, ret ] = dfs(0, -1, cost); + + return ret; + } +}; diff --git a/DFS/3593.Minimum-Increments-to-Equalize-Leaf-Paths/Readme.md b/DFS/3593.Minimum-Increments-to-Equalize-Leaf-Paths/Readme.md new file mode 100644 index 000000000..6f800c71e --- /dev/null +++ b/DFS/3593.Minimum-Increments-to-Equalize-Leaf-Paths/Readme.md @@ -0,0 +1,5 @@ +### 3593.Minimum-Increments-to-Equalize-Leaf-Paths + +很明显,为了减少increment的操作,我们会将所有root-to-leaf的路径与最长的那条路径对齐。但是我们并不需要显式地先求全局最长的路径,我们只需要用dfs函数将每一棵子树内的所有root-to-leaf路径长度拉齐即可。 + +具体地,对于根节点为u的子树,我们设计dfs返回该子树最终的最长路径maxPath,以及将其所有root-to-leaf的路径拉齐至maxPath所需要的操作数totalChanged. 我们做后序遍历,先对所有子节点都做一遍dfs。那么以u为根的maxPath就是所有子节点里最长的路径mx加上cost[u];以u为根的totalChanged就是所有子节点的totalChanged之和,再加上需要将路径长度拉高至mx的子路径的个数。 diff --git a/DFS/3615.Longest-Palindromic-Path-in-Graph/3615.Longest-Palindromic-Path-in-Graph.cpp b/DFS/3615.Longest-Palindromic-Path-in-Graph/3615.Longest-Palindromic-Path-in-Graph.cpp new file mode 100644 index 000000000..bb03c45b2 --- /dev/null +++ b/DFS/3615.Longest-Palindromic-Path-in-Graph/3615.Longest-Palindromic-Path-in-Graph.cpp @@ -0,0 +1,46 @@ +class Solution { + vectoradj[14]; + int memo[14][14][1<<14]; + string label; +public: + int dfs(int u, int v, int mask) { + if (memo[u][v][mask]!=-1) return memo[u][v][mask]; + int ret = 0; + for (int u2: adj[u]) { + if (mask&(1<>& edges, string label) { + this->label = label; + for (auto& e: edges) { + adj[e[0]].push_back(e[1]); + adj[e[1]].push_back(e[0]); + } + memset(memo, -1, sizeof(memo)); + + int ret = 1; + for (int u=0; udivisors; + vectorcur; + vectorrets; + int bestDiff = INT_MAX/2; +public: + void dfs(int idx, int n, int k) { + if (k==1) { + cur.push_back(n); + int diff = cur.back()-cur[0]; + if (diff < bestDiff) { + bestDiff = diff; + rets = cur; + } + cur.pop_back(); + return; + } + + for (int i = idx; i minDifference(int n, int k) { + for (int i=1; i*i<=n; i++) { + if (n%i==0) { + divisors.push_back(i); + if (i*i!=n) divisors.push_back(n/i); + } + } + sort(divisors.begin(), divisors.end()); + + dfs(0, n, k); + + return rets; + } +}; diff --git a/DFS/3669.Balanced-K-Factor-Decomposition/Readme.md b/DFS/3669.Balanced-K-Factor-Decomposition/Readme.md new file mode 100644 index 000000000..6af2ba84b --- /dev/null +++ b/DFS/3669.Balanced-K-Factor-Decomposition/Readme.md @@ -0,0 +1,7 @@ +### 3669.Balanced-K-Factor-Decomposition + +用sqrt(n)的时间将n的所有divisors求出来。本题转化为在divisors数组中寻找k个元素使得乘积恰好为n。因为k的个数较小,可以用暴力DFS解决。 + +递归函数`dfs(i,n,k)`表示要将n拆分为k个元素的乘积,当前可以从第i个divisor开始选择。选中某个约数d(编号为j)之后,即可递归处理`dfs(j,n/d,k-1)`.当k=1时,即可记录所选中的约数。 + +注意DFS过程中,当选择不同divisor时,已选中的数组需要有回溯操作。 diff --git a/Deque/2762.Continuous-Subarrays/2762.Continuous-Subarrays.cpp b/Deque/2762.Continuous-Subarrays/2762.Continuous-Subarrays.cpp new file mode 100644 index 000000000..072185152 --- /dev/null +++ b/Deque/2762.Continuous-Subarrays/2762.Continuous-Subarrays.cpp @@ -0,0 +1,36 @@ +using LL = long long; +class Solution { +public: + long long continuousSubarrays(vector& nums) + { + int n = nums.size(); + + dequedq1; + dequedq2; + + int i = 0; + LL ret = 0; + for (int j=0; j nums[j]) + dq2.pop_back(); + dq2.push_back(j); + + while (!dq1.empty() && !dq2.empty() && nums[dq1.front()]-nums[dq2.front()] > 2) + { + if (!dq1.empty() && dq1.front() <= i) + dq1.pop_front(); + if (!dq2.empty() && dq2.front() <= i) + dq2.pop_front(); + i++; + } + ret += LL(j-i+1); + } + + return ret; + } +}; diff --git a/Deque/2762.Continuous-Subarrays/Readme.md b/Deque/2762.Continuous-Subarrays/Readme.md new file mode 100644 index 000000000..27022ba1e --- /dev/null +++ b/Deque/2762.Continuous-Subarrays/Readme.md @@ -0,0 +1,5 @@ +### 2762.Continuous-Subarrays + +这是一个很常见的滑动窗口的题。总的规律是,窗口越长,越不容易满足条件。所以如果我们固定了左端点i,那么可以找到一个最远的右端点j使得[i:j]满足条件。那么以i为左端点的合法subarray的个数就是`j-i+1`.此后,我们必然只能移动左端点至i+1,而右端点必然也需要单调右移。 + +在窗口滑动的过程中,我们需要满足“最大值与最小值”之差不大于2. 显然我们用两个双端队列就能做到实时维护滑窗的最大值和最小值。 diff --git a/Deque/2969.Minimum-Number-of-Coins-for-Fruits-II/2969.Minimum-Number-of-Coins-for-Fruits-II.cpp b/Deque/2969.Minimum-Number-of-Coins-for-Fruits-II/2969.Minimum-Number-of-Coins-for-Fruits-II.cpp new file mode 100644 index 000000000..1a122f4c4 --- /dev/null +++ b/Deque/2969.Minimum-Number-of-Coins-for-Fruits-II/2969.Minimum-Number-of-Coins-for-Fruits-II.cpp @@ -0,0 +1,29 @@ +class Solution { + int dp[100005][2]; +public: + int minimumCoins(vector& prices) + { + int n = prices.size(); + prices.insert(prices.begin(), 0); + dp[1][0] = INT_MAX/2; + dp[1][1] = prices[1]; + + dequedq; + dq.push_back(1); + + for (int i=2; i<=n; i++) + { + dp[i][1] = min(dp[i-1][0], dp[i-1][1]) + prices[i]; + while (!dq.empty() && dq.front()*2= dp[i][1]) + dq.pop_back(); + dq.push_back(i); + } + + return min(dp[n][0], dp[n][1]); + + } +}; diff --git a/Deque/2969.Minimum-Number-of-Coins-for-Fruits-II/Readme.md b/Deque/2969.Minimum-Number-of-Coins-for-Fruits-II/Readme.md new file mode 100644 index 000000000..10e3dbfe6 --- /dev/null +++ b/Deque/2969.Minimum-Number-of-Coins-for-Fruits-II/Readme.md @@ -0,0 +1,11 @@ +### 2969.Minimum-Number-of-Coins-for-Fruits-II + +对于前i件物品而言,我们令dp[i][0]表示第i个水果不付款的最小代价,dp[i][1]表示第i个水果付款的最小代价。显然,我们容易得出 +```cpp +dp[i][1] = min(dp[i-1][0], dp[i-1][1]) + prices[i] +``` +那么对于dp[i][0]而言,第i个水果不用付款,必然是因为某第j个水果付款的缘故(需要满足`2*j>=i`)。这样的j可能有多个 +```cpp +dp[i][0] = min(dp[i-1][j]) j=(i+1)/2, (i+1)/2+1, ..., i-1. +``` +显然,这是求一个滑动窗口的最小值,使用双端队列deque的套路:我们维护一个递增的deque,里面盛装的是dp[x][1]的值。当队首元素不满足`2*j>=i`时就不断弹出,最终队首元素就是合法滑窗内的最小值,即给dp[i][0]赋值。然后将dp[i][1]入队,并将所有队尾元素大于等于dp[i][1]的都弹出,这是因为它们在数值大小或序列先后上都不及第i物品。 diff --git a/Deque/3578.Count-Partitions-With-Max-Min-Difference-at-Most-K/3578.Count-Partitions-With-Max-Min-Difference-at-Most-K.cpp b/Deque/3578.Count-Partitions-With-Max-Min-Difference-at-Most-K/3578.Count-Partitions-With-Max-Min-Difference-at-Most-K.cpp new file mode 100644 index 000000000..5eca1e201 --- /dev/null +++ b/Deque/3578.Count-Partitions-With-Max-Min-Difference-at-Most-K/3578.Count-Partitions-With-Max-Min-Difference-at-Most-K.cpp @@ -0,0 +1,43 @@ +class Solution { +public: + int countPartitions(vector& nums, int k) { + int n = nums.size(); + long M = 1e9+7; + nums.insert(nums.begin(), 0); + + vectordp(n+1); + vectorpresum(n+1); + dp[0] = 1; + presum[0] = 1; + + dequedq1; + dequedq2; + int left = 1; + for (int i=1; i<=n; i++) { + int x = nums[i]; + while (!dq1.empty() && nums[dq1.back()]x) { + dq2.pop_back(); + } + dq2.push_back(i); + + while (left <= i && nums[dq1.front()] - nums[dq2.front()] > k) { + if (dq1.front()==left) dq1.pop_front(); + if (dq2.front()==left) dq2.pop_front(); + left++; + } + // Any valid parition that ends at left-1, left, left+1, ..., i-1 is good. + + dp[i] = presum[i-1] - (left>=2?presum[left-2]:0); + presum[i] = presum[i-1] + dp[i]; + + dp[i] = (dp[i] + M) % M; + presum[i] = (presum[i] + M) % M; + } + + return dp[n]; + } +}; diff --git a/Deque/3578.Count-Partitions-With-Max-Min-Difference-at-Most-K/Readme.md b/Deque/3578.Count-Partitions-With-Max-Min-Difference-at-Most-K/Readme.md new file mode 100644 index 000000000..13958f4b7 --- /dev/null +++ b/Deque/3578.Count-Partitions-With-Max-Min-Difference-at-Most-K/Readme.md @@ -0,0 +1,11 @@ +### 3578.Count-Partitions-With-Max-Min-Difference-at-Most-K + +我们很容易想到令dp[i]表示以i为最后一段区间的结尾,可以得到的切割方案。此时我们需要考虑最后一段的起点位置j可以在哪里。 + +题目条件中“区间最大值与区间最小值的差”,很容易提示我们可以用deque来分别求区间的最大值和最小值。更具体地,当我们从nums[0]开始,不断加入元素,直至将nums[i]纳入区间时,我们可以得到此时区间的最大值a和最小值b:如果a-b>k,那么意味着区间太长,起点位置应该往右移动。这是因为区间越大,就越容易得到更大的a和更小的b,必然有更大的差值。区间越小,a-b的值就会越小,而且这是一个单调的过程。于是我们调整左端点j右移,每移动一次的过程中,我们查看一下nums[j]的退出是否会影响保存区间最大值和最小值的两个deque(因为我们知道当前最大值或最小值必然是deque的首元素)。直至我们将左端点移动到L的位置,意味着区间[L:i]恰好满足最大值与最小值之差小于k。 + +以上说明区间[L:i]是一个合法的切分,同理更小的区间[L+1:i],[L+2:i]...都是满足条件的切分。既然确定了最后一个区间的切法,那么就有`dp[i] = dp[L-1]+dp[L]+dp[L+1]...+dp[i-1]` 显然,我们会用一个DP的前缀和数组presum来辅助,即`dp[i] = presum[i-1]-presum[L-2]`. + +记得得到上述的dp[i]之后,就可以同样更新presum[i]。 + +以1-index考虑的话,最终的结果就是dp[n]。 diff --git a/Deque/3845.Maximum-Subarray-XOR-with-Bounded-Range/3845.Maximum-Subarray-XOR-with-Bounded-Range.cpp b/Deque/3845.Maximum-Subarray-XOR-with-Bounded-Range/3845.Maximum-Subarray-XOR-with-Bounded-Range.cpp new file mode 100644 index 000000000..9f3072696 --- /dev/null +++ b/Deque/3845.Maximum-Subarray-XOR-with-Bounded-Range/3845.Maximum-Subarray-XOR-with-Bounded-Range.cpp @@ -0,0 +1,80 @@ +class TrieNode { + public: + TrieNode* next[2]; + int count; + TrieNode() + { + for (int i=0; i<2; i++) + next[i]=NULL; + count = 0; + } +}; + +class Solution { + void updateTrie(int x, int delta) { + TrieNode* node = root; + for (int i=30; i>=0; i--) { + int b = ((x>>i)&1); + if (!node->next[b]) + node->next[b] = new TrieNode(); + node = node->next[b]; + node->count += delta; + } + } + + int find(int x) { + TrieNode* node = root; + int ret = 0; + for (int i=30; i>=0; i--) { + int b = ((x>>i)&1); + if (node->next[1-b] && (node->next[1-b]->count)>0) { + ret += ((1-b)<next[1-b]; + } else if (node->next[b] && (node->next[b]->count)>0) { + ret += (b<next[b]; + } + } + return ret^x; + } + + TrieNode* root; +public: + int maxXor(vector& nums, int k) { + root = new TrieNode(); + updateTrie(0, 1); + + int n = nums.size(); + nums.insert(nums.begin(), 0); + vectorprefix(n+1, 0); + for (int i=1; i<=n; i++) + prefix[i] = prefix[i-1]^nums[i]; + + int L = 1; + int ret = 0; + dequemindq, maxdq; + for (int r=1; r<=n; r++) { + while (!mindq.empty() && nums[mindq.back()]>=nums[r]) + mindq.pop_back(); + mindq.push_back(r); + while (!maxdq.empty() && nums[maxdq.back()]<=nums[r]) + maxdq.pop_back(); + maxdq.push_back(r); + + updateTrie(prefix[r], 1); + + while (Lk) { + if (L-1>=0) { + updateTrie(prefix[L-1], -1); + } + if (!mindq.empty() && mindq.front()==L) mindq.pop_front(); + if (!maxdq.empty() && maxdq.front()==L) maxdq.pop_front(); + L++; + } + + ret = max(ret, find(prefix[r])); + } + + return max(ret, nums[1]); + } +}; diff --git a/Deque/3845.Maximum-Subarray-XOR-with-Bounded-Range/Readme.md b/Deque/3845.Maximum-Subarray-XOR-with-Bounded-Range/Readme.md new file mode 100644 index 000000000..a6adc1964 --- /dev/null +++ b/Deque/3845.Maximum-Subarray-XOR-with-Bounded-Range/Readme.md @@ -0,0 +1,7 @@ +### 3845.Maximum-Subarray-XOR-with-Bounded-Range + +本题涉及到“最大值与最小值之差不超过k”的subarray,立即就能联想到经典的deque应用:求滑动窗口里的最大值和最小值,继而可以得到它们的差值。因为窗口越大,引入的元素就越可能增大最大值与最小值的差值。所以本题的基本架构就是双指针,外层循环逐个考察右端点R:相应地,内层单调移动左端点L,直至恰好[L,R]满足“最大值与最小值之差不超过k”。此时说明这个区间内的任意位置x,都能让[x,R]满足“最大值与最小值之差不超过k”。 + +那么在[L,R]的区间里,如果找到一个最优的位置P,使得子区间[P,R]的XOR SUM能最大化呢?很容易联想到XOR的性质:`XOR(P,R) = Pre(R) ^ Pre(P-1)`,其中Pre就是前缀的XOR SUM。此时我们可以维护一个字典树,只存Pre(L-1),Pre(L),...,Pre(R-1)。根据当前Pre(R)的值,可以在字典树里找到最优匹配的路径`T`(即从高到低尽量逐位取反)。显然,`Pre(R)^T`就对应着XOR SUM最大化的某个区间[P,R]。 + +本题的基本算法就是:遍历所有的R,找到对应的L,更新字典树(使得只包含L-1到R-1的前缀和),根据Pre(R)找到最优的匹配(从高到低尽量逐位取反)。最终取全局最大值。 diff --git a/Deque/3956.Maximum-Sum-of-M-Non-Overlapping-Subarrays-I/3956.Maximum-Sum-of-M-Non-Overlapping-Subarrays-I.cpp b/Deque/3956.Maximum-Sum-of-M-Non-Overlapping-Subarrays-I/3956.Maximum-Sum-of-M-Non-Overlapping-Subarrays-I.cpp new file mode 100644 index 000000000..a491ad613 --- /dev/null +++ b/Deque/3956.Maximum-Sum-of-M-Non-Overlapping-Subarrays-I/3956.Maximum-Sum-of-M-Non-Overlapping-Subarrays-I.cpp @@ -0,0 +1,43 @@ +using ll = long long; +class Solution { +public: + long long maximumSum(vector& nums, int m, int l, int r) { + // dp[k][i] = max{dp[k][i-1], dp[k-1][i-d] + presum[i]-presum[i-d]} l<=d<=r; + int n = nums.size(); + vectorpresum(n+1); + nums.insert(nums.begin(), 0); + for (int i=1; i<=n; i++) + presum[i] = presum[i-1] + nums[i]; + + vector>dp(m+1, vector(n+1, LLONG_MIN/2)); + for (int i=0; i<=n; i++) + dp[0][i] = 0; + + ll ret = LLONG_MIN/2; + for (int k=1; k<=m; k++) { + if (k*l>n) break; + deque>dq; + for (int i=1; i<=n; i++) { + if (i>=l) { + ll val = dp[k-1][i-l] - presum[i-l]; + while (!dq.empty() && dq.back().second<=val) + dq.pop_back(); + dq.push_back({i-l, val}); + } + if (i>=r) { + while (!dq.empty() && dq.front().first>temp; diff --git a/Dynamic_Programming/1473.Paint-House-III/Readme.md b/Dynamic_Programming/1473.Paint-House-III/Readme.md index 02ee623bd..b325e8394 100644 --- a/Dynamic_Programming/1473.Paint-House-III/Readme.md +++ b/Dynamic_Programming/1473.Paint-House-III/Readme.md @@ -7,7 +7,7 @@ 2. 当```house[i]==0```,说明第i个房子可以任意喷涂k=1,2,..,n,记得加上喷涂成本. 同理,遍历前一个房子的颜色kk。如果kk与k相同,那么第i个房子和前面的房子可以合并为一个block,即```dp[i][j][k] = min{self, dp[i-1][j][kk]+cost[i][k]}```。如果kk与k不同,那么第i个房子就是第j个block的第一个,即```dp[i][j][k] = min{self, dp[i-1][j-1][kk]+cost[i][k]}```。 -初始状态是```dp[0][0][j] = 0```,其余的状态都是无穷大。 +初始状态较为容易的写法是对第1座房子做单独分析。如果第一座房子已经喷涂,那么`dp[1][1][houses[1]] = 0`,否则`dp[1][1][k] = cost[1][k]`.其余的状态都设为无穷大。DP的转移从i=2开始。 最终的答案是在所有房子喷涂完、构造了target个block、最后一个房子颜色任意的前提下,取最小值。即```min{dp[m][target][k],for k=1,2,..,n``` diff --git a/Dynamic_Programming/152.Maximum-Product-Subarray/152.Maximum-Product-Subarray.cpp b/Dynamic_Programming/152.Maximum-Product-Subarray/152.Maximum-Product-Subarray.cpp deleted file mode 100644 index 7364334fd..000000000 --- a/Dynamic_Programming/152.Maximum-Product-Subarray/152.Maximum-Product-Subarray.cpp +++ /dev/null @@ -1,18 +0,0 @@ -class Solution { -public: - int maxProduct(vector& nums) - { - long MAX = 1; - long MIN = 1; - long ret = INT_MIN; - - for (int i=0; i& nums) - { - int n = nums.size(); - vectordp1(n); - vectordp2(n); + { + int n = nums.size(); + vectordp1(n); // the max prod subarray ending at i + vectordp2(n); // the min prod subarray ending at i dp1[0] = nums[0]; dp2[0] = nums[0]; - long ret = nums[0]; + LL ret = nums[0]; for (int i=1; ij)?0:dp[i+2][j]), dp[i+1][j]); } - return max(nums[0]+((2>n-2)?0:dp[2][n-2]), dp[1][n-1]); + return std::max(dp[0][n-2], dp[1][n-1]); } }; diff --git a/Dynamic_Programming/2172.Maximum-AND-Sum-of-Array/Readme.md b/Dynamic_Programming/2172.Maximum-AND-Sum-of-Array/Readme.md index 6688609f3..0af68beb7 100644 --- a/Dynamic_Programming/2172.Maximum-AND-Sum-of-Array/Readme.md +++ b/Dynamic_Programming/2172.Maximum-AND-Sum-of-Array/Readme.md @@ -1,6 +1,6 @@ ### 2172.Maximum-AND-Sum-of-Array -本题看上像二分图匹配问题。左边是一堆数字,右边是一对slots,要求匹配的边权之和最大。但是标准的二分图匹配要求每条边不能有公共边,本题则是允许最多两条边共享一个slot节点。 +本题看上像二分图匹配问题。左边是一堆数字,右边是一堆slots,要求匹配的边权之和最大。但是标准的二分图匹配要求每条边不能有公共边,本题则是允许最多两条边共享一个slot节点。 同以往一样,我们不用KM算法来解决带权最大二分图匹配,我们也不考虑最小费用最大流的做法,这里依然用状态压缩DP。 diff --git a/Dynamic_Programming/2272.Substring-With-Largest-Variance/Readme.md b/Dynamic_Programming/2272.Substring-With-Largest-Variance/Readme.md index 9b3376e01..107555228 100644 --- a/Dynamic_Programming/2272.Substring-With-Largest-Variance/Readme.md +++ b/Dynamic_Programming/2272.Substring-With-Largest-Variance/Readme.md @@ -27,7 +27,7 @@ ``` 特别注意,curSum0的初始值可以是0,但是curSum1的初始值必须设置为INT_MIN. -这样,总的时间复杂度是o(256n). +这样,总的时间复杂度是o(676n). #### 解法2 我们发现在上面的表达式里,当nums[i]不是1也不是-1的时候,curSum0和curSum1都没有更新,循环是空跑的。所以我们其实只需要关心那些nums[i]非0的位置。 @@ -36,7 +36,7 @@ 注意i和j可能会有其中某一个先走到尽头。如果其中一个走到尽头,那么我们必然移动另一个指针。 -这样的时间复杂度是多少?看上去仍然是是o(256n),但事实上,我们每固定了一个最大频次的字符a,其他所有字符都被看做为最小频次的字符,且只访问了一次。所以时间复杂度优化到了o(26n). +这样的时间复杂度是多少?看上去仍然是是o(676n),但事实上,我们每固定了一个最大频次的字符a,其他所有字符都被看做为最小频次的字符,且只访问了一次。所以时间复杂度优化到了o(26n). 补充: 有网友问,我感觉第二种做法不是严格的O(26 * N). https://youtu.be/P6KnO-Dw0Fo?t=2204 即使可以认为b的pos1循环遍26次就是O(N) (e.g. 小x, 小y), 但是a的pos0在内层循环也遍历了26次而不是一次. 所以两者加一起肯定大于o(N)但是小于O(26N) diff --git a/Dynamic_Programming/2572.Count-the-Number-of-Square-Free-Subsets/2572.Count-the-Number-of-Square-Free-Subsets.cpp b/Dynamic_Programming/2572.Count-the-Number-of-Square-Free-Subsets/2572.Count-the-Number-of-Square-Free-Subsets.cpp new file mode 100644 index 000000000..648ae675d --- /dev/null +++ b/Dynamic_Programming/2572.Count-the-Number-of-Square-Free-Subsets/2572.Count-the-Number-of-Square-Free-Subsets.cpp @@ -0,0 +1,52 @@ +using LL = long long; +class Solution { + LL dp[1005][1025]; + vectorprimes = {2,3,5,7,11,13,17,19,23,29}; + LL M = 1e9+7; +public: + int squareFreeSubsets(vector& nums) + { + int n = nums.size(); + nums.insert(nums.begin(), 0); + + LL ret = 0; + dp[0][0] = 1; + for (int i=1; i<=n; i++) + for (int state = 0; state < (1<<10); state++) + { + if (nums[i]==1) + { + dp[i][state] = dp[i-1][state] * 2 % M; + } + else + { + dp[i][state] = dp[i-1][state]; + int s = helper(nums[i]); + if (s!=-1 && (state&s)==s) + dp[i][state] = (dp[i][state] + dp[i-1][state-s]) % M; + } + if (i==n) + ret = (ret + dp[i][state]) % M; + } + return (ret+M-1)%M; + } + + int helper(int x) + { + int s = 0; + for (int i=0; i 1) + return -1; + else if (count==1) + s += (1<>& types) + { + int n = types.size(); + types.insert(types.begin(), {0,0}); + dp[0][0] = 1; + for (int i=1; i<=n; i++) + for (int j=0; j<=target; j++) + { + for (int k=0; k<=types[i][0]; k++) + { + if (k*types[i][1]>j) break; + dp[i][j] += dp[i-1][j- k*types[i][1]]; + dp[i][j] %= M; + } + } + return dp[n][target]; + } +}; diff --git a/Dynamic_Programming/2585.Number-of-Ways-to-Earn-Points/Readme.md b/Dynamic_Programming/2585.Number-of-Ways-to-Earn-Points/Readme.md new file mode 100644 index 000000000..1b6298c6a --- /dev/null +++ b/Dynamic_Programming/2585.Number-of-Ways-to-Earn-Points/Readme.md @@ -0,0 +1,15 @@ +### 2585.Number-of-Ways-to-Earn-Points + +非常常规的背包DP。将第二个下标设计为已经取得的分数。令dp[i][j]表示前i种题目里恰好取得j分的方案数。对于每种题目类型,我们尝试取不同的数目k。所以总共三层循环。比如,当第i种题目取k道题时,那么方案就取决于前i-1中题目里取`j- k*types[i][1]`分的方案数。 +```cpp +for (int i=1; i<=n; i++) + for (int j=0; j<=target; j++) + { + for (int k=0; k<=types[i][0]; k++) + { + if (k*types[i][1]>j) break; + dp[i][j] += dp[i-1][j- k*types[i][1]]; + dp[i][j] %= M; + } + } +``` diff --git a/Dynamic_Programming/2638.Count-the-Number-of-K-Free-Subsets/2638.Count-the-Number-of-K-Free-Subsets.cpp b/Dynamic_Programming/2638.Count-the-Number-of-K-Free-Subsets/2638.Count-the-Number-of-K-Free-Subsets.cpp new file mode 100644 index 000000000..368b6575f --- /dev/null +++ b/Dynamic_Programming/2638.Count-the-Number-of-K-Free-Subsets/2638.Count-the-Number-of-K-Free-Subsets.cpp @@ -0,0 +1,36 @@ +class Solution { +public: + long long countTheNumOfKFreeSubsets(vector& nums, int k) + { + vector>arr(k); + for (int x: nums) + arr[x%k].push_back(x); + + long long ret = 1; + for (int i=0; i& nums, int k) + { + sort(nums.begin(), nums.end()); + long long take = 0, no_take = 1; + for (int i=0; i=1 && nums[i] == nums[i-1]+k) + { + take = no_take_temp; + no_take = take_temp + no_take_temp; + } + else + { + take = take_temp + no_take_temp; + no_take = take_temp + no_take_temp; + } + } + return take + no_take; + } +}; diff --git a/Dynamic_Programming/2638.Count-the-Number-of-K-Free-Subsets/Readme.md b/Dynamic_Programming/2638.Count-the-Number-of-K-Free-Subsets/Readme.md new file mode 100644 index 000000000..6b7d53111 --- /dev/null +++ b/Dynamic_Programming/2638.Count-the-Number-of-K-Free-Subsets/Readme.md @@ -0,0 +1,7 @@ +### 2638.Count-the-Number-of-K-Free-Subsets + +此题和2597一模一样。将所有元素按照对k的模分组。 + +对于每组里的元素进行排序后,可以取任意的组合,但是相邻两个元素如果相差为k的话就不能同时取。这就是一个典型的house robber。 + +对于不同的组,彼此的取法互不影响,所以是乘法关系。 diff --git a/Dynamic_Programming/2713.Maximum-Strictly-Inreasing-Cells-in-a-Matrix/2713.Maximum-Strictly-Inreasing-Cells-in-a-Matrix.cpp b/Dynamic_Programming/2713.Maximum-Strictly-Inreasing-Cells-in-a-Matrix/2713.Maximum-Strictly-Inreasing-Cells-in-a-Matrix.cpp new file mode 100644 index 000000000..ab794e709 --- /dev/null +++ b/Dynamic_Programming/2713.Maximum-Strictly-Inreasing-Cells-in-a-Matrix/2713.Maximum-Strictly-Inreasing-Cells-in-a-Matrix.cpp @@ -0,0 +1,42 @@ +using AI3 = array; +class Solution { +public: + int maxIncreasingCells(vector>& mat) + { + int m = mat.size(), n = mat[0].size(); + vectornums; + for (int i=0; i> rows(m); + vector> cols(n); + + for (int i=0; isecond + 1); + + iter = cols[j].lower_bound(val); + iter = prev(iter); + len = max(len, iter->second + 1); + + rows[i][val] = max(len, rows[i][val]); + cols[j][val] = max(len, cols[j][val]); + + ret = max(ret, len); + } + + return ret; + } +}; diff --git a/Dynamic_Programming/2713.Maximum-Strictly-Inreasing-Cells-in-a-Matrix/Reamdme.md b/Dynamic_Programming/2713.Maximum-Strictly-Inreasing-Cells-in-a-Matrix/Reamdme.md new file mode 100644 index 000000000..71a10c72e --- /dev/null +++ b/Dynamic_Programming/2713.Maximum-Strictly-Inreasing-Cells-in-a-Matrix/Reamdme.md @@ -0,0 +1,7 @@ +### 2713.Maximum-Strictly-Inreasing-Cells-in-a-Matrix + +我们肯定是将所有的元素排序之后逐个处理。对于(i,j)考虑以它为结尾的递增序列可以多少长,必然会查看序列里它之前的元素,而前一个元素必然是在同一行或者同一列。所以我们只要在同行同列里查找所有比`mat[i][j]`小的位置(x,y)。以(x,y)为结尾的递增序列可以多少长,那么以(i,j)为结尾的递增序列长度就可以增加1。问题就转化为了递归或者动态规划。 + +接下来的问题是,如果扫描同行同列的所有元素,那么总的时间复杂度是`o(MN*M)`。事实上我们只需要查看同行(或者同列)里元素值恰好比`mat[i][j]`小的位置和对应的序列长度即可。所以我们给每行(以及每列)维护一个key有序的map,比如`rows[i][v] = 3`表示第三行里,以值为v的格子为结尾的递增序列的最大长度是3. 所以对于(i,j),我们用`prev(rows[i].lower_bound(mat[i][j])`就能定位最后一个恰好比mat[i][j]`小的位置。 + +注意在对所有的rows[i]和cols[j],初始化的时候添加一个`{INT_MIN, 0}`的key-val对,可以避免lower_bound出现越界。 diff --git a/Dynamic_Programming/2742.Painting-the-Walls/2742.Painting-the-Walls_v1.cpp b/Dynamic_Programming/2742.Painting-the-Walls/2742.Painting-the-Walls_v1.cpp new file mode 100644 index 000000000..eb19d2224 --- /dev/null +++ b/Dynamic_Programming/2742.Painting-the-Walls/2742.Painting-the-Walls_v1.cpp @@ -0,0 +1,31 @@ +class Solution { + int dp[505][505*2]; + int OFFSET = 505; +public: + int paintWalls(vector& cost, vector& time) + { + int n = cost.size(); + cost.insert(cost.begin(), 0); + time.insert(time.begin(), 0); + + for (int i=0; i<=n; i++) + for (int j=-n; j<=n; j++) + dp[i][j+OFFSET] = INT_MAX/2; + dp[0][OFFSET] = 0; + + for (int i=0; i& cost, vector& time) + { + int n = cost.size(); + cost.insert(cost.begin(),0); + time.insert(time.begin(),0); + + for (int i=0; i<=n; i++) + for (int j=0; j<=n; j++) + dp[i][j] = INT_MAX/2; + dp[0][0] = 0; + + for (int i=0; i=0`. + +我们遍历j的范围时,只需要从-n到n。这是因为如果j<=-n,说明至少使用了n个小时的免费工人,必然已经把任务完成。如果j>=n,说明至少使用了n个小时的付费工人,根据规则我们必然可以搭配n个小时的免费工人,也必然已经把任务完成了。所以dp计算的二维循环的时间复杂度是o(n^2). + +注意,本题的转移方程是“从现在到未来的形式”。即已知dp[i][j],我们考虑第i+1个任务时,根据付费还是免费工人两种方案,给未来的两个状态提供优化: +```cpp +dp[i+1][j-1] = min(dp[i+1][j-1], dp[i][j]); +dp[i+1][j+time[i+1]] = min(dp[i+1][j+time[i+1]], dp[i][j+OFFSET]+cost[i+1]); +``` +并且我们要注意`j+time[i+1]`可能会大于n,我们要取cap。这也是我们无法用“从现在到未来的形式”的原因,因为我们无法穷举`dp[i][n]=...`的来源。 + +#### 解法2: +此题还有另外一种巧解。我们将每个付费工人强制捆绑若干个免费工人,即看做可以花cost[i]的代价实现time[i]+1的任务。问至少(不是恰好)实现n个任务的最小代价。这是因为“强制捆绑若干个免费工人”的做法无法保证总完成的任务恰好n,极有可能超过n,如果那种情况发生,我们可以再任意踢掉免费的工人(将完成任务的数量降到n)。 + +此时我们定义状态dp[i][j]表示前i个工人(不一定都用)完成j个任务的最小代价。那么这就是一个典型的背包问题。 + +同理,我们也得用“从现在到未来的形式”,即已知dp[i][j],我们考虑第i+1个工人,根据是否雇佣他两种方案,给未来的两个状态提供优化: +```cpp +dp[i+1][j+time[i+1]+1] = min(dp[i+1][j+time[i+1]+1], dp[i][j]+cost[i+1]); +dp[i+1][j] = min(dp[i+1][j], dp[i][j]); +``` +同理,我们也要注意`j+time[i+1]+1`必须cap by n。 + +最终返回的答案是dp[n][n]. diff --git a/Dynamic_Programming/2786.Visit-Array-Positions-to-Maximize-Score/2786.Visit-Array-Positions-to-Maximize-Score.cpp b/Dynamic_Programming/2786.Visit-Array-Positions-to-Maximize-Score/2786.Visit-Array-Positions-to-Maximize-Score.cpp new file mode 100644 index 000000000..4db44261b --- /dev/null +++ b/Dynamic_Programming/2786.Visit-Array-Positions-to-Maximize-Score/2786.Visit-Array-Positions-to-Maximize-Score.cpp @@ -0,0 +1,34 @@ +using LL = long long; +class Solution { +public: + long long maxScore(vector& nums, int x) + { + int n = nums.size(); + + vector>dp(n, vector(2,LLONG_MIN/2)); + if (nums[0]%2==0) + dp[0][0] = nums[0]; + else + dp[0][1] = nums[0]; + + for (int i=1; i= num; s--) + { + dp[s] += dp[s-num]; + dp[s] %= M; + } + } + + return dp[n]; + } +}; diff --git a/Dynamic_Programming/2787.Ways-to-Express-an-Integer-as-Sum-of-Powers/Readme.md b/Dynamic_Programming/2787.Ways-to-Express-an-Integer-as-Sum-of-Powers/Readme.md new file mode 100644 index 000000000..d5cd9e540 --- /dev/null +++ b/Dynamic_Programming/2787.Ways-to-Express-an-Integer-as-Sum-of-Powers/Readme.md @@ -0,0 +1,13 @@ +### 2787.Ways-to-Express-an-Integer-as-Sum-of-Powers + +#### 解法1: +令dp[i][j]表示数字i可以分解的方案数目,并且要求分解出的最大的因子不能超过j。 + +如果该分解不包含`j^x`,那么就有`dp[i][j] = dp[i][j-1]`; 如果该分解包含了`j^x`,并且`i>=j^x`,则有`dp[i][j] = dp[i-j^x][j-1]`. 两者之后即是dp[i][j]。 + +最终答案是dp[n][n]. + +#### 解法2: +令dp[i]表示数字i可以分解的方案数目。 + +我们从小到大依次考虑因子1,2,3,...n的使用。当可以使用j^x时,所有的dp数列可以更新:`dp_new[i] = dp_old[i] + dp_old[i-j^x]`. 这样刷新n遍dp数组,最终的答案是dp[n]. diff --git a/Dynamic_Programming/2809.Minimum-Time-to-Make-Array-Sum-At-Most-x/2809.Minimum-Time-to-Make-Array-Sum-At-Most-x.cpp b/Dynamic_Programming/2809.Minimum-Time-to-Make-Array-Sum-At-Most-x/2809.Minimum-Time-to-Make-Array-Sum-At-Most-x.cpp new file mode 100644 index 000000000..da9c00f6f --- /dev/null +++ b/Dynamic_Programming/2809.Minimum-Time-to-Make-Array-Sum-At-Most-x/2809.Minimum-Time-to-Make-Array-Sum-At-Most-x.cpp @@ -0,0 +1,35 @@ +using PII = pair; +using LL = long long; +class Solution { + LL dp[1005][1005]; + LL presum[1005]; + int n; +public: + int minimumTime(vector& nums1, vector& nums2, int x) + { + n = nums1.size(); + + vectorarr; + for (int i=0; i=1) dp[i][j] = min(dp[i][j], dp[i-1][j-1] + presum[i-1]); + } + + for (int t=0; t<=n; t++) + if (dp[n][t]<=x) return t; + return -1; + } +}; diff --git a/Dynamic_Programming/2809.Minimum-Time-to-Make-Array-Sum-At-Most-x/Readme.md b/Dynamic_Programming/2809.Minimum-Time-to-Make-Array-Sum-At-Most-x/Readme.md new file mode 100644 index 000000000..ca83884e3 --- /dev/null +++ b/Dynamic_Programming/2809.Minimum-Time-to-Make-Array-Sum-At-Most-x/Readme.md @@ -0,0 +1,28 @@ +### 2809.Minimum-Time-to-Make-Array-Sum-At-Most-x + +首先我们要知道,我们不会给同一个位置的数字重复清零操作,因为后一次清零会完全浪费前一次清零。所以我们最多只会进行n次清零。 + +其次,这道题给人有一种错觉,使用清零次数与达成目标之间存在单调性的关系,即用的清零次数越多,就越容易实现sum<=x的目标。 + +我们先承认这种错觉。那么它会引导我们用二分搜值的思想,即给定清零次数T,我们是否能构造一种方案使得`sum<=x`呢? 我们想象一下,使用了T次清零之后,剩余的sum必然是这种形式 +``` +sum = {0 + nums2[a]*1 + nums2[b]*2 + ... + nums2[c] * (T-1)} + + {nums1[x]+nums2[x]*T + nums1[y]+nums2[y]*T + .... nums1[z]+nums2[z]*T} +``` +也就是说,我们需要将元素分为两部分,前一部分是apply了清零操作,后一部分是没有apply清零操作。显然,对于前一部分,为了使得sum最小,我们会按照nums2的数值倒序排列。对于后一部分,对于顺序没有要求。 + +那么我们该如何将元素进行最优的分割呢?暴力尝试的话需要2^n。有更好的方法吗?其实这可以考虑成01背包问题,我们将所有元素按照nums2升序排序:每个元素有“取”或者“不取”两种决策,求取T个元素时的最小代价。所以我们很容易定义dp[i][j]表示前i个元素里面清零j个元素的最小代价。 +1. 当清零第i个元素时,因为nums2值最大,第i个元素必然是最后一个被清零才合算。说明我们在前i-1个元素依然用了j-1次清零,第j次清零使得nums[i]以0进入代价,同时也让之前的i-1个元素多了一轮“回血”,故增加的代价是nums2[1:i-1]。 +2. 当不清零第i个元素时,说明第i个元素此时经历了j轮回血,故增加的代价是`nums1[i]+nums2[i]*j`。 + +综上我们可以计算出任意的dp[i][j]. 通过`dp[i][T]<=x`就可以判断能否通过T次清零实现目标。 + +但是注意,本题里的单调性是不成立的。例如 +``` +[9,10,10,5,2,4] +[2,4,0,3,3,4] +40 +``` +这组数据。不清零已经符合条件。清零1次,反而结果最小只能是42了。有可能确实没有单调性。 + +事实上,一次DP已经解决所有的问题。我们只需寻找最小的j,使得`dp[i][j]<=x`即是答案。 diff --git a/Dynamic_Programming/2826.Sorting-Three-Groups/2826.Sorting-Three-Groups.cpp b/Dynamic_Programming/2826.Sorting-Three-Groups/2826.Sorting-Three-Groups.cpp new file mode 100644 index 000000000..f3220d39a --- /dev/null +++ b/Dynamic_Programming/2826.Sorting-Three-Groups/2826.Sorting-Three-Groups.cpp @@ -0,0 +1,18 @@ +class Solution { + int dp[105][4]; +public: + int minimumOperations(vector& nums) + { + int n = nums.size(); + nums.insert(nums.begin(), 0); + for (int i=1; i<=n; i++) + { + dp[i][1] = dp[i-1][1] + (nums[i]!=1); + dp[i][2] = min(dp[i-1][1], dp[i-1][2]) + (nums[i]!=2); + dp[i][3] = min(min(dp[i-1][1], dp[i-1][2]), dp[i-1][3]) + (nums[i]!=3); + } + + return min(min(dp[n][1], dp[n][2]), dp[n][3]); + + } +}; diff --git a/Dynamic_Programming/2826.Sorting-Three-Groups/Readme.md b/Dynamic_Programming/2826.Sorting-Three-Groups/Readme.md new file mode 100644 index 000000000..f2d07f8f1 --- /dev/null +++ b/Dynamic_Programming/2826.Sorting-Three-Groups/Readme.md @@ -0,0 +1,9 @@ +### 2826.Sorting-Three-Groups + +令dp[i][j]表示截止到第i个元素为止构成j个group的最小代价,其中j=1,2,3. 显然有 +``` +dp[i][1] = dp[i-1][1] + (nums[i]!=1); +dp[i][2] = min(dp[i-1][1], dp[i-1][2]) + (nums[i]!=2); +dp[i][3] = min(min(dp[i-1][1], dp[i-1][2]), dp[i-1][3]) + (nums[i]!=3); +``` +最终返回dp[n][1],dp[n][2],dp[n][3]中的最小值。 diff --git a/Dynamic_Programming/2830.Maximize-the-Profit-as-the-Salesman/2830.Maximize-the-Profit-as-the-Salesman.cpp b/Dynamic_Programming/2830.Maximize-the-Profit-as-the-Salesman/2830.Maximize-the-Profit-as-the-Salesman.cpp new file mode 100644 index 000000000..54286a5f4 --- /dev/null +++ b/Dynamic_Programming/2830.Maximize-the-Profit-as-the-Salesman/2830.Maximize-the-Profit-as-the-Salesman.cpp @@ -0,0 +1,21 @@ +class Solution { + int dp[100005]; +public: + int maximizeTheProfit(int n, vector>& offers) + { + + unordered_map>>Map; + for (auto& offer:offers) + Map[offer[1]+1].push_back({offer[0]+1, offer[2]}); + + for (int i=1; i<=n; i++) + { + dp[i] = dp[i-1]; + for (auto& [start, val]: Map[i]) + dp[i] = max(dp[i], dp[start-1] + val); + } + + return dp[n]; + + } +}; diff --git a/Dynamic_Programming/2830.Maximize-the-Profit-as-the-Salesman/Readme.md b/Dynamic_Programming/2830.Maximize-the-Profit-as-the-Salesman/Readme.md new file mode 100644 index 000000000..8718d73d7 --- /dev/null +++ b/Dynamic_Programming/2830.Maximize-the-Profit-as-the-Salesman/Readme.md @@ -0,0 +1,7 @@ +### 2830.Maximize-the-Profit-as-the-Salesman + +此题和`2008.Maximum-Earnings-From-Taxi`几乎一样。考虑到`the number of houses`只有1e5级别,最简单的方法就是令dp[i]前i个房子所能得到的最大收益。 + +我们遍历以i结尾的offer,如果该offer的跨度是从[j,i],价值是v,那么我们就有一种转移的方法`dp[i]=dp[j-1]+val`. 除此之外,如果不考虑任何offer,则有`dp[i]=dp[i-1]`. 我们从中选一个最优解作为dp[i]即可。 + +如果本题里houses的数目是1e9级别,我们就需要进行离散化的处理,将所有offer的右边界组成数组T,排序后进行遍历。对于跨度是[t1,t2]的offer,我们需要用二分法在T中找到最后一个小于等于t1的下标,再进行dp的转移。 diff --git a/Dynamic_Programming/2851.String-Transformation/2851.String-Transformation.cpp b/Dynamic_Programming/2851.String-Transformation/2851.String-Transformation.cpp new file mode 100644 index 000000000..11bf0bf25 --- /dev/null +++ b/Dynamic_Programming/2851.String-Transformation/2851.String-Transformation.cpp @@ -0,0 +1,82 @@ +using LL = long long; +LL M = 1e9+7; +class Solution { +public: + int numberOfWays(string s, string t, long long k) + { + string ss = s+s; + ss.pop_back(); + int p = strStr(ss,t); + + int n = s.size(); + vector T = {n-p-1, n-p, p, p-1}; + vector Tk = quickMul(T, k); + + if (s==t) + return Tk[3]; // Tk * (0, 1)' + else + return Tk[2]; // Tk * (1, 0)' + } + + vector multiply(vectormat1, vectormat2) + { + // a1 b1 a2 b2 + // c1 d1 c2 d2 + LL a1 = mat1[0], b1 = mat1[1], c1 = mat1[2], d1 = mat1[3]; + LL a2 = mat2[0], b2 = mat2[1], c2 = mat2[2], d2 = mat2[3]; + return {(a1*a2+b1*c2)%M, (a1*b2+b1*d2)%M, (c1*a2+d1*c2)%M, (c1*b2+d1*d2)%M}; + } + + vector quickMul(vectormat, LL N) { + if (N == 0) { + return {1,0,0,1}; + } + vector mat2 = quickMul(mat, N/2); + if (N%2==0) + return multiply(mat2, mat2); + else + return multiply(multiply(mat2, mat2), mat); + } + + int strStr(string haystack, string needle) + { + int count = 0; + + int n = haystack.size(); + int m = needle.size(); + + vector suf = preprocess(needle); + + vectordp(n,0); + dp[0] = (haystack[0]==needle[0]); + if (m==1 && dp[0]==1) + count++; + + for (int i=1; i0 && haystack[i]!=needle[j]) + j = suf[j-1]; + dp[i] = j + (haystack[i]==needle[j]); + if (dp[i]==needle.size()) + count++; + } + return count; + } + + vector preprocess(string s) + { + int n = s.size(); + vectordp(n,0); + for (int i=1; i=1 && s[j]!=s[i]) + { + j = dp[j-1]; + } + dp[i] = j + (s[j]==s[i]); + } + return dp; + } +}; diff --git a/Dynamic_Programming/2851.String-Transformation/Readme.md b/Dynamic_Programming/2851.String-Transformation/Readme.md new file mode 100644 index 000000000..73463292b --- /dev/null +++ b/Dynamic_Programming/2851.String-Transformation/Readme.md @@ -0,0 +1,26 @@ +### 2851.String-Transformation + +首先,本题中的操作相当于切牌。无论一次切最后k张牌,都等效于切k次最后一张牌。最终得到的序列依然是原序列的shift而已。我们记s(i)表示以将字符串s调整后、变成以原来第i个元素为首的一个shift、 + +显然,只有对应部分的i,可以使得`s(i)=t`。我们可以先用KMP算法,算出t在`s+s`中能匹配几次。我们就可以记录有p种shift使得`s(i)=t`,其中`p<=n`. + +对于每次操作,我们有n-1次选择(对应不同的shift),那么经过k次操作之后,s(i)的分布是什么呢?我们特别关心上述的p种shift,因为它们对应着我们想要的答案。 + +我们令f[j]表示经过j次操作后不是想要的shift(我们称为未匹配)的操作数目(也就是字串数目),令g[j]表示经过j次操作后恰是想要的shift(称为匹配)的操作数(也就是字串数目)。我们有动态转移方程: +``` +f[j] = (n-p-1)*f[j-1] + (n-p)*g[j-1] +g[j] = p*f[j-1] + (p-1)*g[j-1] +``` +第一行的解释:对于j-1轮不匹配的字串,下一轮有n-p-1种操作依然得到不匹配的字串(因为不能shift成自己)。对于j-1轮已经匹配的字串,下一轮有n-p种操作变成不匹配的字串。同理第二行的解释:对于j-1轮不匹配的字串,下一轮有p种操作变成匹配的字串。对于j-1轮已经匹配的字串,下一轮有p-1种操作依然变成匹配的字串(因为不能shift成自己)。 + +所以我们有状态转移 (f,g)'(j) = T * (f,g)'(j-1),其中转移矩阵 +``` +T = [n-p-1, n-p + p, p-1 ] +``` +所以第k轮操作之后,(f,g)'(k) = T^k * (f,g)'(0). 注意,T^k依然是一个2x2的矩阵。 + +其中如果初始时s==t,那么(f,g)(0) = {0, 1},否则 (f,g)(0) = {1, 0}。 另外`T^k`可以用快速幂的思想,用log(k)的时间计算。最后记得再与初始状态`(f,g)'(0)`相乘。 + +由此我们计算出 (f,g)(k),得到第k轮时变成未匹配字串的数目,以及变成匹配字串的数目(答案)。 + diff --git a/Dynamic_Programming/2896.Apply-Operations-to-Make-Two-Strings-Equal/2896.Apply-Operations-to-Make-Two-Strings-Equal_v1.cpp b/Dynamic_Programming/2896.Apply-Operations-to-Make-Two-Strings-Equal/2896.Apply-Operations-to-Make-Two-Strings-Equal_v1.cpp new file mode 100644 index 000000000..0829893d3 --- /dev/null +++ b/Dynamic_Programming/2896.Apply-Operations-to-Make-Two-Strings-Equal/2896.Apply-Operations-to-Make-Two-Strings-Equal_v1.cpp @@ -0,0 +1,35 @@ +class Solution { +public: + int minOperations(string s1, string s2, int x) + { + int ret = 0; + + vectornums; + for (int i=0; i>dp(n, vector(n, INT_MAX/2)); + for (int i=0; i+1nums; + for (int i=0; i>dp(n+1, vector(n+1, INT_MAX/2)); + dp[0][0] = 0; + + for (int i=1; i<=n; i++) + for (int j=0; j<=1; j++) + { + if (i-2>=0) + dp[i][j] = min(dp[i][j], dp[i-2][j] + (nums[i]-nums[i-1])); + + if (j-1>=0 && j-1<=i-1) + dp[i][j] = min(dp[i][j], dp[i-1][j-1] + x); + + if (j+1<=i-1) + dp[i][j] = min(dp[i][j], dp[i-1][j+1]); + } + + + return dp[n][0]; + } +}; diff --git a/Dynamic_Programming/2896.Apply-Operations-to-Make-Two-Strings-Equal/Readme.md b/Dynamic_Programming/2896.Apply-Operations-to-Make-Two-Strings-Equal/Readme.md new file mode 100644 index 000000000..72e39e00a --- /dev/null +++ b/Dynamic_Programming/2896.Apply-Operations-to-Make-Two-Strings-Equal/Readme.md @@ -0,0 +1,47 @@ +### 2896.Apply-Operations-to-Make-Two-Strings-Equal + +注意到,如果从i开始的、连续操作k次相邻元素的flip(每次代价为1),本质上就是将i和i+k距离k的两个元素flip,其他元素保持不变,代价就是k。 + +所以,我们直接将s1和s2里面元素不同的index拿出来放在nums数组里。于是任务就是:每次在nums里挑两个(未访问过的)元素i与j,代价是nums[j]-nums[i],或者x。问最少花多少代价能将nums全部访问。当然,nums的元素个数必须是偶数,否则无解。 + +我们特别注意到,对于第一种操作,只会发生在nums里的两个相邻元素之间。为什么呢?假设有4个元素`p,...,k,j,i`,其中`k,j,i`是相邻的。如果我们将k与i按照第一种操作配对,代价是nums[i]-nums[k];而将j与[k,i]之外的某个p配对,代价是c(p,j)。我们发现,`nums[i]-nums[k] >= nums[i]-nums[j]`,且`cost(p,j) >= cost(p,k)`,所以有`nums[i]-nums[k]+cost(p,j) >= nums[i]-nums[j]+ cost(p,k)`,也就是说不如将“i与j配对,k与p配对”来的更优。 + +接下来思考整个问题。首先要明确并没有任何贪心的方法。每次如何挑选两个元素,并没有特定的规律,最优解会随着数据的不同有各种不同的表现。我们只能用DP或者搜索的方式来解。 + +### 解法1:o(n^3) +最容易想到的是一个o(N^3)的区间DP。我们想得到区间的最优解dp[i][j],只有两种拆解的方式: +1. 遍历一个中间的分界点k,我们先将[i:k]处理完,再将[k+1:j]处理完,那么dp[i][j]就是这两部分最优代价的和。 +2. 最后一个访问的pair是(i,j),所以dp[i][j] = dp[i+1][j-1] + cost(i,j). + +最终取最优的解作为dp[i][j]. 大致的代价如下 +```cpp +for (int d = 1; d<=n; d++) { + for (int i=0; i+d-1>arr; + int count0 = 0; +public: + int countSubMultisets(vector& nums, int l, int r) + { + unordered_mapMap; + for (int x: nums) + { + if (x==0) count0++; + else Map[x]++; + } + + for (auto& p:Map) + arr.push_back(p); + + arr.insert(arr.begin(), {0,0}); + + return (helper(r) - helper(l-1) + M) % M; + } + + int helper(int limit) + { + if (limit<0) return 0; + + int n = arr.size() - 1; + + vector>dp(n+1, vector(limit+1, 0)); + + dp[0][0] = 1; + + for (int i=1; i<=n; i++) + { + auto [v, c] = arr[i]; + for (int j=0; j<=limit; j++) + { + dp[i][j] = (jdp(p1*p2+1); + dp[0] = 1; + int ret = 0; + for (int i=1; i<=p1*p2; i++) + { + dp[i] = (i>=p1 && dp[i-p1]) || (i>=p2 && dp[i-p2]); + if (dp[i]==0) ret = max(ret, i); + } + return ret; + } +}; diff --git a/Dynamic_Programming/2979.Most-Expensive-Item-That-Can-Not-Be-Bought/Readme.md b/Dynamic_Programming/2979.Most-Expensive-Item-That-Can-Not-Be-Bought/Readme.md new file mode 100644 index 000000000..ab903b0cb --- /dev/null +++ b/Dynamic_Programming/2979.Most-Expensive-Item-That-Can-Not-Be-Bought/Readme.md @@ -0,0 +1,5 @@ +### 2979.Most-Expensive-Item-That-Can-Not-Be-Bought + +本题是给出两个质数p1和p2,求不能写成p1与p2的线性组合的最大自然数。此题有数学解,就是`p1*p2-p1-p2`. + +事实上此题有常规的DP解法。令dp[i]表示i是否能写成p1和p2的线性组合,则有`dp[i]=dp[i-p1]||dp[i-p2]`。当我们尝试到`i=p1*p2`时即可停止。事实上大于`p1*p2`的自然数必然能写成两者的线性组合。 diff --git a/Dynamic_Programming/3018.Maximum-Number-of-Removal-Queries-That-Can-Be-Processed-I/3018.Maximum-Number-of-Removal-Queries-That-Can-Be-Processed-I.cpp b/Dynamic_Programming/3018.Maximum-Number-of-Removal-Queries-That-Can-Be-Processed-I/3018.Maximum-Number-of-Removal-Queries-That-Can-Be-Processed-I.cpp new file mode 100644 index 000000000..42ae27531 --- /dev/null +++ b/Dynamic_Programming/3018.Maximum-Number-of-Removal-Queries-That-Can-Be-Processed-I/3018.Maximum-Number-of-Removal-Queries-That-Can-Be-Processed-I.cpp @@ -0,0 +1,41 @@ +class Solution { + int dp[1005][1005]; +public: + int maximumProcessableQueries(vector& nums, vector& queries) + { + int n = nums.size(); + int ret = 0; + + dp[0][n-1] = 0; + for (int len = n-1; len >=1; len--) + for (int i=0; i+len-1=0) + { + int t = dp[i-1][j]; + if (t= queries[t]) + dp[i][j] = max(dp[i][j], t + 1); + else + dp[i][j] = max(dp[i][j], t); + } + if (j+1= queries[t]) + dp[i][j] = max(dp[i][j], t + 1); + else + dp[i][j] = max(dp[i][j], t); + } + } + + for (int i=0; i=queries[dp[i][i]]) + ret = max(ret, dp[i][i]+1); + else + ret = max(ret, dp[i][i]); + } + return ret; + } +}; diff --git a/Dynamic_Programming/3018.Maximum-Number-of-Removal-Queries-That-Can-Be-Processed-I/Readme.md b/Dynamic_Programming/3018.Maximum-Number-of-Removal-Queries-That-Can-Be-Processed-I/Readme.md new file mode 100644 index 000000000..6f9636866 --- /dev/null +++ b/Dynamic_Programming/3018.Maximum-Number-of-Removal-Queries-That-Can-Be-Processed-I/Readme.md @@ -0,0 +1,12 @@ +### 3018.Maximum-Number-of-Removal-Queries-That-Can-Be-Processed-I + +我们令dp[i][j]表示将nums砍至区间[i:j]时,能够通过多少个queries。显然,dp[i][j]是可以由dp[i-1][j]或dp[i][j+1]转化来的。例如,令`dp[i-1][j]=t`,说明操作至[i-1:j]时已经通过了t个queries,那么如果`nums[i-1]>=queries[t]`的话,就可以再砍去nums[i-1]使得通过`t+1`个queries. 反之如果`nums[i-1]=queries[t]`,那么意味着nums可以全部被删除(即通过t+1个queries)。在更新最终答案时需要额外处理这种情况。 + diff --git a/Dynamic_Programming/3041.Maximize-Consecutive-Elements-in-an-Array-After-Modification/3041.Maximize-Consecutive-Elements-in-an-Array-After-Modification.cpp b/Dynamic_Programming/3041.Maximize-Consecutive-Elements-in-an-Array-After-Modification/3041.Maximize-Consecutive-Elements-in-an-Array-After-Modification.cpp new file mode 100644 index 000000000..1c9611b2a --- /dev/null +++ b/Dynamic_Programming/3041.Maximize-Consecutive-Elements-in-an-Array-After-Modification/3041.Maximize-Consecutive-Elements-in-an-Array-After-Modification.cpp @@ -0,0 +1,39 @@ +class Solution { + int dp[100005][2]; +public: + int maxSelectedElements(vector& nums) + { + sort(nums.begin(), nums.end()); + int n = nums.size(); + + dp[0][0] = 1; + dp[0][1] = 1; + + int ret = 1; + + for (int i=1; i& nums, int k) + { + int n = nums.size(); + nums.insert(nums.begin(), 0); + + vector>>dp(n+1, vector>(k+1, vector(2, LLONG_MIN/3))); + + for (int i=0; i<=n; i++) + { + dp[i][0][0] = 0; + } + + for (int i=1; i<=n; i++) + for (int j=1; j<=k; j++) + { + if (j%2==0) + { + dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1]); + dp[i][j][1] = max(dp[i-1][j][1], max(dp[i-1][j-1][0], dp[i-1][j-1][1])) - (LL)nums[i]*(k+1-j); + } + else + { + dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1]); + dp[i][j][1] = max(dp[i-1][j][1], max(dp[i-1][j-1][0], dp[i-1][j-1][1])) + (LL)nums[i]*(k+1-j); + } + } + + return max(dp[n][k][0],dp[n][k][1]); + + } +}; diff --git a/Dynamic_Programming/3077.Maximum-Strength-of-K-Disjoint-Subarrays/Readme.md b/Dynamic_Programming/3077.Maximum-Strength-of-K-Disjoint-Subarrays/Readme.md new file mode 100644 index 000000000..3380b64cc --- /dev/null +++ b/Dynamic_Programming/3077.Maximum-Strength-of-K-Disjoint-Subarrays/Readme.md @@ -0,0 +1,18 @@ +### 3077.Maximum-Strength-of-K-Disjoint-Subarrays + +我们令dp[i][j]表示前i个元素里找出j个subarray的最优解。注意,我们认为k是个常数,即`dp[i][j] = sum[1]*k - sum[2]*(k-1) + ...`,而不是`dp[i][j] = sum[1]*j - sum[2]*(j-1) + ...`. + +显然,我们在考虑dp[i][j]时,会思考对于nums[i]的决策。如果nums[i]不加入任何subarray,那么就有`dp[i][j] = dp[i-1][j]`. 如果nums[i]加入subarray,那么它就是属于sum[j]。但是此时有一个问题,它是加入已有的sum[j]呢,还是自己独创一个sum[j]。前者的话就是`dp[i-1][j]+nums[i]`,后者就是`dp[i-1][j-1]+nums[i]`. 但是注意到,前者要求`dp[i-1][j]`中的sum[j]必须结尾在第i-1个元素,才能将nums[i]顺利接在sum[j]里,而我们的dp定义并没有这个约束。 + +为了解决这个问题,我们重新定义dp,加入第三个维度表示“最后一个subarray是否以当前元素结尾”。即dp[i][j][0]表示前i个元素分成j个subarray,且nums[i]不参与最后一个subarray;类似dp[i][j][1]表示前i个元素分成j个subarray,且nums[i]参与了最后一个subarray。于是我们容易写出新的转移方程。以j是偶数为例,对于dp[i][j][0],由于nums[i]不起作用,完全取决于dp[i-1][j],不用考虑它的第三个维度: +``` +dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1]); +``` +对于dp[i][j][1],我们需要考虑nums[i]是否是接在nums[i-1]后面属于同一个subarray,还是自己新成立一个subarray。如果是前者,我们考虑的前驱状态是dp[i-1][j][1]; 如果是后者,我们考虑的前驱状态是dp[i-1][j-1][x] +``` +dp[i][j][1] = max(dp[i-1][j][1], max(dp[i-1][j-1][0], dp[i-1][j-1][1])) - (LL)nums[i]*(k+1-j); +``` +最终返回的答案是`max(dp[n][k][0], dp[n][k][1])`. + +初始状态是对于所有的dp[i][0][0]赋值为零,其他都设为负无穷大。 + diff --git a/Dynamic_Programming/3082.Find-the-Sum-of-the-Power-of-All-Subsequences/3082.Find-the-Sum-of-the-Power-of-All-Subsequences.cpp b/Dynamic_Programming/3082.Find-the-Sum-of-the-Power-of-All-Subsequences/3082.Find-the-Sum-of-the-Power-of-All-Subsequences.cpp new file mode 100644 index 000000000..60b9dccc8 --- /dev/null +++ b/Dynamic_Programming/3082.Find-the-Sum-of-the-Power-of-All-Subsequences/3082.Find-the-Sum-of-the-Power-of-All-Subsequences.cpp @@ -0,0 +1,37 @@ +using LL = long long; +class Solution { + LL dp[105][105][105]; + LL M = 1e9+7; +public: + int sumOfPower(vector& nums, int k) + { + int n = nums.size(); + nums.insert(nums.begin(), 0); + + dp[0][0][0] = 1; + + for (int i=1; i<=n; i++) + for (int s=0; s<=k; s++) + for (int j=0; j<=i; j++) + { + dp[i][s][j] = dp[i-1][s][j]; + if (s>=nums[i] && j>0) + dp[i][s][j] += dp[i-1][s-nums[i]][j-1]; + dp[i][s][j] %= M; + } + + vectorpower(10005); + power[0] = 1; + for (int i=1; i<=n; i++) + power[i] = power[i-1]*2%M; + + LL ret = 0; + for (int j=1; j<=n; j++) + { + LL t = dp[n][k][j]; + ret = (ret + t*power[n-j]%M) % M; + } + + return ret; + } +}; diff --git a/Dynamic_Programming/3082.Find-the-Sum-of-the-Power-of-All-Subsequences/Readme.md b/Dynamic_Programming/3082.Find-the-Sum-of-the-Power-of-All-Subsequences/Readme.md new file mode 100644 index 000000000..6f27c5469 --- /dev/null +++ b/Dynamic_Programming/3082.Find-the-Sum-of-the-Power-of-All-Subsequences/Readme.md @@ -0,0 +1,12 @@ +### 3082.Find-the-Sum-of-the-Power-of-All-Subsequences + +“子序列的子序列”思考起来比较费劲,但是如果只是求“和为k的子序列的个数”,这个看上去就是典型的DP。然后我们再思考一下,本题其实就是求每个“和为k的子序列”有多少个超序列(super sequence)。 + +举个列子,假设总元素个数是n。如果有一个子序列q的长度是m,它的和是k,那么nums里就有`(n-m)^2`个序列包含q,这些序列的都有这么一个子序列q满足条件,所以q本质上给最终答案贡献了`(n-m)^2`。 + +所以我们只需要求出所有不同长度的、和为k的子序列个数。这个只不过在前述DP的基础上,再增加一个变量/下标记录已经选取元素的个数。即令dp[i][s][j]表示在前i个元素里、选取j个元素、和为s的子序列有多少个。显然它的转移方程就取决于第i个元素是否选取: +```cpp +dp[i][s][j] += dp[i-1][s][j]; // no select nums[i] +dp[i][s][j] += dp[i-1][s-nums[i]][j-1], if (s>=nums[i] && j>=1); // select nums[i] +``` +最终考察完整个nums之后,我们遍历子序列的长度j,就可以知道存在有dp[n][k][j]个符合要求的子序列,并且其长度是j。那么它的超序列就有`2^(n-j)`个。 diff --git a/Dynamic_Programming/3098.Find-the-Sum-of-Subsequence-Powers/3098.Find-the-Sum-of-Subsequence-Powers.cpp b/Dynamic_Programming/3098.Find-the-Sum-of-Subsequence-Powers/3098.Find-the-Sum-of-Subsequence-Powers.cpp new file mode 100644 index 000000000..3360df7b3 --- /dev/null +++ b/Dynamic_Programming/3098.Find-the-Sum-of-Subsequence-Powers/3098.Find-the-Sum-of-Subsequence-Powers.cpp @@ -0,0 +1,55 @@ +using LL = long long; +class Solution { + LL M = 1e9+7; + int n; +public: + int sumOfPowers(vector& nums, int K) + { + n = nums.size(); + sort(nums.begin(), nums.end()); + nums.insert(nums.begin(), 0); + + LL ret = 0; + for (int i=1; i<=n; i++) + for (int j=i+1; j<=n; j++) + { + int d = nums[j]-nums[i]; + ret = (ret + helper(nums, K, d, i, j)) % M; + } + return ret; + } + + LL helper(vector& nums, int K, int d, int a, int b) + { + vector>dp1(n+2, vector(n+2)); + vector>dp2(n+2, vector(n+2)); + + for (int i=1; i<=n; i++) + { + dp1[i][1] = 1; + dp2[i][1] = 1; + } + + for (int i=1; i<=a; i++) + for (int j=2; j<=K; j++) + { + for (int k=1; nums[i]-nums[k]>d && k=b; i--) + for (int j=2; j<=K; j++) + { + for (int k=n; nums[k]-nums[i]>=d && k>i; k--) + dp2[i][j] = (dp2[i][j] + dp2[k][j-1]) % M; + } + + LL ret = 0; + for (int t=1; td`,就有`dp1[i][j] += dp1[k][j-1]`,将所有符合条件的j遍历一遍,就可以求出dp[i][j]. + +同理,我们从后往前进行DP,求出在[b,n]区间里有多少相邻元素之差大于d的子序列。可以求解dp2[i][j]表示以i开头的、长度为j、且相邻元素跨度大于等于d的子序列个数。 + +这样,我们只需要将期望长度K分配给[a,b]的前后两段,假设分别是t和K-t,就可以得到组合数`dp[a][t]*dp[b][K-t]`,对应的就是包含a和b的、符合条件的子序列的个数。我们对于所有t=1,2,...K-1,将组合数求和即可。 + +特别注意,dp1和dp2的定义略有不同,前者要求跨度大于d,后者要求跨度大于等于d。这是因为一个子序列里可能有多个最小跨度d,我们约定只认为第一个出现的最小跨度d是我们的枚举对象。所以在[1,a]区间内,我们不接受相邻元素跨度恰好为d的情况。 diff --git a/Dynamic_Programming/3122.Minimum-Number-of-Operations-to-Satisfy-Conditions/3122.Minimum-Number-of-Operations-to-Satisfy-Conditions.cpp b/Dynamic_Programming/3122.Minimum-Number-of-Operations-to-Satisfy-Conditions/3122.Minimum-Number-of-Operations-to-Satisfy-Conditions.cpp new file mode 100644 index 000000000..c3cff6617 --- /dev/null +++ b/Dynamic_Programming/3122.Minimum-Number-of-Operations-to-Satisfy-Conditions/3122.Minimum-Number-of-Operations-to-Satisfy-Conditions.cpp @@ -0,0 +1,36 @@ +class Solution { + int dp[1005][10]; +public: + int minimumOperations(vector>& grid) + { + int m = grid.size(), n = grid[0].size(); + + for (int i=0; i=0) dp0[i][j] += dp1[i-k][j]; + if (j-k>=0) dp1[i][j] += dp0[i][j-k]; + dp0[i][j] %= M; + dp1[i][j] %= M; + } + } + + return (dp0[zero][one]+dp1[zero][one]) % M; + } +}; diff --git a/Dynamic_Programming/3130.Find-All-Possible-Stable-Binary-Arrays-II/3130.Find-All-Possible-Stable-Binary-Arrays-II_v2.cpp b/Dynamic_Programming/3130.Find-All-Possible-Stable-Binary-Arrays-II/3130.Find-All-Possible-Stable-Binary-Arrays-II_v2.cpp new file mode 100644 index 000000000..3face3ad0 --- /dev/null +++ b/Dynamic_Programming/3130.Find-All-Possible-Stable-Binary-Arrays-II/3130.Find-All-Possible-Stable-Binary-Arrays-II_v2.cpp @@ -0,0 +1,40 @@ +using LL = long long; +class Solution { + LL dp0[205][205]; + LL dp1[205][205]; + LL presum0[205][205]; + LL presum1[205][205]; + LL M = 1e9+7; +public: + int numberOfStableArrays(int zero, int one, int limit) + { + dp0[0][0]=1; + dp1[0][0]=1; + presum0[0][0] = 1; + presum1[0][0] = 1; + + for (int i=0; i<=zero; i++) + for (int j=0; j<=one; j++) + { + if (i==0 && j==0) continue; + + // 1<=k<=min(i,limit) + dp0[i][j] = (i-1<0?0:presum1[j][i-1]) - (i-min(i,limit)-1<0?0:presum1[j][i-min(i,limit)-1]); + + + // 1<=k<=min(j,limit) + dp1[i][j] = (j-1<0?0:presum0[i][j-1]) - (j-min(j,limit)-1<0?0:presum0[i][j-min(j,limit)-1]); + + dp0[i][j] = (dp0[i][j] + M) %M; + dp1[i][j] = (dp1[i][j] + M) %M; + + presum0[i][j] = (j<1?0:presum0[i][j-1]) + dp0[i][j]; + presum1[j][i] = (i<1?0:presum1[j][i-1]) + dp1[i][j]; + + presum0[i][j] %= M; + presum1[j][i] %= M; + } + + return (dp0[zero][one]+dp1[zero][one]) % M; + } +}; diff --git a/Dynamic_Programming/3130.Find-All-Possible-Stable-Binary-Arrays-II/Readme.md b/Dynamic_Programming/3130.Find-All-Possible-Stable-Binary-Arrays-II/Readme.md new file mode 100644 index 000000000..69c9a9c37 --- /dev/null +++ b/Dynamic_Programming/3130.Find-All-Possible-Stable-Binary-Arrays-II/Readme.md @@ -0,0 +1,40 @@ +### 3130.Find-All-Possible-Stable-Binary-Arrays-II + +#### 解法1: +对于每一步决策而言,我们需要考虑的因素无非就是:已经用了几个0,已经用了几个1,当前最后一步是0还是1. 事实上,我们就用这些状态作为变量,即可定义动态规划。令dp0[i][j]表示已经用了i个0、j个1,并且最后一个数字填写的是0时,可以构造的stable binary array的个数。类似地,令dp1[i][j]表示已经用了i个0、j个1,并且最后一个数字填写的是1时,可以构造的stable binary array的个数。 + +如何计算dp0[i][j]呢?因为最后一步填0,且唯一的限制就是不能有连续超过limit+1个0,所以它之前最后一次出现的1,必须在`i+j-limit, i+j-limit+1, ..., i+j-1`中间的一处。所以就有 +``` +dp0[i][j] = dp1[i-limit][j] + dp1[i-limit+1][j] + ... + dp1[i-1][j] +``` +同理,dp1[i][j]的前趋状态取决于最后一次出现0的位置, +``` +dp1[i][j] = dp0[i][j-limit] + dp1[i][j-limit+1] + ... + dp1[i][j-1] +``` +综上,我们用三层循环就可以求出dp0和dp1。最终答案就是将所有的0和1用完,但结尾的元素可以是0或1,即`dp0[zero][one]+dp1[zero][one]`. +```cpp +for (int i=0; i<=zero; i++) + for (int j=0; j<=one; j++) + { + for (int k=1; k<=limit; k++) + { + if (i>=k) dp0[i][j] += dp1[i-k][j]; + if (j>=k) dp1[i][j] += dp0[i][j-k]; + } + } +``` + +#### 解法2: +注意到上述解法的最内层循环,其实dp0[i][j]是累加了dp1[...][j]的一段区间,区间范围是[i-min(i,limit), i-1]. 同理,dp1[i][j]是累加了dp0[i][...]的一段区间,区间范围是[j-min(j,limit), j-1]. 为了节省这层循环,我们想到可以用前缀和。 + +令`presum0[i][...]`表示`dp0[i][...]`的前缀和,`presum1[j][...]`表示`dp1[...][j]`的前缀和。于是区间之和就可以表示成前缀和之差: +``` +dp0[i][j] = presum1[j][i-1] - presum1[j][i-min(i,limit)-1] +dp1[i][j] = presum0[i][j-1] - presum0[i][j-min(j,limit)-1] +``` +用完之后,记得将新算出的dp0[i][j]和dp1[i][j]来更新presum0与presum1 +``` +presum0[i][j] = presum0[i][j-1] + dp0[i][j] +presum1[j][i] = presum1[j][i-1] + dp1[i][j] +``` +就这样在i与j的双层循环里,不断滚动更新dp0[i][j]、dp1[i][j]、presum0[i][j]与presum1[j][i]. diff --git a/Dynamic_Programming/3177.Find-the-Maximum-Length-of-a-Good-Subsequence-II/3177.Find-the-Maximum-Length-of-a-Good-Subsequence-II_v1.cpp b/Dynamic_Programming/3177.Find-the-Maximum-Length-of-a-Good-Subsequence-II/3177.Find-the-Maximum-Length-of-a-Good-Subsequence-II_v1.cpp new file mode 100644 index 000000000..62a19d259 --- /dev/null +++ b/Dynamic_Programming/3177.Find-the-Maximum-Length-of-a-Good-Subsequence-II/3177.Find-the-Maximum-Length-of-a-Good-Subsequence-II_v1.cpp @@ -0,0 +1,31 @@ +class Solution { + int dp[505][26]; +public: + int maximumLength(vector& nums, int k) + { + int n = nums.size(); + + int ret = 1; + + for (int i=0; i=1) + ans = max(ans, dp[j][t-1]+1); + } + + dp[i][t] = ans; + ret = max(ret, ans); + } + } + + return ret; + } +}; diff --git a/Dynamic_Programming/3177.Find-the-Maximum-Length-of-a-Good-Subsequence-II/3177.Find-the-Maximum-Length-of-a-Good-Subsequence-II_v2.cpp b/Dynamic_Programming/3177.Find-the-Maximum-Length-of-a-Good-Subsequence-II/3177.Find-the-Maximum-Length-of-a-Good-Subsequence-II_v2.cpp new file mode 100644 index 000000000..02fc5b52f --- /dev/null +++ b/Dynamic_Programming/3177.Find-the-Maximum-Length-of-a-Good-Subsequence-II/3177.Find-the-Maximum-Length-of-a-Good-Subsequence-II_v2.cpp @@ -0,0 +1,35 @@ +class Solution { + int dp[5005][55]; +public: + int maximumLength(vector& nums, int k) + { + int n = nums.size(); + vector>max_value(55); + vectormax_all(55); + + int ret = 1; + + for (int i=0; i=1) + ans = max(ans, max_all[t-1]+1); + + dp[i][t] = ans; + ret = max(ret, ans); + } + + for (int t=0; t<=k; t++) + { + max_value[t][nums[i]] = max(max_value[t][nums[i]], dp[i][t]); + max_all[t] = max(max_all[t], dp[i][t]); + } + } + + return ret; + } +}; diff --git a/Dynamic_Programming/3177.Find-the-Maximum-Length-of-a-Good-Subsequence-II/Readme.md b/Dynamic_Programming/3177.Find-the-Maximum-Length-of-a-Good-Subsequence-II/Readme.md new file mode 100644 index 000000000..6e055fbf6 --- /dev/null +++ b/Dynamic_Programming/3177.Find-the-Maximum-Length-of-a-Good-Subsequence-II/Readme.md @@ -0,0 +1,59 @@ +### 3177.Find-the-Maximum-Length-of-a-Good-Subsequence-II + +#### 解法1:For 3176 +对于常规的DP解法,我们容易设置状态变量dp[i][t]表示前i个元素里、我们已经出现了t次相邻元素不等的情况下,能够得到的goode subsequence的最大长度。 + +显然转移的突破口就在于nums[i]是否与sequence的前一个元素相同。我们枚举j=1) + ans = max(ans, dp[j][t-1]+1); + } + dp[i][t] = ans; + ret = max(ret, ans); + } + } +``` + +#### 解法2: +上述解法的时间复杂度是o(N^2*K)。如何优化呢?事实上我们可以对最内层的j循环优化。首先看else分支,它的本质是在dp[j][t]中取最大值。这个其实我们可以在之前计算dp的过程中顺便维护一下dp[j][t]的最大值即可。即`max_all[t] = max(dp[j][t]) j=0,1,2..i-1`. + +再看if分支,它的本质就是在dp[j][t-1]中、对于那些nums[j]==nums[i]的dp值取最大值。这其实也可以用一个hash,以nums[i]为key,来维护这个最大值。即`max_value[t][v] = max(dp[j][t][v]) j=0,1,2..i-1 and nums[j]==v`. + +有人会说,else分支是只有在nums[j]!=nums[i]时才跑的,所以`max_all[t]`的定义有缺陷,应该剔除掉一些元素。其实我们可以不在意。因为逻辑上一定有`dp[j][t]>dp[j][t-1]`,所以对于那些nums[j]==nums[i]的元素j,走if分支会比走else分支更合算。所以`max_all[t]`不剔除与nums[i]相同的那些dp值也没有关系。 + +于是解法1就可以变成如下。注意我们在t循环计算完dp[i][t]之后,再进行一次循环更新max_valuet[t][nums[i]和max_all[t]. +```cpp + for (int i=0; i& power) + { + mapMap; + for (int x: power) + Map[x]++; + + vector>spell(Map.begin(), Map.end()); + + vectordp(spell.size()); + + for (int i=0; i=1) dp[i] = max(dp[i], dp[i-1]); + + // pick the i-th spell along with previous ones + if (i>=1 && p - spell[i-1].first > 2) + dp[i] = max(dp[i], dp[i-1] + p * count); + else if (i>=2 && p - spell[i-2].first > 2) + dp[i] = max(dp[i], dp[i-2] + p * count); + else if (i>=3) + dp[i] = max(dp[i], dp[i-3] + p * count); + } + + return dp[spell.size()-1]; + } +}; diff --git a/Dynamic_Programming/3186.Maximum-Total-Damage-With-Spell-Casting/Readme.md b/Dynamic_Programming/3186.Maximum-Total-Damage-With-Spell-Casting/Readme.md new file mode 100644 index 000000000..dc3212c0b --- /dev/null +++ b/Dynamic_Programming/3186.Maximum-Total-Damage-With-Spell-Casting/Readme.md @@ -0,0 +1,9 @@ +### 3186.Maximum-Total-Damage-With-Spell-Casting + +这非常类似一个house robber的问题。我们将所有的spell按照power的distinct value按从小到大排序,定义dp[i]表示从前i件spell里选取所能构成的最大和,其中题意要求不能选取power值差距在2以内的spell。特别注意,dp[i]不一定要求必须取第i种spell。 + +当我们考察spell[i]的时候,我们可以不取第i种spell,这样的话就是dp[i]=dp[i-1]. + +如果取第i种spell,那么保底就是仅取第i种药水的收益。其次我们查看spell[i-1]是否与spell[i]的差值在2之外,如果是的话,那么dp[i]就可以在dp[i-1]的基础上加上所有属于spell[i]的power。如果不是的话,我们往前查看spell[i-2]与spell[i]的差值是否在2之外,如果是的话,那么dp[i]就可以在dp[i-2]的基础上加上所有属于spell[i]的power。如果再不是的话,那么dp[i]可以直接在dp[i-3]的基础上加上所有属于spell[i]的power,这是因为spell数值彼此不同,spell[i-3]和spell[i]的差值必然大于2. + +最终答案返回dp[n-1]即可。 diff --git a/Dynamic_Programming/3193.Count-the-Number-of-Inversions/3193.Count-the-Number-of-Inversions.cpp b/Dynamic_Programming/3193.Count-the-Number-of-Inversions/3193.Count-the-Number-of-Inversions.cpp new file mode 100644 index 000000000..c07e9f516 --- /dev/null +++ b/Dynamic_Programming/3193.Count-the-Number-of-Inversions/3193.Count-the-Number-of-Inversions.cpp @@ -0,0 +1,54 @@ +using LL = long long; +class Solution { + LL dp[305][405]; + LL M = 1e9+7; +public: + int numberOfPermutations(int n, vector>& requirements) + { + dp[0][0] = 1; + + mapMap; + for (auto req: requirements) + { + int end = req[0] + 1; + int cnt = req[1]; + Map[end] = cnt; + } + + int cur = 0; + for (int i=1; i<=n; i++) + { + if (Map.find(i)!=Map.end()) + cur = Map[i]; + + auto iter = Map.lower_bound(i); + LL limit = iter->second; + for (int j=cur; j<=limit; j++) + { + for (int k=0; k<=j; k++) + { + if (j-k<=i-1) + { + dp[i][j] += dp[i-1][k]; + dp[i][j] %= M; + } + } + } + + if (Map.upper_bound(i)==Map.end()) + { + LL ret = dp[i][cur]; + return ret * fact(n-i) % M; + } + } + return 0; + } + + LL fact(LL n) + { + LL ret = 1; + for (int i=1; i<=n; i++) + ret = ret * i % M; + return ret; + } +}; diff --git a/Dynamic_Programming/3193.Count-the-Number-of-Inversions/Readme.md b/Dynamic_Programming/3193.Count-the-Number-of-Inversions/Readme.md new file mode 100644 index 000000000..d0c4a09ff --- /dev/null +++ b/Dynamic_Programming/3193.Count-the-Number-of-Inversions/Readme.md @@ -0,0 +1,22 @@ +### 3193.Count-the-Number-of-Inversions + +对于permutation类型的DP题有着类似的套路。其核心就是,任意一个长度为n的permutation的前i个元素,可以一一对应于一个长度为i的permutation。所以对于长度为n的permutation做动态规划时,对于状态变量dp[i](长度为n的permutation的前i个元素组成的、符合条件的序列个数),都可以等效看做是长度为i的(即1-i组成的)、符合条件的permutation个数。 + +因为本题涉及逆序对的数目,并且题目数据量给出的逆序对数目不超过400,故我们可以将其作为状态变量的一个下标。即我们定义dp[i][j]表示1-i组成的permutation里、逆序对数目是j的排列的个数。 + +对于前i个元素组成的permutation,能允许有多少逆序对呢?这取决于requirements给出的约束。举个例子,如果requirement要求在第p位有a个逆序对,在第q位有b个逆序对,并且恰好有`p<=i<=q`(即p和q是i最贴近的两个约束点),那么对于dp[i][j]而言,j的取值就是`a<=j<=b`. + +如何求解dp[i][j]呢?我们需要寻找它与前驱状态dp[i-1][k]的关系。注意到相对于dp[i-1][],我们在permutation中引入了新元素i,如果将i放在排列的最后,那么它不会引入任何新的逆序对。如果将i放在排列的倒数第二个位置,那么会引入一个逆序对... 依次类推,如果前驱状态dp[i-1][k]已经有k个逆序对,那么相对于j而言,我们需要再引入`j-k`个逆序对。这能否实现呢?其实只需要满足在i-1的排列中至少有j-k个元素即可,即`j-k<=i-1`。故 +```cpp +for (int i=1; i<=n; i++) +{ + int a = ... , b = ... ; + for (int j=a; j<=b; j++) + for (int k=0; k<=j; k++) + { + if (j-k <= i-1) + dp[i][j] += dp[i-1][k]; + } +} +``` +注意到,当处理完requirement的最后一个约束位置i后,此后上限b就不存在了。此时意味还有`t = n-i`个元素没有加入排列。注意这t个元素不能加入前i个元素组成的排列里,否则会违反在i处的约束;但是这t个元素本身可以在排列之后任意混排,不影响之前的requirement。故最终的答案就是`dp[i][r]*t!`,其中r表示requirement在i处的逆序对数目要求。 diff --git a/Dynamic_Programming/3196.Maximize-Total-Cost-of-Alternating-Subarrays/3196.Maximize-Total-Cost-of-Alternating-Subarrays.cpp b/Dynamic_Programming/3196.Maximize-Total-Cost-of-Alternating-Subarrays/3196.Maximize-Total-Cost-of-Alternating-Subarrays.cpp new file mode 100644 index 000000000..7b7ac7b8d --- /dev/null +++ b/Dynamic_Programming/3196.Maximize-Total-Cost-of-Alternating-Subarrays/3196.Maximize-Total-Cost-of-Alternating-Subarrays.cpp @@ -0,0 +1,20 @@ +using LL = long long; +class Solution { + LL dp[100005][2]; +public: + long long maximumTotalCost(vector& nums) + { + int n = nums.size(); + + dp[0][1] = nums[0]; + dp[0][0] = LLONG_MIN/2; + + for (int i=1; i& nums, int k) + { + vectorlast(k, -1); + int ret = 0; + + int n = nums.size(); + for (int i=0; ifreq(26); + for (char c : s) { + freq[c-'a']++; + } + int max_freq = *max_element(freq.begin(), freq.end()); + + int ret = INT_MAX/2; + vectordiff(26); + + for (int target = 1; target <= max_freq; target++) + { + for (int i=0; i<26; i++) + diff[i] = freq[i] - target; + vector>dp(26, vector(2, INT_MAX/2)); + + int carry; + dp[0][0] = freq[0]; + dp[0][1] = abs(diff[0]); + + for (int i=1; i<26; i++) + { + dp[i][0] = min(dp[i-1][0], dp[i-1][1]) + freq[i]; + + dp[i][1] = min(dp[i-1][0], dp[i-1][1]) + abs(diff[i]); + + if (i>=1 && diff[i-1]>0 && diff[i]<0) + { + int common = min(abs(diff[i-1]), abs(diff[i])); + dp[i][1] = min(dp[i][1], dp[i-1][1] + abs(diff[i])-common); + } + + if (i>=1 && freq[i-1]>0 && diff[i]<0) + { + int common = min(abs(freq[i-1]), abs(diff[i])); + dp[i][1] = min(dp[i][1], dp[i-1][0] + abs(diff[i])-common); + } + } + + ret = min(ret, min(dp[25][0], dp[25][1])); + } + + return ret; + } +}; diff --git a/Dynamic_Programming/3389.Minimum-Operations-to-Make-Character-Frequencies-Equal/Readme.md b/Dynamic_Programming/3389.Minimum-Operations-to-Make-Character-Frequencies-Equal/Readme.md new file mode 100644 index 000000000..c645d91c0 --- /dev/null +++ b/Dynamic_Programming/3389.Minimum-Operations-to-Make-Character-Frequencies-Equal/Readme.md @@ -0,0 +1,20 @@ +### 3389.Minimum-Operations-to-Make-Character-Frequencies-Equal + +此题的突破口是对所有可能的频率进行尝试,暴力地从1枚举到max_freq(对应出现频次最多的字母),然后考察是否有一种方法能够将所有字符的频次都变换成target。 + +对于题目中的规则,我们最需要深刻体会的就是第三条。事实上,我们不会将某个字符连续变换两次。比如说,a->b->c,那这两次变换,还不如直接删除a,添加c来得直观。所以唯一使用规则三的情景就是:对于字母表相邻的两种字符x和y,如果x需要删除一些,y需要增加一些,那么我们不妨将部分的x转化为y,以节省操作。更具体的,如果x需要删除a个,y需要增加b个,普通的“删除+增加”的操作需要a+b次,但是如果将c=min(a,b)个x转化为y次,我们就额外节省了c次操作。 + +此题的复杂之处在于,即使我们确定了某个目标频次target,但规则同时也允许部分字符的频次变成零。对于这两个不同的“做法”,需要考虑的策略其实也是不同的。因此我们对于a->z的的每个字符,都要考虑到它的两种“做法”。 + +假设我们考虑相邻的两个字符i-1和字符i。令`diff[i]=freq[i]-target`,正则表示相比于target多了,负表示相比于target还亏欠。定义dp[i][0]表示对字符i采取清零操作的总最小代价;dp[i][1]表示对字符i变换成target频次的总最小代价。 + +1. 如果对字符i采取清零,即dp[i][0],那么所需要的操作数必然是freq[i],与之前的状态无关。故`dp[i][0] = min(dp[i-1][0], dp[i-1][1]) + freq[i]`; + +2. 如果对字符i变换成目标targt,那么我们分两种情况: + * 不采用规则3,只靠增删,那么同理有`dp[i][1] = min(dp[i-1][0], dp[i-1][1]) + abs(diff[i])` + * 采用规则3,同时前一个字符的操作是通过删减达到清零,这就意味着a=freq[i-1],b=abs(diff[i]),故c=min(a,b),且有`dp[i][1] = dp[i-1][0]+abs(diff[i])-c` + * 采用规则3,同时前一个字符的操作是通过删减达到target,这就意味着a=diff[i-1],b=abs(diff[i]),故c=min(a,b),且有`dp[i][1] = dp[i-1][0]+abs(diff[i])-c` + +最终对于target而言,最优解就是遍历完26个字母后的`min(dp[25][0],dp[25][1])` + +全局最取遍历所有target之后的最优解。 diff --git a/Dynamic_Programming/3444.Minimum-Increments-for-Target-Multiples-in-an-Array/3444.Minimum-Increments-for-Target-Multiples-in-an-Array.cpp b/Dynamic_Programming/3444.Minimum-Increments-for-Target-Multiples-in-an-Array/3444.Minimum-Increments-for-Target-Multiples-in-an-Array.cpp new file mode 100644 index 000000000..86b5d5a9f --- /dev/null +++ b/Dynamic_Programming/3444.Minimum-Increments-for-Target-Multiples-in-an-Array/3444.Minimum-Increments-for-Target-Multiples-in-an-Array.cpp @@ -0,0 +1,33 @@ +class Solution { + int dp[50005][1<<4]; +public: + int minimumIncrements(vector& nums, vector& target) + { + int n = nums.size(); + int m = target.size(); + nums.insert(nums.begin(), 0); + + for (int state = 0; state<(1<0; subset=(subset-1)&state) + { + long long L = 1; + for (int j=0; j>j)&1) + L = lcm(L, target[j]); + } + long long cost = (nums[i] % L == 0) ? 0 : (L - nums[i]%L); + dp[i][state] = min((long long)dp[i][state], (long long)dp[i-1][state-subset] + cost); + } + } + + return dp[n][(1<& nums) + { + for (int i=0; i& nums, vector>& queries) + { + for (int i=0; i=0; v--) + if (v>=d && dp[i][v-d] == true) + dp[i][v] = true; + } + if (isOK(nums)) return k+1; + } + return -1; + } +}; diff --git a/Dynamic_Programming/3489.Zero-Array-Transformation-IV/Readme.md b/Dynamic_Programming/3489.Zero-Array-Transformation-IV/Readme.md new file mode 100644 index 000000000..476ca9320 --- /dev/null +++ b/Dynamic_Programming/3489.Zero-Array-Transformation-IV/Readme.md @@ -0,0 +1,17 @@ +### 3489.Zero-Array-Transformation-IV + +这本质是一个背包问题。每处理一个query,在对应区间内的nums[i]就多得了一次删减的操作。 + +我们需要查看这些nums[i]在获得这个额外的删减机会之后,是否能连同之前的删减操作,实现置零?很显然,如果该query能够让nums[i]再削减d,那么就取决于nums[i]之前能否削减至d。 + +我们令dp[i][v]表示如果nums[i]的数值是v,能否最终削减成为零。就有 +``` +for q: queries + a = q[0], b = q[1], d = q[2]; + for (i=a; i=b; i++) { + for (int v=0; v<=1000; v++) { + dp[i][v] = dp[i][v] || d[i][v-d]; + } + } +``` +最终查看所有的dp[i][nums[i]]是否为true。 diff --git a/Dynamic_Programming/3538.Merge-Operations-for-Minimum-Travel-Time/3538.Merge-Operations-for-Minimum-Travel-Time.cpp b/Dynamic_Programming/3538.Merge-Operations-for-Minimum-Travel-Time/3538.Merge-Operations-for-Minimum-Travel-Time.cpp new file mode 100644 index 000000000..45d1a4ca3 --- /dev/null +++ b/Dynamic_Programming/3538.Merge-Operations-for-Minimum-Travel-Time/3538.Merge-Operations-for-Minimum-Travel-Time.cpp @@ -0,0 +1,30 @@ +const int INF = INT_MAX / 2; +int dp[51][11][101]; + +class Solution { +public: + int minTravelTime(int l, int n, int K, vector& pos, vector& time) { + + fill(&dp[0][0][0], &dp[0][0][0]+51*11*101, INT_MAX/2); + + dp[0][0][time[0]] = 0; + + for (int i=0; idp(n, INT_MAX/2); + + for (int j=0; j findCoins(vector& numWays) { + int n = numWays.size(); + numWays.insert(numWays.begin(), 0); + + vectorrets; + vectordp(n+1); + dp[0] = 1; + for (int c=1; c<=n; c++) { + if (numWays[c] == dp[c]) + continue; + rets.push_back(c); + for (int i=c; i<=n; i++) { + dp[i] += dp[i-c]; + } + } + + for (int i=1; i<=n; i++) { + if (dp[i]!=numWays[i]) + return {}; + } + + return rets; + } +}; diff --git a/Dynamic_Programming/3592.Inverse-Coin-Change/Readme.md b/Dynamic_Programming/3592.Inverse-Coin-Change/Readme.md new file mode 100644 index 000000000..46bbd2cf0 --- /dev/null +++ b/Dynamic_Programming/3592.Inverse-Coin-Change/Readme.md @@ -0,0 +1,26 @@ +### 3592.Inverse-Coin-Change + +这是一道非常有意思的“反向重构”的DP题。 + +首先,从题境上来看这类似一道经典的背包问题。给出一系列硬币面值coins可以无限重复使用,问有多少种构造方法能拼出面值n来?对于这个问题,我们有如下动态规划解法。特别注意最外层的循环是硬币面额 +``` +for (int c: coins) + for (int i=c; i<=n; i++) + dp[i] += dp[i-c]; +``` +状态转移的思想就是,每轮引入一种新的硬币面额c:遍历所有面值i,考察最后一个硬币是否使用c。如果不使用,那么dp[i]依然是前一轮的数值;如果使用c,那么就取决于dp[i-c]。 + +我们可以沿用同样的思路,来思考numWays(也就是dp)是怎么计算出来的 +``` +for (int c: coins) { + if c 不存在 + continue; + else if c 存在 + for (int i=c; i<=n; i++) + dp[i] += dp[i-c]; +} +``` + +那么如何判断c是否存在呢?突破点就是面额c的构造方法。如果numWays[c]等于前一轮(还没有引入面额c的时候)的dp[c],那么就意味着面额c一定不存在。否则c的构造必然至少还能增加一种方法(即只使用一个c硬币)。反之,面额c必须存在,否则无法弥补这个差额。 + +当然,我们仅凭numWays[c]的分析来决定面额的种类还是比较片面的,它不能保证最终据此计算得到的dp都与所有的numsWays一致。所以我们还要再次校验一下。 diff --git a/Dynamic_Programming/3654.Minimum-Sum-After-Divisible-Sum-Deletions/3654.Minimum-Sum-After-Divisible-Sum-Deletions.cpp b/Dynamic_Programming/3654.Minimum-Sum-After-Divisible-Sum-Deletions/3654.Minimum-Sum-After-Divisible-Sum-Deletions.cpp new file mode 100644 index 000000000..1a4362f44 --- /dev/null +++ b/Dynamic_Programming/3654.Minimum-Sum-After-Divisible-Sum-Deletions/3654.Minimum-Sum-After-Divisible-Sum-Deletions.cpp @@ -0,0 +1,23 @@ +using ll = long long; +class Solution { +public: + long long minArraySum(vector& nums, int k) { + int n = nums.size(); + nums.insert(nums.begin(), 0); + + vectordp(n+1,LLONG_MAX/4); + vectordp_by_r(k,LLONG_MAX/4); + dp[0] = 0; + dp_by_r[0] = 0; + + ll presum = 0; + for (int i=1; i<=n; i++) { + presum += nums[i]; + int r = presum % k; + dp[i] = min(dp[i-1] + nums[i], dp_by_r[r]); + dp_by_r[r] = min(dp_by_r[r], dp[i]); + } + + return dp[n]; + } +}; diff --git a/Dynamic_Programming/3654.Minimum-Sum-After-Divisible-Sum-Deletions/Readme.md b/Dynamic_Programming/3654.Minimum-Sum-After-Divisible-Sum-Deletions/Readme.md new file mode 100644 index 000000000..c743eef2f --- /dev/null +++ b/Dynamic_Programming/3654.Minimum-Sum-After-Divisible-Sum-Deletions/Readme.md @@ -0,0 +1,9 @@ +### 3654.Minimum-Sum-After-Divisible-Sum-Deletions + +首先,题意中“after each deletion, the remaining elements close the gap”这个表述其实带有迷惑性。我们其实不需要这样的操作。假设有a,b,c,d四个位置,我们先删除[b,c],剩余合并之后再删除[a,d],那么必然等价于可以直接删除[a,d]。因为[b:c]能被k整除,且[a:b)+(c:d]能被k整除,则[a:d]一定也能被k整除。因此原题可以转化为,在nums里删除若干段互不相交的、能被k整除的区间,使得剩余的元素之和最小。 + +由此本题就具有了典型的“无后效性”。我们可以考虑动态规划。令dp[i]表示只处理前i个元素能够得到最优解,突破口就在于nums[i]的处理。 +1. 如果不删除nums[i],那么直接有`dp[i] = dp[i-1]+nums[i]`。 +2. 如果删除nums[i],那么我们需要找到一个位置j,使得sum[j+1:i]能被k整除,则有转移方程`dp[i]=dp[j]`。满足条件“sum[j+1:i]能被k整除”的j可能有多处,它们明显有一个共同的特征,就是前缀和presum[j]必须与presum[i]关于k同余。于是我们可以将i之前的所有dp值按照`presum[j]%k`的余数分类,每种余数只记录最小的dp值,记作dp_by_r。假设`presum[i]%k==r`,那么我们就可以直接得到`dp_by_r[r]`,就是dp[i]的前驱状态。 + +最终返回dp[n]即可。 diff --git a/Dynamic_Programming/3661.Maximum-Walls-Destroyed-by-Robots/3661.Maximum-Walls-Destroyed-by-Robots.cpp b/Dynamic_Programming/3661.Maximum-Walls-Destroyed-by-Robots/3661.Maximum-Walls-Destroyed-by-Robots.cpp new file mode 100644 index 000000000..ded1f1cbf --- /dev/null +++ b/Dynamic_Programming/3661.Maximum-Walls-Destroyed-by-Robots/3661.Maximum-Walls-Destroyed-by-Robots.cpp @@ -0,0 +1,39 @@ +class Solution { + int dp[100005][2]; +public: + int maxWalls(vector& robots, vector& distance, vector& walls) { + int n = robots.size(); + vector>r; + for (int i=0; i>& grid) { + int M = 1e9+7; + int m = grid.size(), n = grid[0].size(); + for (int i=0; i=1 && j>=0) { + if (grid[i-1][j]==0) + dp[i][j][0] += dp[i-1][j][0]+dp[i-1][j][1]; + else + dp[i][j][0] += dp[i-1][j][1]; + } + if (i>=0 && j-1>=0) { + if (grid[i][j-1]==0) + dp[i][j][1] += dp[i][j-1][0]+dp[i][j-1][1]; + else + dp[i][j][1] += dp[i][j-1][0]; + } + dp[i][j][0]%=M; + dp[i][j][1]%=M; + } + return (dp[m-1][n-1][0]+dp[m-1][n-1][1])%M; + } +}; diff --git a/Dynamic_Programming/3665.Twisted-Mirror-Path-Count/Readme.md b/Dynamic_Programming/3665.Twisted-Mirror-Path-Count/Readme.md new file mode 100644 index 000000000..4769aca17 --- /dev/null +++ b/Dynamic_Programming/3665.Twisted-Mirror-Path-Count/Readme.md @@ -0,0 +1,11 @@ +### 3665.Twisted-Mirror-Path-Count + +考虑到对于镜子而言,左边入只能下边出,上边入只能右边出,所以我们需要在设计dp状态时考虑入射方向。令dp[i][j][d]表示以d方向进入(i,j)时的路径数量。令d=0表示向下,d=1表示向右。 + +考虑从(i-1,j)往下进入(i,j)。如果(i-1,j)是普通的格子,那么就有`dp[i][j][0]+=dp[i-1][j][0]+dp[i-1][j][1]`.如果(i-1,j)是镜子,那么只能是向右进入镜子的路径才能往下进入(i,j),即`dp[i][j][0]+=dp[i-1][j][1]`。 + +同理考虑从(i,j-1)往右进入(i,j),更新`dp[i][j][1]`. + +最终输出`dp[m-1][n-1][0]+`dp[m-1][n-1][1]`. + +注意初始条件(0,0),可以任意设置`dp[0][0][0]=1`或者`dp[0][0][1]=1`。 diff --git a/Dynamic_Programming/3685.Subsequence-Sum-After-Capping-Elements/3685.Subsequence-Sum-After-Capping-Elements.cpp b/Dynamic_Programming/3685.Subsequence-Sum-After-Capping-Elements/3685.Subsequence-Sum-After-Capping-Elements.cpp new file mode 100644 index 000000000..3633e575c --- /dev/null +++ b/Dynamic_Programming/3685.Subsequence-Sum-After-Capping-Elements/3685.Subsequence-Sum-After-Capping-Elements.cpp @@ -0,0 +1,29 @@ +class Solution { +public: + vector subsequenceSumAfterCapping(vector& nums, int k) { + int n = nums.size(); + vectordp(k+1,0); + dp[0] = 1; + vectorrets(n, 0); + + sort(nums.begin(), nums.end()); + int i = 0; + for (int x=1; x<=n; x++) { + while (i=1; c--) { + if (c=1; c--) { + dp[c] |= dp[c-nums[i]; + } +} +``` + +对于此题而言,对于每一个给定的x,我们要用nums里所有小于x的元素,以及剩余的元素当做x使用,问是否能组合出和为k的方案。我们暂时先不考虑第二种用法,即只用nums里小于x的元素。我们发现,随着x的增大,其实第一个for循环里可选的元素种类的上界也在单调增大,故总体这依然是一个o(n*k)可解的问题。 + +然后更深入地思考,我们将x从小到大遍历的时候,可以不断提升i,从而加入所有不超过x的新nums[i],就可以不断更新dp。同时我们还需要考虑等于x的元素:因为capping的缘故,这样的元素有n-i个。此时我们需要考虑这额外的n-i个元素能否对于组成k有帮助。显然我们可以用同样的背包思想: +```cpp +for (int j=1; jup(m+1,1); + vectordown(m+1,1); + + for (int pos = 2; pos<=n; pos++) { + vectornew_up(m+1, 0), new_down(m+1, 0); + int presum = 0; + for (int x=1; x<=m; x++) { + new_up[x] = presum; + presum = (presum + down[x])% MOD; + } + presum = 0; + for (int x=m; x>=0; x--) { + new_down[x] = presum; + presum = (presum + up[x])% MOD; + } + + up = move(new_up); + down = move(new_down); + } + + long long ans = 0; + for (int x=1; x<=m; x++) { + ans += up[x]; + ans += down[x]; + ans%=MOD; + } + return ans; + } +}; diff --git a/Dynamic_Programming/3699.Number-of-ZigZag-Arrays-I/Readme.md b/Dynamic_Programming/3699.Number-of-ZigZag-Arrays-I/Readme.md new file mode 100644 index 000000000..d5639ec57 --- /dev/null +++ b/Dynamic_Programming/3699.Number-of-ZigZag-Arrays-I/Readme.md @@ -0,0 +1,15 @@ +### 3699.Number-of-ZigZag-Arrays-I + +考虑到数据量,不难想到用基础的DP来解决。令up[i][x]表示第i个元素为x、且最后一段是上升的合法序列的数列。同理可以定义down[i][x]。其中x的范围是[1,m],且`m=r-l+1`; + +对于up[i][x]的状态转移,很明显取决于序列里的前一个元素。根据题意,以第i-1个元素结尾的序列的最后一段必须是下降的,故第i-1个元素必须是大于x的。所有有 +```cpp +up[i][x] = sum(down[i-1][y]) for y=x+1,...,m +``` +同理有 +```cpp +down[i][x] = sum(up[i-1][y]) for y=1,2,...,x-1 +``` +最终返回`sum(up[i][x]+down[i][x]) for x=1,2,...,m`. + +本题的时间复杂度只取决于最外的两层循环N*M. 当遍历x时,我们可以记录donw[i][y]的前缀和,就可以高效地计算up[i][x]. diff --git a/Dynamic_Programming/3801.Minimum-Cost-to-Merge-Sorted-Lists/3801.Minimum-Cost-to-Merge-Sorted-Lists.cpp b/Dynamic_Programming/3801.Minimum-Cost-to-Merge-Sorted-Lists/3801.Minimum-Cost-to-Merge-Sorted-Lists.cpp new file mode 100644 index 000000000..65dcec1b9 --- /dev/null +++ b/Dynamic_Programming/3801.Minimum-Cost-to-Merge-Sorted-Lists/3801.Minimum-Cost-to-Merge-Sorted-Lists.cpp @@ -0,0 +1,62 @@ +using ll = long long; +class Solution { +public: + int countLessOrEqual(vector>& lists, int mask, int val) { + int n = lists.size(); + int count = 0; + for (int i=0; i>i)&1)==0) continue; + auto iter = upper_bound(lists[i].begin(), lists[i].end(), val); + count += iter - lists[i].begin(); + } + return count; + } + + int find_median(vector>& lists, int mask, int c) { + int low = -INT_MAX/2, high = INT_MAX/2; + while (low < high) { + int mid = low+(high-low)/2; + if (countLessOrEqual(lists, mask, mid)>& lists) { + int n = lists.size(); + vectorlen(1<med(1<>i)&1)==0) continue; + m += lists[i].size(); + } + med[mask] = find_median(lists, mask, (m+1)/2); + len[mask] = m; + } + + + vectordp(1<0; subset=(subset-1)&mask) + { + int other = mask^subset; + if (other==0) continue; + if (subset > other) continue; + ll cost = dp[subset] + dp[other] + len[subset] + len[other] + llabs(med[subset]-med[other]); + dp[mask] = min(dp[mask], cost); + } + + } + + return dp[(1<&cost, int l, int r, int k) { + if (k==0) return 0; + int m = r-l+1; + if (m<=0) return LLONG_MAX/2; + + vector>dp(m+1, vector(k+1, LLONG_MAX/2)); + + dp[0][0] = 0; + + for (int i=1; i<=m; i++) { + int idx = l+i-1; + for (int j=0; j<=k;j++) { + dp[i][j] = dp[i-1][j]; + if (i>=2 && j>=1) + dp[i][j] = min(dp[i][j], dp[i-2][j-1] + cost[idx]); + if (j==1) + dp[i][j] = min(dp[i][j], cost[idx]); + } + } + return dp[m][k]; + } + + int minOperations(vector& nums, int k) { + int n = nums.size(); + if (k==0) return 0; + if (k>n/2) return -1; + + vectorcost(n); + for (int i=0; i>& items, int budget) { + int n = items.size(); + vectorgain(n, 1); + for (int i=0; idp(budget+1, -1); + dp[0] = 0; + for (int i=0; i=0; b--) { + if (b>=items[i][1] && dp[b-items[i][1]]!=-1) + dp[b] = max(dp[b], dp[b-items[i][1]]+gain[i]); + } + + vectorp; + for (auto x: items) p.push_back(x[1]); + int minPrice = *min_element(p.begin(), p.end()); + + int ret = 0; + for (int b=0; b<=budget; b++) { + int rem = budget - b; + ret = max(ret, dp[b]+rem/minPrice); + } + return ret; + } +}; diff --git a/Dynamic_Programming/3946.Maximum-Number-of-Items-From-Sale-I/Readme.md b/Dynamic_Programming/3946.Maximum-Number-of-Items-From-Sale-I/Readme.md new file mode 100644 index 000000000..b2b5ee2fd --- /dev/null +++ b/Dynamic_Programming/3946.Maximum-Number-of-Items-From-Sale-I/Readme.md @@ -0,0 +1,11 @@ +### 3946.Maximum-Number-of-Items-From-Sale-I + +本题的意思是说,有n件物品。对于物品i而言,如果单买一件的话,会有gain[i]的收益,但是如果买更多的话,只有1的收益。求总体budget限制下的最大收益。 + +此题的精彩之处在于,其本质是01背包问题。 + +我们可以将budget分为两部分,`b`和`budget-b`。前者意味着用b的代价,在这n件物品里,完全按照01背包问题进行选择。剩下部分,无论购买什么,每件只会带来1的收益,显然此时必然贪心选择单价最小的物品即可。 + +对于第一部分,经典的01背包问题最终给出的dp[b]就是表示在付出代价为`b`时01背包的最大收益,此时剩余`budget-b`直接除以minPrice就是可以再增加的最大收益。 + +最终我们取全局最大的`dp[b] + (budget-b)/minPrice`. diff --git a/Dynamic_Programming/518.Coin-Change-2/518.Coin-Change-2_v1.cpp b/Dynamic_Programming/518.Coin-Change-2/518.Coin-Change-2_v1.cpp index 3f3465781..043f12224 100644 --- a/Dynamic_Programming/518.Coin-Change-2/518.Coin-Change-2_v1.cpp +++ b/Dynamic_Programming/518.Coin-Change-2/518.Coin-Change-2_v1.cpp @@ -2,7 +2,7 @@ class Solution { public: int change(int amount, vector& coins) { - vectordp(amount+1,0); + vectordp(amount+1,0); dp[0] = 1; for (int i=0; i& coins) { - vectordp(amount+1,0); + vectordp(amount+1,0); dp[0] = 1; for (int i=0; iN) continue; if (grid[i-1][j-1]==-1||grid[x-1][y-1]==-1) continue; - if (i==1&&j==1&&x==1) - { - dp[i][j][x] = grid[0][0]; - continue; - } + if (i==1&&j==1&&x==1) continue; dp[i][j][x] = max(dp[i][j][x], dp[i-1][j][x-1]); dp[i][j][x] = max(dp[i][j][x], dp[i][j-1][x-1]); diff --git a/Dynamic_Programming/940.Distinct-Subsequences-II/940.Distinct-Subsequences-II.cpp b/Dynamic_Programming/940.Distinct-Subsequences-II/940.Distinct-Subsequences-II_v1.cpp similarity index 100% rename from Dynamic_Programming/940.Distinct-Subsequences-II/940.Distinct-Subsequences-II.cpp rename to Dynamic_Programming/940.Distinct-Subsequences-II/940.Distinct-Subsequences-II_v1.cpp diff --git a/Dynamic_Programming/940.Distinct-Subsequences-II/940.Distinct-Subsequences-II_v2.cpp b/Dynamic_Programming/940.Distinct-Subsequences-II/940.Distinct-Subsequences-II_v2.cpp new file mode 100644 index 000000000..6e4252b56 --- /dev/null +++ b/Dynamic_Programming/940.Distinct-Subsequences-II/940.Distinct-Subsequences-II_v2.cpp @@ -0,0 +1,22 @@ +using LL = long long; +class Solution { + LL dp[26]; + LL M = 1e9+7; +public: + int distinctSubseqII(string s) + { + for (char ch: s) + { + LL sum = 0; + for (int i=0; i<26; i++) + sum = (sum + dp[i]) % M; + + dp[ch-'a'] = sum + 1; + } + + LL ret = 0; + for (int i=0; i<26; i++) + ret = (ret + dp[i]) % M; + return ret; + } +}; diff --git a/Dynamic_Programming/940.Distinct-Subsequences-II/Readme.md b/Dynamic_Programming/940.Distinct-Subsequences-II/Readme.md index 71a15ab6f..d9e0d5f8a 100644 --- a/Dynamic_Programming/940.Distinct-Subsequences-II/Readme.md +++ b/Dynamic_Programming/940.Distinct-Subsequences-II/Readme.md @@ -1,5 +1,7 @@ ### 940.Distinct-Subsequences-II +#### 解法1:(deprecated) + 此题是字符串序列的一道经典题。如果第一次做的话,可能会觉得有难度。 尝试构造状态dp[i],表示截止第i个字符为止,我们能够创建的distinct子序列有多少.对于这个dp[i]的定义,我们并不要求s[i]必须是子序列的一部分。 @@ -34,7 +36,7 @@ XXa (8) 最终的输出是dp[n]。但是这个数字包含了“空字串”,所以答案需要再减去1. -#### 补充 +##### 补充 有一个听众问我,为什么去重的操作里,只需要减去dp[j-1](j是上一个满足S[j]==S[i]的字符的index),而不减去其他的dp[k-1](k是在j更早之前的某些字符,也满足S[k]==S[i])。这个问题很深刻。 我举个例子:...XXX... (a1) ...YYY... (a2) ...ZZZ... (a3), 其中a1,a2,a3都代表相同的字符a,他们对应的index分别是k,j,i. XXX/YYY/ZZZ表示在各自区间内取的某个subsequence. @@ -51,5 +53,22 @@ XXX(a3) ``` 这是因为```XXX(a1)```本质是和```XXXYYY(a2)```重合的!就最终的subsequence的样子而言,前者就是后者的一部分。我们计算dp[i]时,减去的dp[j-1],去掉了形如```XXXYYY(a2)```的重复,其实也就已经去掉了形如```XXX(a1)```的重复。所以我们不需要考虑其他在j之前的任何S[k]==S[i]的case。 +#### 解法2:(preferred) +此题和`1987.Number-of-Unique-Good-Subsequences`几乎一样。 + +我们令dp[c]表示截止到目前,以字母c结尾的unique subsequence的数目。 + +核心的代码是: +```cpp + for (int i=0; i>& edges, int distanceThreshold) + { + int dp[n][n]; + for (int i=0; i& coins, vector>& edges) + { + int n = coins.size(); + vector>next(n); + + vectordegree(n); + for (auto& edge: edges) + { + int a = edge[0], b = edge[1]; + next[a].insert(b); + next[b].insert(a); + degree[a]++; + degree[b]++; + } + + vectordeleted(n); + queueq; + for (int i=0; idepth(n, -1); + for (int i=0; i=3); + + if (ret >= 1) + return (ret-1)*2; + else + return 0; + } +}; diff --git a/Graph/2603.Collect-Coins-in-a-Tree/Readme.md b/Graph/2603.Collect-Coins-in-a-Tree/Readme.md new file mode 100644 index 000000000..94a316e9a --- /dev/null +++ b/Graph/2603.Collect-Coins-in-a-Tree/Readme.md @@ -0,0 +1,9 @@ +### 2603.Collect-Coins-in-a-Tree + +首先,对于那些处于端点位置的非coin节点、及全部由非coin节点组成支链,我们注定是不会去理会的。所以我们可以第一步进行“剪枝”,用拓扑排序的方法,从度为1的非coin节点开始,一层一层往内圈剥洋葱,将这些多余的分支砍去。剩下的图形,叶子节点必然都是coin;当然也可能存在一些非coin的节点,但它们都位于去往其他coin节点的必经之路上,我们也必须去理会。 + +接下来考虑考虑题目中说,Collect all the coins that are at a distance of at most 2 from the current vertex. 这就意味着我们不必走到每个端点去收集coin,只要走到端点之前两步的位置就可以收集。所以我们可以进一步将这些不用到达的节点都砍去。这里我们同样可以用拓扑排序的方法,从度为1的节点开始,一层一层往内剥洋葱,从小到大来标记每个节点的深度。这里的深度的定义就是,从该点到它的所有的子孙节点里的最大距离。举个例子,假设A->B,A->C->D->E,其中B和E都是端点,那么A的深度就是4. + +通过拓扑排序标记了所有节点从外圈到内圈的深度之后,我们发现,深度大于等于3的节点是我们必须访问的。而深度小于3的节点我们不需要访问,只需要走到深度等于3的节点就能收集到端点处的coin(如果有的话)。假设深度大于等于3的节点的个数有m个,因为这m个点必然是联通的,所以对应有m-1条边。我们注意到,起点和终点必须在同一处,这就意味着无论如何每条边我们必须走两次(一来一回),所以最终的答案就是`2(m-1)`,起点选在这m个节点的任意一个都可。 + +特别注意,如果m等于0,直接返回0. diff --git a/Graph/2608.Shortest-Cycle-in-a-Graph/2608.Shortest-Cycle-in-a-Graph.cpp b/Graph/2608.Shortest-Cycle-in-a-Graph/2608.Shortest-Cycle-in-a-Graph.cpp new file mode 100644 index 000000000..4bffafa3c --- /dev/null +++ b/Graph/2608.Shortest-Cycle-in-a-Graph/2608.Shortest-Cycle-in-a-Graph.cpp @@ -0,0 +1,57 @@ +class Solution { + unordered_setnext[1005]; + int n; +public: + int findShortestCycle(int n, vector>& edges) + { + this->n = n; + for (auto&edge: edges) + { + int a = edge[0], b = edge[1]; + next[a].insert(b); + next[b].insert(a); + } + + int ret = INT_MAX; + for (auto&edge: edges) + { + int a = edge[0], b = edge[1]; + next[a].erase(b); + next[b].erase(a); + ret = min(ret, BFS(a,b)); + next[a].insert(b); + next[b].insert(a); + } + + if (ret==INT_MAX) return -1; + return ret+1; + } + + int BFS(int start, int end) + { + vectorvisited(n); + queueq; + q.push(start); + visited[start] = 1; + + int step = 0; + while (!q.empty()) + { + int len = q.size(); + while (len--) + { + int cur = q.front(); + q.pop(); + if (cur==end) return step; + for (int nxt: next[cur]) + { + if (visited[nxt]) continue; + q.push(nxt); + visited[nxt] = 1; + } + } + step++; + } + return INT_MAX; + } +}; diff --git a/Graph/2608.Shortest-Cycle-in-a-Graph/Readme.md b/Graph/2608.Shortest-Cycle-in-a-Graph/Readme.md new file mode 100644 index 000000000..5ed018960 --- /dev/null +++ b/Graph/2608.Shortest-Cycle-in-a-Graph/Readme.md @@ -0,0 +1,3 @@ +### 2608.Shortest-Cycle-in-a-Graph + +这是图论里的经典问题。解法非常简单,就是遍历所有的边`a-b`。查看如果将该边断开,从a到b的最短距离d,那么d+1就是就包含d的最短环。然后取全局的最小值即可。 diff --git a/Graph/2642.Design-Graph-With-Shortest-Path-Calculator/2642.Design-Graph-With-Shortest-Path-Calculator.cpp b/Graph/2642.Design-Graph-With-Shortest-Path-Calculator/2642.Design-Graph-With-Shortest-Path-Calculator.cpp new file mode 100644 index 000000000..04cfb1a13 --- /dev/null +++ b/Graph/2642.Design-Graph-With-Shortest-Path-Calculator/2642.Design-Graph-With-Shortest-Path-Calculator.cpp @@ -0,0 +1,44 @@ +class Graph { + int n; + int dp[100][100]; +public: + Graph(int n, vector>& edges) { + this->n = n; + for (int i=0; i edge) + { + int a = edge[0], b = edge[1]; + for (int i=0; iaddEdge(edge); + * int param_2 = obj->shortestPath(node1,node2); + */ diff --git a/Graph/2642.Design-Graph-With-Shortest-Path-Calculator/Readme.md b/Graph/2642.Design-Graph-With-Shortest-Path-Calculator/Readme.md new file mode 100644 index 000000000..a82e45572 --- /dev/null +++ b/Graph/2642.Design-Graph-With-Shortest-Path-Calculator/Readme.md @@ -0,0 +1,17 @@ +### 2642.Design-Graph-With-Shortest-Path-Calculator + +根据题意,我们要时刻准备返回任意两点之间的最短路径,因此Dijkstra算法是不行的。除此之外,想求任意两点之间的最短路径,最经典的算法就是Floyd算法了,而o(N^3)的时间复杂度也是可以接受的。所以我们用Floyd预处理这个图,代码非常优雅 +```cpp + for (int k = 0; k < n; k++) { + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + dp[i][j] = Math.min(dp[i][j], dp[i][k] + dp[k][j]); + } + } + } +``` +特别注意k必须放在最外层。从形式上看,本质上这就是一个动态规划。 + +当我们新增一条从a->b的edge时,会对已有网络的最短路径产生什么影响呢?很显然,dp[i][j]无非就两种情况:经过edge,不经过edge。对于前者,我们只需要考察`dp[i][a]+edge+dp[b][j]`;对于后者,依然还是`dp[i][j]`。两者取小,就是更新后的dp[i][j].所以我们能用N^2的时间更新所有的`dp[i][j]`,这也是符合数据量的。 + +综上,我们可以实时输出`dp[i][j]`表示两点之间的最短距离。 diff --git a/Graph/2699.Modify-Graph-Edge-Weights/2699.Modify-Graph-Edge-Weights.cpp b/Graph/2699.Modify-Graph-Edge-Weights/2699.Modify-Graph-Edge-Weights.cpp new file mode 100644 index 000000000..c9a2d2947 --- /dev/null +++ b/Graph/2699.Modify-Graph-Edge-Weights/2699.Modify-Graph-Edge-Weights.cpp @@ -0,0 +1,69 @@ +using PII = pair; +class Solution { + unordered_map next[105]; + int todo[105][105]; +public: + vector> modifiedGraphEdges(int n, vector>& edges, int source, int destination, int target) + { + for (auto& edge: edges) + { + int a = edge[0], b = edge[1], c=edge[2]; + if (c==-1) + { + c = 1; + todo[a][b] = 1; + todo[b][a] = 1; + } + next[a][b] = c; + next[b][a] = c; + } + + priority_queue, greater<>>pq; + vectordist1(n, INT_MAX/3); + + pq.push({0, destination}); + while (!pq.empty()) + { + auto [d, cur] = pq.top(); + pq.pop(); + if (dist1[cur]!=INT_MAX/3) continue; + dist1[cur] = d; + for (auto [nxt, weight]: next[cur]) + { + if (dist1[nxt]!=INT_MAX/3) continue; + pq.push({d+weight, nxt}); + } + } + + + vectordist(n, INT_MAX/3); + pq.push({0, source}); + while (!pq.empty()) + { + auto [d, cur] = pq.top(); + pq.pop(); + if (dist[cur]!=INT_MAX/3) continue; + dist[cur] = d; + if (cur==destination && d != target) return {}; + for (auto [nxt, weight]: next[cur]) + { + if (dist[nxt]!=INT_MAX/3) continue; + if (todo[cur][nxt]==1 && dist[cur]+weight+dist1[nxt] < target) + { + weight = target-dist[cur]-dist1[nxt]; + next[cur][nxt] = weight; + next[nxt][cur] = weight; + } + pq.push({d+weight, nxt}); + } + } + + for (auto& edge: edges) + { + int a = edge[0], b = edge[1]; + edge[2] = next[a][b]; + } + + return edges; + } +}; diff --git a/Graph/2699.Modify-Graph-Edge-Weights/Readme.md b/Graph/2699.Modify-Graph-Edge-Weights/Readme.md new file mode 100644 index 000000000..aa7ec6d09 --- /dev/null +++ b/Graph/2699.Modify-Graph-Edge-Weights/Readme.md @@ -0,0 +1,15 @@ +### 2699.Modify-Graph-Edge-Weights + +因为最终修正边权之后的图里要求所有的边都是正数,所以我们第一步肯定先将所有能修改的边从-1改为为最小的正数值1放入图中。 + +最暴力的思想就是不停地跑Dijkstra求起点到终点的最短距离。如果当前的最短距离已经大于target,那么无解。如果当前的最短距离就是target,那么我们就不需要改动。如果当前的最短距离小于target,且最短距离里不包括任何可修改的边,那么也是无解。剩下的情况就是最短距离小于target,且其中包含了至少一条可修改的边,那么我们可以贪心地将该边权调大,使得路径恰为target。这样我们就消灭了一条小于target的路径。然后重复以上的过程。这样的算法可能会跑o(E)遍的Dijkstra,会TLE。 + +我们再审视一下我们的Dijkstra算法。注意当我们每次从PQ里弹出一个已经确定最短距离的的点,会尝试通过其邻接的边将一个新点加入PQ,如果我们所用到的所有的边都是不可修改的,那么我们弹出的点及其最短路径也都是不可修改的。但是当我们需要用到一条可修改的边时,比如说已知从起点到a的最短路径,然后a与b有一条可修改的边,此时我们在将b加入PQ时就会有所顾虑。如果“起点到a的最短距离”+“ab之间的边权1”+“b到终点的最短距离”小于target的话,那么我们就违反了题意。所以我们可以贪心地更改这条可修改边,使得三段距离之和变成target。这就意味着我们需要提前计算“b到终点的最短距离”。这样,当b收录进入PQ的时候,我们就保证了这条到达b的路径,不会造成任何“起点到终点的最短路径小于target”,我们可以放心地加入PQ共后续使用。 + +所以依据上面的算法,可以在一次的Dijkstra的过程中不断地贪心地设置可修改边的边权。知道我们发现终点从PQ里弹出时,意味着我们已经确定了起点到终点的最短距离。如果这个距离不为target,那么就是无解。 + +=========== + +Q: 当边P-Q为可编辑边时,则需考虑`dist[S-P] + weight(P, Q) + dist1[Q-D] < target`,但为何我们能够笃定dist1[Q-D]未经过任何我们已经修改过的可编辑边呢? 因为如果dist1[Q-D]有经过已修改过的可编辑边,现阶段的dist1[Q-D]其实已经比当时纪录的还大了,那上面的条件式可能会给出错误的判定结果。 + +A: 假设如你所说,当从优先队列弹出P点时,在Q到D的最短路径有一条已经修改过的可编辑边,假设为AB。既然AB已经修改过,那么AB必然是从起点S到某个点(假设是C)的最短距离(已经早于P点从优先队列里处理过)的一部分。于是即存在这样一条路径S-A-B-C,它是短于S-P的(这是因为Dijkstra算法会会按从小到大输出各个点的最短路径)。OK,既然 S-A-B-C>next[100005]; + unordered_mapcount; + LL ret = 0; +public: + long long countPalindromePaths(vector& parent, string s) + { + int n = parent.size(); + for (int i=1; i countVisitedNodes(vector& edges) + { + int n = edges.size(); + vectorrets(n); + + for (int i=0; iq; + for (int i=0; i&rets) + { + if (rets[cur]!=0) + return rets[cur]; + + rets[cur] = dfs(next[cur], rets) + 1; + return rets[cur]; + } +}; diff --git a/Graph/2876.Count-Visited-Nodes-in-a-Directed-Graph/Readme.md b/Graph/2876.Count-Visited-Nodes-in-a-Directed-Graph/Readme.md new file mode 100644 index 000000000..ed8ebc279 --- /dev/null +++ b/Graph/2876.Count-Visited-Nodes-in-a-Directed-Graph/Readme.md @@ -0,0 +1,7 @@ +### 2876.Count-Visited-Nodes-in-a-Directed-Graph + +对于任何有向图而言,顺着边的方向走向去,只有两种归宿:要么进入死胡同,要么进入循环圈。所以你可以把有向图简单地认为就是若干个单链并入一个环。 + +我们先找出入度为零的节点,然后用拓扑排序的方法将所有单链上的节点排除掉。剩下的就是环上的节点。从环的任意节点出发,可以遍历整个环得到环的长度(也就是对于这些节点的答案)。 + +最后再遍历单链节点,dfs直至遇到环的入口,这段距离加上环的长度,就是对于这些节点的答案。 diff --git a/Graph/2959.Number-of-Possible-Sets-of-Closing-Branches/2959.Number-of-Possible-Sets-of-Closing-Branches.cpp b/Graph/2959.Number-of-Possible-Sets-of-Closing-Branches/2959.Number-of-Possible-Sets-of-Closing-Branches.cpp new file mode 100644 index 000000000..2665fb090 --- /dev/null +++ b/Graph/2959.Number-of-Possible-Sets-of-Closing-Branches/2959.Number-of-Possible-Sets-of-Closing-Branches.cpp @@ -0,0 +1,53 @@ +class Solution { +public: + int numberOfSets(int n, int maxDistance, vector>& roads) + { + int ret = 0; + for (int state=0; state<(1<>d(n, vector(n, INT_MAX/3)); + for (int i=0; i>i)&1)==0) continue; + d[i][i] = 0; + } + + for (auto road: roads) + { + int a = road[0], b = road[1], w = road[2]; + if (((state>>a)&1)==0) continue; + if (((state>>b)&1)==0) continue; + + for (int i=0; i>i)&1)==0) continue; + for (int j=0; j>j)&1)==0) continue; + d[i][j] = min(d[i][j], d[i][a]+w+d[b][j]); + d[i][j] = min(d[i][j], d[i][b]+w+d[a][j]); + } + } + } + + int flag = 1; + for (int i=0; i>i)&1)==0) continue; + for (int j=0; j>j)&1)==0) continue; + if (d[i][j]>maxDistance) + { + flag = 0; + break; + } + } + if (flag==0) break; + } + if (flag) ret++; + } + + return ret; + } +}; diff --git a/Graph/2959.Number-of-Possible-Sets-of-Closing-Branches/Readme.md b/Graph/2959.Number-of-Possible-Sets-of-Closing-Branches/Readme.md new file mode 100644 index 000000000..fd826991c --- /dev/null +++ b/Graph/2959.Number-of-Possible-Sets-of-Closing-Branches/Readme.md @@ -0,0 +1,15 @@ +### 2959.Number-of-Possible-Sets-of-Closing-Branches + +因为节点数目n只有10,所以我们可以暴力枚举所有的closure方案,只需要2^n不超过1024种。 + +对于每种closure的方案,我们可以用类似Floy算法的n^3的时间度算出任意两点间的最短距离(排除掉closed point),然后只需要检查是否都小于targetDistance即可。 + +Floyd松弛算法如下: +```cpp +for road : roads + int a = road[0], b = road[1], w = road[2]; + for (int i=0; i& original, vector& changed, vector& cost) + { + for (int i=0; i<26; i++) + for (int j=0; j<26; j++) + { + if (i!=j) + d[i][j] = LLONG_MAX/3; + else + d[i][j] = 0; + } + + + for (int i=0; i countOfPairs(int n, int x, int y) + { + if (x>y) return countOfPairs(n, y, x); + this->n = n; + + vectorrets; + + if (abs(x-y)<=1) + { + for (int t=1; t<=n; t++) + rets.push_back((n-t)*2); + return rets; + } + + f1(x-1); + f1(n-y); + + cout<<"OK"<=t) count[t] += 1; + } + } + + void f4(LL d) + { + for (int t=1; t<=n; t++) + { + if (t < d-t) + count[t] += d*2; + else if (t == d-t) + count[t] += d; + } + } +}; diff --git a/Graph/3017.Count-the-Number-of-Houses-at-a-Certain-Distance-II/Readme.md b/Graph/3017.Count-the-Number-of-Houses-at-a-Certain-Distance-II/Readme.md new file mode 100644 index 000000000..09dc30780 --- /dev/null +++ b/Graph/3017.Count-the-Number-of-Houses-at-a-Certain-Distance-II/Readme.md @@ -0,0 +1,67 @@ +### 3017.Count-the-Number-of-Houses-at-a-Certain-Distance-II + +我们画一个示意图,将图划分为ABC三个区域,其中[x,y]部分为C。 +``` +A-A-A-C(x)-C------C-C(y)-B-B-B + |______________| +``` + +任意两个房子之间的最短距离,可以落入如下六个分类之中, AA,BB,AC,BC,AB,CC. 比如AC表示其中一个放在位于A区,另一个房子位于C区。我们分类讨论。 + +1. AA:对于长度为a个房子的简单串联,里面有多少对距离为t的配对呢?我们记做`helper0(a)` + +对于一个合法配对,将第一个房子记做i,则另一个房子记做i+t,那么要求 +``` +i>=1 +i+t<=a +``` +得到i的范围是[1, a-t]. 故对于距离t,我们可以增加`a-t`个配对(暂时不计首尾互换的重复路径) + +2. BB,计算方法同AA。 + +3. AC:这部分是由一个长度为a的长链,加上一个长度为d的圆环。里面有多少对距离为t的配对呢? + +显然,对于处于圆环上的点,为了与A实现最短距离,我们会根据它们离圆环入口x的位置,平均拆分成两半。这样就行程了三叉的形状:一条单链长度是a+1,然后接着两条支链,长度分别是d/2和(d-1)/2. + +对于在单链上的任意一点i,与长度为b的支链上的任意一点(不包括x点)能组成合法配对的条件是 +``` +i>=1 +i+t>=a+2 +i<=a +i+t<=a+1+b +``` +得到i的范围是[max(1,a+2-t), min(a,a+b+1-t)]. 由此可以计算出有多少个配对。 + +同理,可以计算单链上的任意一点,与长度为c的另一条支链上的任意一点(不包括x点)能组成的合法配对。 + +此外,我们需要单独出计算单链上的任意一点i,到x点能组成的合法配对。单独计算这个是为了避免在处理两条支链时重复计算。 +``` +i>=1 +i+t==a+1 +``` +即需要满足t<=a时,可以增加一个配对。 + +4. BC,计算方法同AC + +5. AB,计算方法类似AA。假设A的部分长度是a,B的部分长度是b,中间间隔了2(因为x和y相连)。里面有多少对距离为t的配对呢?我们记做`helper2(a)` + +对于在A上的任意一点i,与B上的任意一点能组成合法配对的条件是 +``` +i>=1 +i<=a +i+t>=a+3 +i+t<=a+2+b +``` +得到i的范围是[max(1,a+3-t), min(a,a+b+2-t)]. 由此可以计算出有多少个配对。 + +6. CC,此部分是一个长度为d的圆环,问里面有多少个长度为t的配对? + +对于圆环上任意一点i,顺时针走t步到达i+t的位置。这两个位置要形成一个有效配对,此时要保证它们的逆时针路径要小于t。即 +``` +t < d-t +``` +对于满足这个要求的t,那么圆环上的任意一点都是可以合法配对的起点,故可以增加d个配对。比如说d=4,那么当t=1时的四个配对是[1,2],[2,3],[3,4],[4,1]. + +此时有一个特别需要注意的地方,当`2*t==d`时,虽然也可以增加d个配对,但是这d个配对里,已经包含了首尾颠倒的重复路径。比如说d=4,那么当t=2时的四个配对是[1,3],[2,4],[3,1],[4,2],可以其中包含了重复的路径。而我们之前所有情况的讨论,所计算的配对都是单向的(编号小的在前,编号大的在后),都是需要乘以2的。唯独这个情况下,我们不能再乘以2. + +将以上六种情况的计数全部加起来就是最终答案。 diff --git a/Graph/3112.Minimum-Time-to-Visit-Disappearing-Nodes/3112.Minimum-Time-to-Visit-Disappearing-Nodes.cpp b/Graph/3112.Minimum-Time-to-Visit-Disappearing-Nodes/3112.Minimum-Time-to-Visit-Disappearing-Nodes.cpp new file mode 100644 index 000000000..e256c94f3 --- /dev/null +++ b/Graph/3112.Minimum-Time-to-Visit-Disappearing-Nodes/3112.Minimum-Time-to-Visit-Disappearing-Nodes.cpp @@ -0,0 +1,35 @@ +using PII = pair; +class Solution { + vectornext[50005]; +public: + vector minimumTime(int n, vector>& edges, vector& disappear) + { + vectorrets(n, -1); + + for (auto& edge: edges) + { + int a = edge[0], b = edge[1], w = edge[2]; + next[a].push_back({b,w}); + next[b].push_back({a,w}); + } + + priority_queue, greater<>>pq; + pq.push({0, 0}); + while (!pq.empty()) + { + auto [dist, cur] = pq.top(); + pq.pop(); + if (rets[cur]!=-1) continue; + rets[cur] = dist; + + for (auto [nxt, len]: next[cur]) + { + if (rets[nxt]!=-1) continue; + if (dist + len >= disappear[nxt]) continue; + pq.push({dist + len, nxt}); + } + } + + return rets; + } +}; diff --git a/Graph/3112.Minimum-Time-to-Visit-Disappearing-Nodes/Readme.md b/Graph/3112.Minimum-Time-to-Visit-Disappearing-Nodes/Readme.md new file mode 100644 index 000000000..e5b0c07c6 --- /dev/null +++ b/Graph/3112.Minimum-Time-to-Visit-Disappearing-Nodes/Readme.md @@ -0,0 +1,5 @@ +### 3112.Minimum-Time-to-Visit-Disappearing-Nodes + +非常典型的单源最短路径问题,使用Dijkstra算法毋庸置疑。 + +我们只需要在Dikstra更新每个节点的最短时间时,判断一下此时的最短时间和该点disappear的时间。如果时间disappear更早,那么说明这个点无法出现在任何路径上,将其略过不加入Dijkstra的后续计算。 diff --git a/Graph/3123.Find-Edges-in-Shortest-Paths/3123.Find-Edges-in-Shortest-Paths.cpp b/Graph/3123.Find-Edges-in-Shortest-Paths/3123.Find-Edges-in-Shortest-Paths.cpp new file mode 100644 index 000000000..c1b10ddd1 --- /dev/null +++ b/Graph/3123.Find-Edges-in-Shortest-Paths/3123.Find-Edges-in-Shortest-Paths.cpp @@ -0,0 +1,62 @@ +using PII = pair; + +class Solution { + vectornext[50005]; +public: + vector findAnswer(int n, vector>& edges) + { + int m = edges.size(); + vectorrets(m, false); + + for (auto& edge : edges) + { + int a = edge[0], b = edge[1], w = edge[2]; + next[a].push_back({b,w}); + next[b].push_back({a,w}); + } + + priority_queue, greater<>>pq; + + pq.push({0, 0}); + vectord1(n, INT_MAX/3); + while (!pq.empty()) + { + auto [dist, cur] = pq.top(); + pq.pop(); + if (d1[cur]!= INT_MAX/3) continue; + d1[cur] = dist; + + for (auto [nxt, len]: next[cur]) + { + if (d1[nxt]!= INT_MAX/3) continue; + pq.push({dist + len, nxt}); + } + } + + pq.push({0, n-1}); + vectord2(n, INT_MAX/3); + while (!pq.empty()) + { + auto [dist, cur] = pq.top(); + pq.pop(); + if (d2[cur]!= INT_MAX/3) continue; + d2[cur] = dist; + + for (auto [nxt, len]: next[cur]) + { + if (d2[nxt]!= INT_MAX/3) continue; + pq.push({dist + len, nxt}); + } + } + + for (int i=0; iname2idx; +public: + double maxAmount(string initialCurrency, vector>& pairs1, vector& rates1, vector>& pairs2, vector& rates2) + { + unordered_setSet; + for (auto pair: pairs1) + { + Set.insert(pair[0]); + Set.insert(pair[1]); + } + for (auto pair: pairs2) + { + Set.insert(pair[0]); + Set.insert(pair[1]); + } + int idx = 0; + for (string s: Set) + name2idx[s] = idx++; + + + int n = name2idx.size(); + vector> dist1 = floyd(pairs1, rates1); + vector> dist2 = floyd(pairs2, rates2); + + int s = name2idx[initialCurrency]; + double ret = 1.0; + for (int i=0; i> floyd(vector>& pairs, vector& rates) + { + int n = name2idx.size(); + vector>dist(n, vector(n,0)); + for (int i=0; ib的路径长度(汇率)是t的话,必然有b->a的路径长度是1/t,别忘了将其也加入图优化的松弛过程。 diff --git a/Greedy/031.Next-Permutation/031.Next-Permutation.cpp b/Greedy/031.Next-Permutation/031.Next-Permutation.cpp new file mode 100644 index 000000000..c3752dcd9 --- /dev/null +++ b/Greedy/031.Next-Permutation/031.Next-Permutation.cpp @@ -0,0 +1,24 @@ +class Solution { +public: + void nextPermutation(vector& nums) + { + int i = nums.size()-1; + while (i>=1 && nums[i]<=nums[i-1]) + i--; + + if (i==0) + { + sort(nums.begin(), nums.end()); + return; + } + + i--; + + int j = nums.size()-1; + while (nums[j]<=nums[i]) + j--; + swap(nums[i], nums[j]); + sort(nums.begin()+i+1, nums.end()); + return; + } +}; diff --git a/Greedy/031.Next-Permutation/Readme.md b/Greedy/031.Next-Permutation/Readme.md new file mode 100644 index 000000000..250ef1182 --- /dev/null +++ b/Greedy/031.Next-Permutation/Readme.md @@ -0,0 +1,7 @@ +### 031.Next-Permutation + +首先,如果已经是完全降序的序列,它是没有next permuation的。此时输出重新按升序排列的数组。 + +接下来,我们从后往前遍历,当第一次出现`nums[i]arr) @@ -31,7 +31,6 @@ class Solution { for (int i = 0; i < arr.size(); i++) { max_ending_here = max_ending_here + arr[i]; - max_ending_here %= M; if (max_ending_here < 0) max_ending_here = 0; if (max_so_far < max_ending_here) diff --git a/Greedy/1504.Count-Submatrices-With-All-Ones/1504.Count-Submatrices-With-All-Ones.cpp b/Greedy/1504.Count-Submatrices-With-All-Ones/1504.Count-Submatrices-With-All-Ones.cpp deleted file mode 100644 index 00c5f3f93..000000000 --- a/Greedy/1504.Count-Submatrices-With-All-Ones/1504.Count-Submatrices-With-All-Ones.cpp +++ /dev/null @@ -1,37 +0,0 @@ -class Solution { -public: - int numSubmat(vector>& mat) - { - int M=mat.size(); - if (M==0) return 0; - int N=mat[0].size(); - int ret = 0; - for (int i=0; i(N,0); - for (int k=i; kdp(26, 0); + + int ret = 0; + for (int i=0; i& nums) + { + sort(nums.begin(), nums.end()); + int n = nums.size(); + return min({nums[n-2]-nums[1], nums[n-1]-nums[2], nums[n-3]-nums[0]}); + } +}; diff --git a/Greedy/2567.Minimum-Score-by-Changing-Two-Elements/Readme.md b/Greedy/2567.Minimum-Score-by-Changing-Two-Elements/Readme.md new file mode 100644 index 000000000..f09858e8f --- /dev/null +++ b/Greedy/2567.Minimum-Score-by-Changing-Two-Elements/Readme.md @@ -0,0 +1,7 @@ +### 2567.Minimum-Score-by-Changing-Two-Elements + +设想,如果贪心地将两个修改的名额都用来降低 high score,我们可以有三种方法:(1) 将最大值改为次大值,最小值改为次小值,这样high score就是`nums[n-2]-nums[1]`. (2) 将最小的两个值都改为第三小的值,这样high score就是`nums[n-1]-nums[2]`. (2) 将最大的两个值都改为第三大的值,这样high score就是`nums[n-3]-nums[0]`. + +此时我们发现,我们选取上述的哪个方案,因为出现了重复元素,所以low score都是零。 + +所以答案就是在三个high score里面挑最小值即可。 diff --git a/Greedy/2568.Minimum-Impossible-OR/2568.Minimum-Impossible-OR_v1.cpp b/Greedy/2568.Minimum-Impossible-OR/2568.Minimum-Impossible-OR_v1.cpp new file mode 100644 index 000000000..6776497aa --- /dev/null +++ b/Greedy/2568.Minimum-Impossible-OR/2568.Minimum-Impossible-OR_v1.cpp @@ -0,0 +1,17 @@ +class Solution { +public: + int minImpossibleOR(vector& nums) + { + sort(nums.begin(), nums.end()); + int mx = 0; + for (int i=0; i mx+1) + return mx+1; + else + mx = (mx | nums[i]); + } + + return mx+1; + } +}; diff --git a/Greedy/2568.Minimum-Impossible-OR/2568.Minimum-Impossible-OR_v2.cpp b/Greedy/2568.Minimum-Impossible-OR/2568.Minimum-Impossible-OR_v2.cpp new file mode 100644 index 000000000..e7c6c54c3 --- /dev/null +++ b/Greedy/2568.Minimum-Impossible-OR/2568.Minimum-Impossible-OR_v2.cpp @@ -0,0 +1,13 @@ +class Solution { +public: + int minImpossibleOR(vector& nums) + { + unordered_setSet(nums.begin(), nums.end()); + for (int i=0; i<31; i++) + { + if (Set.find(1<mx+1`,那么我们如论如何都无法构造出mx+1来。 + +同理,本题里的或运算和加法运算有着相同的性质:越搞数越大。假设前i-1个元素里,我们能构造连续的自然数[1,mx],那么如果`nums[i]<=mx+1`,那么意味着前i个元素里,我们可以任意构造[1,mx|num[i]]里的元素。反之,如果`nums[i]>mx+1`,那么将nums[i]与任何[1,mx]里面的元素进行操作,得到的都会比nums[i]还大,我们如论也无法构造出mx+1来。 + +#### 解法2 +假设我们能够构造出2^0,2^1,..,2^k,那么意味着[1,2^(k+1)-1]里面的任何元素都能构造出来。但是我们肯定无法构造出2^(k+1),所以我们只需要查看2^(k+1)是否在数组里即可。如果在的话,那么递归处理,我们只需要查看`2^(k+2)`是否在数组里即可。即本题求的就是最小的、不在数组里的2的幂。 diff --git a/Greedy/2571.Minimum-Operations-to-Reduce-an-Integer-to-0/2571.Minimum-Operations-to-Reduce-an-Integer-to-0.cpp b/Greedy/2571.Minimum-Operations-to-Reduce-an-Integer-to-0/2571.Minimum-Operations-to-Reduce-an-Integer-to-0.cpp new file mode 100644 index 000000000..c57e673de --- /dev/null +++ b/Greedy/2571.Minimum-Operations-to-Reduce-an-Integer-to-0/2571.Minimum-Operations-to-Reduce-an-Integer-to-0.cpp @@ -0,0 +1,21 @@ +class Solution { +public: + int minOperations(int n) + { + int ret =0 ; + for (int i=0; i<31; i++) + { + if (count(n+(1<>& lcp) + { + int n = lcp.size(); + string s(n, '0'); + + int i = 0; + for (char ch = 'a'; ch<='z'; ch++) + { + while (i>dp(n, vector(n,0)); + for (int i=n-1; i>=0; i--) + for (int j=n-1; j>=0; j--) + { + if (s[i]==s[j]) + dp[i][j] = (i==n-1 || j==n-1)? 1: (dp[i+1][j+1] + 1); + if (dp[i][j] != lcp[i][j]) + return ""; + } + + return s; + } +}; diff --git a/Greedy/2573.Find-the-String-with-LCP/Readme.md b/Greedy/2573.Find-the-String-with-LCP/Readme.md new file mode 100644 index 000000000..81a5db7cd --- /dev/null +++ b/Greedy/2573.Find-the-String-with-LCP/Readme.md @@ -0,0 +1,11 @@ +### 2573.Find-the-String-with-LCP + +首先,我们知道,如果lcp[i][j]>=0,那么一定意味着s[i]==s[j],这意味着我们知道任意两个字符是否相等的信息。假设LCP的信息是准确的,那么仅凭这些信息我们就可以充分地构造出字符串来。 + +我们先考察s中第一个未填写的位置i,此时必然是s[0]。由于我们只有字符相等的约束,而没有字符大小的约束,所以为了构造字典序最小的字符串,我们必然将s[0]填写为'a'。此时我们只需要考察所有`lcp[0][j]>0`的位置j,那么必然有s[j]=s[0]='a'。当然如果我们发现s[j]已经被填写过了且不是'a',那么说明引出了矛盾,可以直接返回无解。 + +接下来我们考察s中第二个未填写的字符i。注意这个位置可能不一定是s[1],因为s[1]可能已经由于一些相等关系的约束而已经填写了。同样,为了使得字典序最小,我们必然将s[i]置为'b'。此时我们只需要考察所有`lcp[i][j]>0`的位置j,那么必然有s[j]=s[i]='b'。当然如果我们发现s[j]已经被填写过了且不是'b',那么说明引出了矛盾,可以直接返回无解。 + +依次类推,我们可以将26个字母顺次填入s未填充的位置上。如果最后还有一些位置没有填充完,说明无法用26个英文字符完成任务。 + +以上得到的s是基于LCP可信赖的前提。但是LCP本身可能是有问题的,比如`lcp[0][2]=3`但是`lcp[1][3]=4`,这样的信息是矛盾的。所以我们还需要检验基于s的LCP矩阵的准确性。求任意两个位置的LCP,这本质就是求一个双序列的DP,用两层循环即可实现。 diff --git a/Greedy/2576.Find-the-Maximum-Number-of-Marked-Indices/2576.Find-the-Maximum-Number-of-Marked-Indices.cpp b/Greedy/2576.Find-the-Maximum-Number-of-Marked-Indices/2576.Find-the-Maximum-Number-of-Marked-Indices.cpp new file mode 100644 index 000000000..f18f83e3d --- /dev/null +++ b/Greedy/2576.Find-the-Maximum-Number-of-Marked-Indices/2576.Find-the-Maximum-Number-of-Marked-Indices.cpp @@ -0,0 +1,23 @@ +class Solution { +public: + int maxNumOfMarkedIndices(vector& nums) + { + sort(nums.begin(), nums.end()); + int n = nums.size(); + + int i = 0, j = n/2; + int count = 0; + for (int i=0; inums[j]) + j++; + if (j=2a`的元素b与之配对。目的是尽量保留更大的元素可以用来匹配次小的元素。然后重复这样的过程。 + +但是这种策略会遇到这样的一个例子:`[2,4,5,9]`。当2与4匹配之后,此时未被匹配的次小元素是5,反而变得更大了。 + +正确的思考方式是:如果总元素是n,那么最多能匹配n/2对。为了更多地凑出这些pairs,每个pair的较小值必然在排序后的nums的前半部分,而较大值在nums的后半部分。假设有一对[a,b]都在前半部分,另一对[c,d]都在后半部分,那么必然可以重构出两对[a,c],[b,d]同样更容易条件。同理,可以证明出其他情况下,任何处于同一个半区的配对都不会是最优解。 + +所以本题只需要使用双指针,第一个指针i在前半区遍历,第二个指针j在后半区遍历,对于每个i单调移动j看看是否能有配对即可。 + +本题在codeforces上的原题是:https://codeforces.com/contest/372/problem/A + diff --git a/Greedy/2580.Count-Ways-to-Group-Overlapping-Ranges/2580.Count-Ways-to-Group-Overlapping-Ranges.cpp b/Greedy/2580.Count-Ways-to-Group-Overlapping-Ranges/2580.Count-Ways-to-Group-Overlapping-Ranges.cpp new file mode 100644 index 000000000..f37b46d2b --- /dev/null +++ b/Greedy/2580.Count-Ways-to-Group-Overlapping-Ranges/2580.Count-Ways-to-Group-Overlapping-Ranges.cpp @@ -0,0 +1,26 @@ +using LL = long long; +LL M = 1e9+7; +class Solution { +public: + int countWays(vector>& ranges) + { + sort(ranges.begin(), ranges.end()); + int n = ranges.size(); + LL ret = 1; + + for (int i=0; i>& tasks) + { + sort(tasks.begin(), tasks.end(), [](vector&a, vector&b){ + return a[1] < b[1]; + }); + + vectortime(2005); + for (int i=0; i= duration) continue; + int diff = duration - overlap; + for (int t=end; t>=start; t--) + { + if (time[t]==0) + { + time[t] = 1; + diff--; + } + if (diff == 0) + break; + } + } + + int ret = 0; + for (int t=0; t<=2000; t++) + ret += (time[t]==1); + return ret; + } +}; diff --git a/Greedy/2589.Minimum-Time-to-Complete-All-Tasks/2589.Minimum-Time-to-Complete-All-Tasks_v2.cpp b/Greedy/2589.Minimum-Time-to-Complete-All-Tasks/2589.Minimum-Time-to-Complete-All-Tasks_v2.cpp new file mode 100644 index 000000000..58e273662 --- /dev/null +++ b/Greedy/2589.Minimum-Time-to-Complete-All-Tasks/2589.Minimum-Time-to-Complete-All-Tasks_v2.cpp @@ -0,0 +1,46 @@ +using AI3 = array; +class Solution { +public: + int findMinimumTime(vector>& tasks) + { + sort(tasks.begin(), tasks.end(), [](vector&a, vector&b){ + return a[1] < b[1]; + }); + + vectorarr; + arr.push_back({-2,-1,0}); + + for (int i=0; i 0) + { + if (abs(arr.back()[1] - cur) < diff) + { + diff -= abs(arr.back()[1] - cur); + cur = arr.back()[0] - 1; + arr.pop_back(); + } + else + { + arr.push_back({cur-diff+1, end, arr.back()[2] + end-(cur-diff)}); + diff = 0; + } + } + } + + return arr.back()[2]; + } +}; diff --git a/Greedy/2589.Minimum-Time-to-Complete-All-Tasks/Readme.md b/Greedy/2589.Minimum-Time-to-Complete-All-Tasks/Readme.md new file mode 100644 index 000000000..6674d3426 --- /dev/null +++ b/Greedy/2589.Minimum-Time-to-Complete-All-Tasks/Readme.md @@ -0,0 +1,22 @@ +### 2589.Minimum-Time-to-Complete-All-Tasks + +#### 解法1: +我们将所有任务按照end排序。这是因为end早的任务我们必然先考虑,其它没有到deadline的任务都可以放一放。对于第一个任务,我们必然会尽量拖延它的启动时间,即实际的运作时段是`[end-duration+1, end]`,这样就可以与后面的任务有更多的重合时间。对于第二个任务,必然会充分利用它与第一个任务实际工作的重合部分,假设已经重合的时间不够完成第二个任务,那么我们会在什么时段继续工作呢?其实也是同理,就是卡在第二个任务的deadline之前完成,目的也是为了尽量拖延,增加与后面的任务重合的概率。依此贪心的策略,就可以处理所有的任务。 + +因为本题的数据量不多,任务的个数`n <= 2000`,另外整体的时间跨度也不大`1 <= starti, endi <= 2000`,所以本题可以通过在时间轴上的遍历来暴力解决。比如对于某个任务[start,end,duration],我们先看时间轴上哪些时刻是标记为开工的,与它的重合部分有多长,再与duration比较还得需要多长时间diff才能完成。如果T大于零,那么我们就从end开始往前遍历,将没有在工作的时刻标记为开工,直至把diff都消耗完。 + +这样的时间复杂度是`O(N*T)`。 + +#### 解法2: +上述算法的时间复杂度其实可以不依赖于总时间跨度T。可以想象,如果每个任务的时间跨度都很大,那么遍历时间轴的效率是很低的。上述的算法可以用o(nlogn)来实现。 + +在上述算法里,我们依次处理每个任务的时候,其实都会在时间轴上确定下一段段的实际开工时间,它们是一系列互不重叠的区间。所以我们用arr来盛装这些区间,对于每个区间我们用[a,b,totalTime]表示,a表示起点、b表示终点(都是双闭区间),totalTime表示该区间结束时整个时间轴上已经开工时间的总和,相当于arr里开工区间的长度的前缀和。 + +对于一个新任务[start,end,duration],我们先要计算新区间与已经开工的这些区间有多少重合度`overlap`。因为有了前缀和的信息,所以这个计算是可行的。我们只需要用二分法,定位start在arr里的位置,找到最后一个早于start的区间interval。如果此interval与新任务完全不重合,那么新任务与已开工的重合度`overlap`就是interval右边的所有开工区间的长度,这可以用两个前缀和之差得到。如果此interval与新任务有重合部分,那么`overlap`就是前一种情况的计算结果,再加上此区间与[start,end]的重合部分。 + +我们知道了`duration`和`overlap`,就可以知道我们还需要从end开始往前填充若干个开工区间之间的间隔,以满足额外的开工长度`diff`,显然这会导致arr最后的几个开工区间合并起来。所以我们就暴力从后往前枚举每个区间[a,b],思路如下: +1. 如果融合了该区间,那么我们新增了多长的开工时间(新增的开工时间其实是该区间与下一个区间之间的间隔长度) +2. 如果新增的开工时间大于diff,那么说明该区间不用被重合,我们只需要计算该区间右端点后的某个位置x,将[x,end]加入arr即可。 +3. 如果新增的开工时间小于diff,那么说明该区间需要被融合,我们将arr的最后一个区间重置为[a,end]. 更新diff后开启下一个循环。 + +最终的总开工时间就是arr里最后一个区间对应的前缀和。 diff --git a/Greedy/2598.Smallest-Missing-Non-negative-Integer-After-Operations/2598.Smallest-Missing-Non-negative-Integer-After-Operations.cpp b/Greedy/2598.Smallest-Missing-Non-negative-Integer-After-Operations/2598.Smallest-Missing-Non-negative-Integer-After-Operations.cpp new file mode 100644 index 000000000..18ee10bd5 --- /dev/null +++ b/Greedy/2598.Smallest-Missing-Non-negative-Integer-After-Operations/2598.Smallest-Missing-Non-negative-Integer-After-Operations.cpp @@ -0,0 +1,27 @@ +class Solution { +public: + int findSmallestInteger(vector& nums, int value) + { + vectorcount(value); + + for (int& x: nums) + { + x = ((x%value)+value) % value; + count[x] += 1; + } + + int min_count = INT_MAX; + int k; + + for (int i=0; i=0; i--) + { + for (char ch=s[i]+1; ch<'a'+k; ch++) + { + if (!checkOK(s, i, ch)) continue; + s[i] = ch; + + for (int j=i+1; j=1 && s[i-1]==ch) return false; + if (i>=2 && s[i-2]==ch) return false; + return true; + } +}; diff --git a/Greedy/2663.Lexicographically-Smallest-Beautiful-String/Readme.md b/Greedy/2663.Lexicographically-Smallest-Beautiful-String/Readme.md new file mode 100644 index 000000000..d03848765 --- /dev/null +++ b/Greedy/2663.Lexicographically-Smallest-Beautiful-String/Readme.md @@ -0,0 +1,7 @@ +### 2663.Lexicographically-Smallest-Beautiful-String + +本题的关键就是如何解读“不能出现回文子串”。其实这个约束可以简化为“没有任何两个相邻的字符相同”,且“没有任何长度为3的子串里第一个和第三个字符相同”。 + +然后我们就可以贪心地从低位往高位遍历,查看某位置i上能否填写一个比原先更大的字符,且满足上述的约束。如果可以,那么我们必然会尝试贪心地将[i+1:n-1]这一段构造为字典序最小、且符合约束的字符串。事实上,我们总是能构造成功的,因为在任何的位置上,我们只有两个约束(不能与前一个字符相同,不能与前前字符相同),但是`k>=4`,我们至少可以有四种候选。故这样的贪心构造法必然能实现,且保证字典序最小。 + +因此,只要我们从低位往高位遍历,找到第一个实现上述构造(即s[i]和s[i+1:n-1]都满足条件)的位置,那么就有了最终答案。 diff --git a/Greedy/2712.Minimum-Cost-to-Make-All-Characters-Equal/2712.Minimum-Cost-to-Make-All-Characters-Equal_v1.cpp b/Greedy/2712.Minimum-Cost-to-Make-All-Characters-Equal/2712.Minimum-Cost-to-Make-All-Characters-Equal_v1.cpp new file mode 100644 index 000000000..9a47b6cfb --- /dev/null +++ b/Greedy/2712.Minimum-Cost-to-Make-All-Characters-Equal/2712.Minimum-Cost-to-Make-All-Characters-Equal_v1.cpp @@ -0,0 +1,15 @@ +using LL = long long; +class Solution { +public: + long long minimumCost(string s) + { + int n = s.size(); + long long ret = 0; + for (int i=1; ileft(n); + int lastOne = -1; + LL sum = 0; + for (int i=0; i=1 && s[i-1]=='1') + sum = sum+1; + else + sum += (i+1) + i; + + left[i] = sum; + lastOne = i; + } + + vectorright(n); + lastOne = n; + sum = 0; + for (int i=n-1; i>=0; i--) + { + if (s[i]=='0') + { + right[i] = sum; + continue; + } + + if (i+1 goodSubsetofBinaryMatrix(vector>& grid) + { + int m = grid.size(), n = grid[0].size(); + unordered_map>Map; + for (int i=0; i>j)&1)) + { + flag = 0; + break; + } + } + if (flag==0) continue; + if (Map[s].size()==0) continue; + + for (int k: Map[s]) + { + if (k!=i) + { + vectorrets({i,k}); + sort(rets.begin(), rets.end()); + return rets; + } + } + } + } + + return {}; + } +}; diff --git a/Greedy/2732.Find-a-Good-Subset-of-the-Matrix/Readme.md b/Greedy/2732.Find-a-Good-Subset-of-the-Matrix/Readme.md new file mode 100644 index 000000000..daa971062 --- /dev/null +++ b/Greedy/2732.Find-a-Good-Subset-of-the-Matrix/Readme.md @@ -0,0 +1,13 @@ +### 2732.Find-a-Good-Subset-of-the-Matrix + +我们将每行用一个最多含5 bit的二进制数编码来表示它的每个列位置是0还是1. 为了增大复杂度,我们令列数是5. + +首先,我们考虑两种特殊情况。如果有一行的编码是0,那么它自身组成的集合就符合条件。另外,如果有两行的编码的“交集”为零,那么这两行组成的集合也符合条件。 + +接下来考虑,如果任何两行的state的交集都不为0,那么会出现什么情况。 + +我们可以知道,想要有解,至少存在一行,最多含有两个bit 1. 理由是,如果所有的行都存在三个或以上的bit 1,那么无论选取哪些k行,总的bit 1的数就是大于等于3k,但是根据题意“任何一列的bit 1的数目不能超过行数的一半”,即总的bit 1的数目不能超过`0.5k*5=2.5k`,从而产生矛盾。不失一般性地,我们可以令某一行的编码是b00011。 + +回到之前的前提,“如果任何两行的编码的交集都不为0”,那么其他选取的k-1行里,在第0和1的位置上至少有一个bit 1。于是总体的这k行里,就有了至少k+1个bit 1。这就说明了在第0和1的位置上,不可能有任何一列的bit 1的个数少于等于`floor(k/2)`。得到矛盾,因此“任何两行的编码交集都不为0”情况下,是不可能有解的。 + +综上,我们只需要考察之前所述的两种特殊情况即可找出解,或者判定无解。对于第二种特殊情况,我们建立`编码->行号`的映射,就可以知道对于行A而言,是否存在与之符合条件的行B了。 diff --git a/Greedy/2745.Construct-the-Longest-New-String/2745.Construct-the-Longest-New-String.cpp b/Greedy/2745.Construct-the-Longest-New-String/2745.Construct-the-Longest-New-String.cpp new file mode 100644 index 000000000..9a3d1bfde --- /dev/null +++ b/Greedy/2745.Construct-the-Longest-New-String/2745.Construct-the-Longest-New-String.cpp @@ -0,0 +1,8 @@ +class Solution { +public: + int longestString(int x, int y, int z) + { + int t = x+y+z-max(0, (max(x,y)-min(x,y)-1)); + return t*2; + } +}; diff --git a/Greedy/2745.Construct-the-Longest-New-String/Readme.md b/Greedy/2745.Construct-the-Longest-New-String/Readme.md new file mode 100644 index 000000000..330d99ac9 --- /dev/null +++ b/Greedy/2745.Construct-the-Longest-New-String/Readme.md @@ -0,0 +1,7 @@ +### 2745.Construct-the-Longest-New-String + +当我们仅考虑AA和BB时,我们可以将其交替串联,如BBAABBAA...,注意最后可以AA或BB结尾,使用两种片段的个数最多差1。这样能使用到的片段个数是 `min(x,y)*2 + min(abs(x-y),1)`. + +然后考虑所有的AB,只需将其插入任何BB与AA之间即可,不影响之前的构造。 + +所以最终能使用到的片段个数是 `min(x,y)*2 + min(abs(x-y),1) +z`. diff --git a/Greedy/2749.Minimum-Operations-to-Make-the-Integer-Zero/2749.Minimum-Operations-to-Make-the-Integer-Zero.cpp b/Greedy/2749.Minimum-Operations-to-Make-the-Integer-Zero/2749.Minimum-Operations-to-Make-the-Integer-Zero.cpp new file mode 100644 index 000000000..8bbf04ca8 --- /dev/null +++ b/Greedy/2749.Minimum-Operations-to-Make-the-Integer-Zero/2749.Minimum-Operations-to-Make-the-Integer-Zero.cpp @@ -0,0 +1,20 @@ +using LL = long long; +class Solution { +public: + int makeTheIntegerZero(int num1, int num2) + { + long long x = num1; + long long y = num2; + int k = 1; + while (1) + { + x -= y; + if (x < k) return -1; + + int count = __builtin_popcountll(x); + if (count <= k) return k; + k++; + } + return -1; + } +}; diff --git a/Greedy/2749.Minimum-Operations-to-Make-the-Integer-Zero/2749.Minimun-Operations-to-make-the-integer-Zero-PYTHON b/Greedy/2749.Minimum-Operations-to-Make-the-Integer-Zero/2749.Minimun-Operations-to-make-the-integer-Zero-PYTHON new file mode 100644 index 000000000..820e251bf --- /dev/null +++ b/Greedy/2749.Minimum-Operations-to-Make-the-Integer-Zero/2749.Minimun-Operations-to-make-the-integer-Zero-PYTHON @@ -0,0 +1,17 @@ +class Solution(object): + def makeTheIntegerZero(self,num1,num2): + x = num1 + y = num2 + k = 1 + + while True: + x = x - y + if x < k: + return -1 + + if bin(x).count('1') <= k: + return k + + k = k + 1 + +# I hope this can help anyone whos resolving this huge problem on python ;) diff --git a/Greedy/2749.Minimum-Operations-to-Make-the-Integer-Zero/Readme.md b/Greedy/2749.Minimum-Operations-to-Make-the-Integer-Zero/Readme.md new file mode 100644 index 000000000..0a59bc74b --- /dev/null +++ b/Greedy/2749.Minimum-Operations-to-Make-the-Integer-Zero/Readme.md @@ -0,0 +1,5 @@ +### 2749.Minimum-Operations-to-Make-the-Integer-Zero + +本题就是寻找最小的操作次数k,使得`num1-k*num2`可以表示为k个`2^i`相加的形式,标记为(*)。 + +我们观察k个`2^i`相加,它有最小值就是k。所以如果`num1-k*num2 doors); + * void closeDoor(); + * bool isDoorOpen(); + * void moveRight(); + * }; + */ +class Solution { +public: + int houseCount(Street* street, int k) + { + while (!street->isDoorOpen()) + street->moveRight(); + street->moveRight(); + + int step = 1; + int lastOpen = 0; + for (int i=0; iisDoorOpen()) + { + lastOpen = step; + street->closeDoor(); + } + step++; + street->moveRight(); + } + return lastOpen; + } +}; diff --git a/Greedy/2753.Count-Houses-in-a-Circular-Street-II/Readme.md b/Greedy/2753.Count-Houses-in-a-Circular-Street-II/Readme.md new file mode 100644 index 000000000..47e7a2e56 --- /dev/null +++ b/Greedy/2753.Count-Houses-in-a-Circular-Street-II/Readme.md @@ -0,0 +1,5 @@ +### 2753.Count-Houses-in-a-Circular-Street-II + +我们先找到一处状态为open的门。然后从下一个位置作为起点,连续走k格,图中如果遇到任何open的门就将其关闭,但同时记录并保持更新lastOpen相对于起点的距离。 + +走完k格之后,lastOpen一定就是起点之前的那扇门,于是lastOpen相对于起点的距离就是整圈的长度。 diff --git a/Greedy/2813.Maximum-Elegance-of-a-K-Length-Subsequence/2813.Maximum-Elegance-of-a-K-Length-Subsequence.cpp b/Greedy/2813.Maximum-Elegance-of-a-K-Length-Subsequence/2813.Maximum-Elegance-of-a-K-Length-Subsequence.cpp new file mode 100644 index 000000000..76d6f2be6 --- /dev/null +++ b/Greedy/2813.Maximum-Elegance-of-a-K-Length-Subsequence/2813.Maximum-Elegance-of-a-K-Length-Subsequence.cpp @@ -0,0 +1,49 @@ +using LL = long long; +using PII = pair; +class Solution { +public: + long long findMaximumElegance(vector>& items, int k) + { + sort(items.rbegin(), items.rend()); + + LL sum = 0; + unordered_mapMap; + for (int i=0; i, greater<>>pq; + for (int i=0; i 1) + { + sum -= profit; + sum += items[i][0]; + t++; + Map[cate]--; + Map[items[i][1]]++; + + ret = max(ret, sum + t*t); + break; + } + } + } + + return ret; + } +}; diff --git a/Greedy/2813.Maximum-Elegance-of-a-K-Length-Subsequence/Readme.md b/Greedy/2813.Maximum-Elegance-of-a-K-Length-Subsequence/Readme.md new file mode 100644 index 000000000..12052adb1 --- /dev/null +++ b/Greedy/2813.Maximum-Elegance-of-a-K-Length-Subsequence/Readme.md @@ -0,0 +1,13 @@ +### 2813.Maximum-Elegance-of-a-K-Length-Subsequence + +一个显然的想法是,能否遍历种类的数目:在固定种类数目的情况下,贪心地选择对应profit最高的k个item。但是即使说我们只考虑t个category,但是这样的t-distinct的category组合也非常多,我们无法穷举。 + +我们继续考虑。如果将所有元素按照profit降序排列,粗暴地取前k个元素,并记此时有t种不同的category,那么我们至少可以claim,当强制选择t个category时,此时的收益一定是最高的。因为我们选取的项目本身就是profit的top K. + +然后我们想,强制选择小于t个category的话,该如何规划呢?本题的突破口就在这里。我们知道,相比于上述`choose profit top K`的决策,其他任何决策都不会在`total_profit`更优;并且如果打算选择的category个数还更小的话,`distinct_categories^2`也不会占优势。故总的elegance肯定不及上面的方案。所以我们可以终止这个方向的探索。 + +然后我们想,强制选择多余t个category的话,该如何规划呢?既然top K个item已经包含了t个category,我们必然会贪心地按照profit的降序考察后续的项目,直至找到一个属于新种类的item,这样就有了t+1个category.注意,此时我们为了保持item总数为k,必然要吐出一个item:这个item必然是profit尽量小,同时它对应的category必须还存在其他的元素(否则将其吐出之后总的category数目就又不够t+1了)。所以我们的做法是将之前的top k item都放入一个小顶堆的PQ,需要弹出时查看当前profit最小的item是否是“单身”,如果是的话就忽略,如果否的话就可以将其“吐出”而将属于新category的item加入。这样我们就得到了t+1个category时的profit top k. + +依次类推,我们可以得到t+2个category时的profit top k,以及t+3个category时的profit top k等等。最终在所有category数目对应的最大elegance里挑选最大值。 + +但是注意,在贪心的过程中,如果我们无法找到一个可以吐出的item时,意味着我们无法构造“t+1个category时的profit top k”,因为这时已经发生了`t+1>k`。 diff --git a/Greedy/2835.Minimum-Operations-to-Form-Subsequence-With-Target-Sum/2835.Minimum-Operations-to-Form-Subsequence-With-Target-Sum.cpp b/Greedy/2835.Minimum-Operations-to-Form-Subsequence-With-Target-Sum/2835.Minimum-Operations-to-Form-Subsequence-With-Target-Sum.cpp new file mode 100644 index 000000000..524315fb7 --- /dev/null +++ b/Greedy/2835.Minimum-Operations-to-Form-Subsequence-With-Target-Sum/2835.Minimum-Operations-to-Form-Subsequence-With-Target-Sum.cpp @@ -0,0 +1,51 @@ +class Solution { +public: + int minOperations(vector& nums, int target) + { + vectorcount(31, 0); + for (int x: nums) + { + int i = 0; + while (x>0) + { + x/=2; + i++; + } + count[i-1] += 1; + } + + vectort; + for (int i=0; i<31; i++) + { + if ((target>>i)&1) + t.push_back(i); + } + + int ret = 0; + for (int i: t) + { + int j = 0; + while (j0) + { + count[i] -= 1; + continue; + } + + while (j<31 && count[j]==0) + j++; + if (j==31) return -1; + count[j] -= 1; + for (int k=j-1; k>=i; k--) + count[k]+=1; + ret += j-i; + } + + return ret; + } +}; diff --git a/Greedy/2835.Minimum-Operations-to-Form-Subsequence-With-Target-Sum/Readme.md b/Greedy/2835.Minimum-Operations-to-Form-Subsequence-With-Target-Sum/Readme.md new file mode 100644 index 000000000..d2f2b9364 --- /dev/null +++ b/Greedy/2835.Minimum-Operations-to-Form-Subsequence-With-Target-Sum/Readme.md @@ -0,0 +1,19 @@ +### 2835.Minimum-Operations-to-Form-Subsequence-With-Target-Sum + +显然我们会将nums里的元素做二进制分解,每个二进制位上会有若干个1,我们将其记录在count数组里。count[i]表示第i个bit位上我们有多少个1. + +同理,我们会将target做二进制分解,每个bit位上的1表示我们需要从count里得到的“支持”。比如说,如果target上的每个需要1的二进制位i上,count[i]都大于零的话,那么意味着nums已经可以拼凑出target了。 + +我们从低到高逐个考虑target所需要的二进制位. 假设我们需要第i个bit位上的1,那么我们该如何考察count能否支持呢? + +1. 首先我们考虑比i低的二进制位上,count是否能够通过现有的低位上的“1”的sum来实现第i位上的1(注意,因为nums里每个元素只有一个bit 1,所以低位上1的sum必然对应着nums里某些元素的sum). 我们可以将所有低位的1都加起来,通过逐次进位的形式,看看能否传播到第i位。比如说count[0]=5,i=2, 那么我们可以对count做如下变化 +``` +step 1: count[0]=1, count[1]=2 +step 2: count[0]=1, count[1]=0, count[2]=1 +``` + +基本思想就是:能进位则进位。最终每个count[]上不是0就是1. 上面的例子里,count[0]=5 确实可以给target的第i位提供1的支持。 + +2. 其次,如果以上方法不能实现,那么就意味着我们需要将高位上的1进行“拆解”以满足第i位上的1。显然,我们会贪心地在count里找到最接近i且count>0的位置j,将其拆解j-i次,就可以将第j位上的1传播到j-1,j-2,...i各个位上。这样我们就满足了taget在第i位上的需求。 + +通过以上方法,就是实现本题的最优方案。 diff --git a/Greedy/2856.Minimum-Array-Length-After-Pair-Removals/2856.Minimum-Array-Length-After-Pair-Removals.cpp b/Greedy/2856.Minimum-Array-Length-After-Pair-Removals/2856.Minimum-Array-Length-After-Pair-Removals.cpp new file mode 100644 index 000000000..facb827a9 --- /dev/null +++ b/Greedy/2856.Minimum-Array-Length-After-Pair-Removals/2856.Minimum-Array-Length-After-Pair-Removals.cpp @@ -0,0 +1,22 @@ +class Solution { +public: + int minLengthAfterRemovals(vector& nums) + { + int n = nums.size(); + unordered_mapMap; + for (int i=0; i n/2) + return n - (n-mx)*2; + else + return (n%2); + + } +}; diff --git a/Greedy/2856.Minimum-Array-Length-After-Pair-Removals/Readme.md b/Greedy/2856.Minimum-Array-Length-After-Pair-Removals/Readme.md new file mode 100644 index 000000000..109179d8a --- /dev/null +++ b/Greedy/2856.Minimum-Array-Length-After-Pair-Removals/Readme.md @@ -0,0 +1,7 @@ +### 2856.Minimum-Array-Length-After-Pair-Removals + +本题的本质就是Boyer-Moore Majority Voting Algorithm的实现。当存在一个超过半数的majority时,显然其他所有元素“联合”起来不能使它“消除”。反过来的结论也是成立的。 + +所以,当存在一个超过半数的majority时,记它的频次是f。那么剩余元素的频次是n-f。每个其他元素消灭一个多数元素,剩下的就是`n-(n-f)*2`. + +当不存在超过半数的majority时,理论上是能够最终彼此消灭的,但是别忘了n的奇偶性。当n是奇数时一定会有一个元素留下来。 diff --git a/Greedy/2868.The-Wording-Game/2868.The-Wording-Game.cpp b/Greedy/2868.The-Wording-Game/2868.The-Wording-Game.cpp new file mode 100644 index 000000000..ba81b73ce --- /dev/null +++ b/Greedy/2868.The-Wording-Game/2868.The-Wording-Game.cpp @@ -0,0 +1,23 @@ +class Solution { +public: + bool canAliceWin(vector& a, vector& b) + { + int m = a.size(), n = b.size(); + int i = 0, j = 0; + + while (1) + { + while (j a[i][0]+1) + return true; + + while (i b[j][0]+1) + return false; + } + + return false; + } +}; diff --git a/Greedy/2868.The-Wording-Game/Readme.md b/Greedy/2868.The-Wording-Game/Readme.md new file mode 100644 index 000000000..186f3fe66 --- /dev/null +++ b/Greedy/2868.The-Wording-Game/Readme.md @@ -0,0 +1,5 @@ +### 2868.The-Wording-Game + +本题看上去是很复杂的决策,但本质上就是简单的贪心。类似于打扑克,自己尽量出能恰好压过对手的牌,保留更大的牌后发制人,使得自己可以支撑更多的回合。 + +注意,本题里集合a与b之间都没有任何相同的字符串。这说明不必顾虑“对手出了某张牌导致自己有相同的牌无法打出”的情况。谁手里的牌大、牌多就是能赢的硬道理。 diff --git a/Greedy/2871.Split-Array-Into-Maximum-Number-of-Subarrays/2871.Split-Array-Into-Maximum-Number-of-Subarrays.cpp b/Greedy/2871.Split-Array-Into-Maximum-Number-of-Subarrays/2871.Split-Array-Into-Maximum-Number-of-Subarrays.cpp new file mode 100644 index 000000000..bb271a326 --- /dev/null +++ b/Greedy/2871.Split-Array-Into-Maximum-Number-of-Subarrays/2871.Split-Array-Into-Maximum-Number-of-Subarrays.cpp @@ -0,0 +1,23 @@ +class Solution { +public: + int maxSubarrays(vector& nums) + { + int n = nums.size(); + int ret = 0; + for (int i=0; i& nums, int k) + { + vectorcount(32); + + for (int x: nums) + { + for (int i=0; i<32; i++) + { + if ((x>>i)&1) + count[i] += 1; + } + } + + LL ret = 0; + + for (int t=0; t=0; i--) + { + if (count[i]>0) + { + x += (1LL< 1 1 +1, 0 => 0 1 +0, 1 => 0 1 +0, 0 => 0 0 +``` +我们发现OR的效果其实是在每个bit位上“收集”1,而AND的效果其实就是“送出”1. 一进一出,不难发现`X+Y= AND+OR`。也就是说每次操作X和Y的一对数,我们在“零和”的前提下,拉大了“贫富差距”。这是我们想要的吗?是的,因为这能增大平方和。简单的证明,当`x>=y`且`d>0`时 +``` +(x+d)^2 + (y-d)^2 = x^2 + y^2 + 2d*(x-y) + 2d^2 > x^2 + y^2 +``` + +因为可以无限次操作,所以可以任意从某个元素出发,通过不断OR来“吸取”其他元素里各bit位上的1,直至构造出尽量大的元素。 + +代码中,我们统计每个bit上,nums里总共提供多少个1. 构造大数时,只需从最高位到最低位尽量填充1即可,如果没有库存了,就填充0. 最终取前k个大数,算一下平方和即可。 diff --git a/Greedy/2983.Palindrome-Rearrangement-Queries/2983.Palindrome-Rearrangement-Queries.cpp b/Greedy/2983.Palindrome-Rearrangement-Queries/2983.Palindrome-Rearrangement-Queries.cpp new file mode 100644 index 000000000..15bf8654b --- /dev/null +++ b/Greedy/2983.Palindrome-Rearrangement-Queries/2983.Palindrome-Rearrangement-Queries.cpp @@ -0,0 +1,90 @@ +using PII = pair; +class Solution { + int diff[100005]; + int presum1[100005][26]; + int presum2[100005][26]; +public: + vector canMakePalindromeQueries(string s, vector>& queries) + { + int n = s.size(); + string t = s.substr(n/2, n/2); + reverse(t.begin(), t.end()); + int m = n/2; + s = s.substr(0, n/2); + t = "#"+t; + s = "#"+s; + + for (int i=1; i<=m; i++) + diff[i] = diff[i-1] + (s[i]!=t[i]); + + for (int i=1; i<=m; i++) + for (int ch=0; ch<26; ch++) + { + presum1[i][ch] = presum1[i-1][ch] + (s[i]=='a'+ch); + presum2[i][ch] = presum2[i-1][ch] + (t[i]=='a'+ch); + } + + vectorrets; + for (auto& query: queries) + { + int a = query[0]+1, b = query[1]+1; + int c = m-1-(query[3]-n/2)+1; + int d = m-1-(query[2]-n/2)+1; + + rets.push_back(process(a,b,c,d,m)); + } + return rets; + } + + bool process(int a, int b, int c, int d, int m) + { + vectorcross; + if (max(a,c) <= min(b,d)) + cross.push_back({max(a,c), min(b,d)}); + vectorA; + vectorB; + if (cross.size() == 0) + { + A.push_back({a,b}); + B.push_back({c,d}); + } + else + { + if (a<=c-1) A.push_back({a,c-1}); + if (d+1<=b) A.push_back({d+1, b}); + if (b+1<=d) B.push_back({b+1, d}); + if (c<=a-1) B.push_back({c, a-1}); + } + + int count_diff = 0; + vectorUnion; + for (auto x: cross) Union.push_back(x); + for (auto x: A) Union.push_back(x); + for (auto x: B) Union.push_back(x); + for (auto [s,e]: Union) + count_diff += diff[e] - diff[s-1]; + if (diff[m] - count_diff != 0) return false; + + vectorcount1(26); + vectorcount2(26); + for (int ch=0; ch<26; ch++) + { + count1[ch] = presum1[b][ch] - presum1[a-1][ch]; + count2[ch] = presum2[d][ch] - presum2[c-1][ch]; + } + for (int ch=0; ch<26; ch++) + { + for (auto [s,e]: A) + count1[ch] -= presum2[e][ch] - presum2[s-1][ch]; + for (auto [s,e]: B) + count2[ch] -= presum1[e][ch] - presum1[s-1][ch]; + if (count1[ch]<0 || count2[ch]<0) + return false; + } + + for (int ch=0; ch<26; ch++) + if (count1[ch]!=count2[ch]) return false; + + return true; + } +}; diff --git a/Greedy/2983.Palindrome-Rearrangement-Queries/Readme.md b/Greedy/2983.Palindrome-Rearrangement-Queries/Readme.md new file mode 100644 index 000000000..ddaeb877e --- /dev/null +++ b/Greedy/2983.Palindrome-Rearrangement-Queries/Readme.md @@ -0,0 +1,24 @@ +### 2983.Palindrome-Rearrangement-Queries + +首先我们预处理一下,将s的后半段翻转之后记为t,令s和t的长度都是m=n/2。并且将两个字符串都看做1-index。 + +我们容易得到s中可以重排的区间记做[a,b],t中可以重排的区间记做[c,d]. 考虑到这两个区间可能有多种交汇的可能:不相交、相交但不包含,完全包含。我们做如下区间处理: +1. 计算相交的区间,记做cross:```{max(a,c), min(b,d)}```,注意该区间可能为空。 +2. 计算属于区间ab但不属于cd的区域,记做A + ```cpp + if (a<=c-1) A.push_back({a, c-1}); + if (d+1<=b) A.push_back({d+1, b}); + ``` +3. 计算属于区间cd但不属于ab的区域,记做B + ```cpp + if (c<=a-1) B.push_back({c,a-1}); + if (b+1<=d) B.push_back({b+1,d}); + ``` +4. 计算要么属于ab要么属于cd的区间,记做Union,其实就是以上cross, A, B里面区间的合并。 + +判断合法的条件有如下: +1. 在Union之外的区域,必须要求s和t每个字符都相等。这里有一个巧妙的判定方法。我们构造前缀数组diff[i]表示前i个位置里有多少个s与t字母不相同的位置。于是我们只需要查验Union里面的、不同字母的位置个数,是否等于diff[m]即可。 +2. 对于A区间,s里面的字符调整后必须和t里面的字符完全一致。所以我们先将s在区间ab的字符频次放入count1,再消耗掉t在区间A里的字符频次。要求不能出现负数。 +3. 对于B区间,t里面的字符调整后必须和s里面的字符完全一致。所以我们先将t在区间cd的字符频次放入count2,再消耗掉s在区间B里的字符频次。要求不能出现负数。 +4. 最后,剩余的count1和count2代表了cross部分,两者的字母频次必须完全一致。 + diff --git a/Greedy/3012.Minimize-Length-of-Array-Using-Operations/3012.Minimize-Length-of-Array-Using-Operations.cpp b/Greedy/3012.Minimize-Length-of-Array-Using-Operations/3012.Minimize-Length-of-Array-Using-Operations.cpp new file mode 100644 index 000000000..a3b8e6b96 --- /dev/null +++ b/Greedy/3012.Minimize-Length-of-Array-Using-Operations/3012.Minimize-Length-of-Array-Using-Operations.cpp @@ -0,0 +1,15 @@ +class Solution { +public: + int minimumArrayLength(vector& nums) + { + sort(nums.begin(), nums.end()); + for (int i=1; i& nums, int k) + { + int n = nums.size(); + vectorarr(n); + int ret = 0; + for (int t=29; t>=0; t--) + { + for (int i=0; i>t)&1); + + if (checkOK(arr, k)) + ret = ret*2+0; + else + { + ret = ret*2+1; + for (int i=0; i> 1); + } + } + return ret; + } + + bool checkOK(vector&arr, int k) + { + int n = arr.size(); + int count = 0; + int i = 0; + while (i>& points) + { + sort(points.begin(), points.end(), [](vector&a, vector&b){ + if (a[0]==b[0]) return a[1]>b[1]; + else return a[0] upper) continue; + if (points[j][1] > lower && points[j][1] <= upper) + ret++; + lower = max(lower, points[j][1]); + } + } + + return ret; + } +}; diff --git a/Greedy/3027.Find-the-Number-of-Ways-to-Place-People-II/Readme.md b/Greedy/3027.Find-the-Number-of-Ways-to-Place-People-II/Readme.md new file mode 100644 index 000000000..feb2bb3cf --- /dev/null +++ b/Greedy/3027.Find-the-Number-of-Ways-to-Place-People-II/Readme.md @@ -0,0 +1,5 @@ +### 3027.Find-the-Number-of-Ways-to-Place-People-II + +此题允许n^2的时间复杂度,可以暴力枚举所有符合条件的{左上角、右下角}配对,判断是否是合法的解。 + +对于二维坐标的点,有两个方向上的自由度,同时考虑他们之间的包含关系肯定复杂,我们必然会尝试将他们先按照一个维度排序,比如说x轴。对于两个点A和B在x轴上是递增的,他们能够配对成功的条件就是:x轴上A与B之间的点,不能在y轴上也出现在A与B之间。换句话说,如果横坐标位于A与B之间的所有点的y坐标上限是P(排除那些高于A的点),那么B能与A配对的条件就是:B的y周坐标必须大于P。随着B在x轴上离A越远,那么这个上限值其实是单调递增的。由此从左到右扫一遍所有的点,不断更新上限P,就能判断出每个点是否可以与A配对。 diff --git a/Greedy/3139.Minimum-Cost-to-Equalize-Array/3139.Minimum-Cost-to-Equalize-Array.cpp b/Greedy/3139.Minimum-Cost-to-Equalize-Array/3139.Minimum-Cost-to-Equalize-Array.cpp new file mode 100644 index 000000000..3e8fea5c5 --- /dev/null +++ b/Greedy/3139.Minimum-Cost-to-Equalize-Array/3139.Minimum-Cost-to-Equalize-Array.cpp @@ -0,0 +1,41 @@ +using LL = long long; +LL M = 1e9+7; +class Solution { +public: + int minCostToEqualizeArray(vector& nums, int cost1, int cost2) + { + int n = nums.size(); + sort(nums.begin(), nums.end()); + + if (n<=2) + { + return (LL)(nums[n-1]-nums[0])*cost1%M; + } + + LL total = accumulate(nums.begin(), nums.end(), 0LL); + + cost2 = min(cost1*2, cost2); + int m = nums.back(); + LL ret = LLONG_MAX; + for (int limit = m; limit <= 2*m; limit++) + { + LL diff0 = limit - nums[0]; + LL diff_all = (LL)limit*n - total; + + LL ans; + if (diff0 <= diff_all/2) + { + ans = diff_all/2*cost2 + (diff_all%2==1?cost1:0); + } + else + { + ans = (diff_all - diff0)*cost2 + (diff0 - (diff_all - diff0))*cost1; + } + + ret = min(ret, ans); + } + + return ret%M; + + } +}; diff --git a/Greedy/3139.Minimum-Cost-to-Equalize-Array/Readme.md b/Greedy/3139.Minimum-Cost-to-Equalize-Array/Readme.md new file mode 100644 index 000000000..aadc62ecb --- /dev/null +++ b/Greedy/3139.Minimum-Cost-to-Equalize-Array/Readme.md @@ -0,0 +1,15 @@ +### 3139.Minimum-Cost-to-Equalize-Array + +首先,我们令cost2=min(cost1*2, cost2),保证使用第二种操作不会比第一种操作更亏。这样变换之后,我们知道应该尽量使用cost2更合算。 + +其次要注意,并不是操作的次数越少,付出的代价就越少。比如说nums=[1,4,4],我们可以使用三次cost1,使得所有元素最终变为4;也可以使用六次cost2,使得最终所有元素变成7. 哪种方案更好,取决于cost1和cost2的大小关系。因此本题的最优策略,不一定是将所有的元素都变成max(nums),而是可能一个更大的数字,我们记做limit。假如limit是已知量,我们如何计算需要用的最小代价呢? + +既然我们需要将所有元素都增至limit,那么将数组排序后,每个元素离目标还差{diff0, diff1, diff2, ...., 0}。记所有的diff之和为`diff_all`。我们容易理解这样一个结论:如果在所有的diff里面存在一个超过`diff_all`一半的绝对多数,那它必然就是diff0. 我们为了尽可能多地利用第二种操作,必然是每增加nums0的同时搭配一个增加其他的元素。最终我们会进行cost2的操作共`diff_all-diff0`次。剩下来还没增至limit的只是nums0,还差`limit-(diff_all-diff0)`,这就需要用cost1来实现。于是总共的代价为`(diff_all-diff0)*cost2 + (limit-(diff_all-diff0))*cost1`. + +相反,如果{diff}里面不存在一个绝对多数,根据Boyer-Moore Majority Voting的原理,那么我们必然可以持续找到一对pair进行增一操作,且它们来自两个不同的元素。最终直至所有元素都增至limit,或者只差一次增一操作故无法找到pair了。这种情况下,我们的cost2操作了`diff_all/2`次,并且根据diff_all的奇偶性,可能再增加一次cost1的操作。 + +以上我们知道当limit已知时,如何计算最小代价。但是limit该如何确定呢?我们发现,随着limit的增长,{diff}数组整体变大,越来越不可能出现第一种情况。而一旦{diff}进入第二种情况时,就已经将cost2用到了极致(即只会用最多一次cost1),再增长limit就没有意义。那什么时候{diff}会进入第二种情况呢?显然,至少当limit变成`2*max(nums)`,{diff}里面肯定不会出现绝对多数了(当n>=3时):这是因为diff0小于2m,而其他每个diff都大于m。 + +所以我们只需要穷举limit的范围`[m, 2m]`,对于给定的limit我们计算最小代价,全局再取最小即可。考虑到m是1e6,这是可以暴力实现的。 + + diff --git a/Greedy/3219.Minimum-Cost-for-Cutting-Cake-II/3219.Minimum-Cost-for-Cutting-Cake-II.cpp b/Greedy/3219.Minimum-Cost-for-Cutting-Cake-II/3219.Minimum-Cost-for-Cutting-Cake-II.cpp new file mode 100644 index 000000000..98acd52a6 --- /dev/null +++ b/Greedy/3219.Minimum-Cost-for-Cutting-Cake-II/3219.Minimum-Cost-for-Cutting-Cake-II.cpp @@ -0,0 +1,37 @@ +using LL = long long; +class Solution { +public: + long long minimumCost(int m, int n, vector& h, vector& v) + { + sort(h.rbegin(), h.rend()); + sort(v.rbegin(), v.rend()); + + LL i=0, j=0; + LL ret = 0; + while (iv[j]) + { + ret += h[i]*(j+1); + i++; + } + else + { + ret += v[j]*(i+1); + j++; + } + } + while (i costV`的时候,方案1更优。 + +综上,我们可以得到一个推测的结论:我们只需要将所有横切与纵切的位置按照cost从大到小排序、依次切割并且“一切到底”,这样得到的总代价最小。 + +上述结论有一个重要的前提,就是每刀都是一切到底。那么是否存在不“一切到底”反而更优的情况呢?考虑下面 +``` + _________C____ +A |_____|B | + | | | + |_____|______| + D +``` +假设按照排序后的cost,第一刀是竖切,第二刀是横切AB。那么是否该将右半边也横切掉呢?我们的顾虑是,如果在右半边存在一个竖切的位置CD,那么AB一切到底会引入两倍的竖切CD。而如果先切CD,再切AB的延长线,那么我们引入的代价是CD+AB*2. 事实上,从之前的排序过程中我们已经知道AB的代价大于CD,所以后者的方案是不如前者的。因此,我们在按照cost顺次执行切割的时候都会“一切到底”。 diff --git a/Greedy/3394.Check-if-Grid-can-be-Cut-into-Sections/3394.Check-if-Grid-can-be-Cut-into-Sections.cpp b/Greedy/3394.Check-if-Grid-can-be-Cut-into-Sections/3394.Check-if-Grid-can-be-Cut-into-Sections.cpp new file mode 100644 index 000000000..87f6a0874 --- /dev/null +++ b/Greedy/3394.Check-if-Grid-can-be-Cut-into-Sections/3394.Check-if-Grid-can-be-Cut-into-Sections.cpp @@ -0,0 +1,37 @@ +class Solution { +public: + bool checkValidCuts(int n, vector>& arr) + { + vector>widths; + vector>heights; + for (int i=0; i>&arr) + { + sort(arr.begin(),arr.end()); + + int j=0; + int count = 0; + for (int i=0; i=3) return true; + } + return false; + } +}; diff --git a/Greedy/3394.Check-if-Grid-can-be-Cut-into-Sections/Readme.md b/Greedy/3394.Check-if-Grid-can-be-Cut-into-Sections/Readme.md new file mode 100644 index 000000000..85ca694d5 --- /dev/null +++ b/Greedy/3394.Check-if-Grid-can-be-Cut-into-Sections/Readme.md @@ -0,0 +1,7 @@ +### 3394.Check-if-Grid-can-be-Cut-into-Sections + +本题的本质就是在横纵方向上,分别查验是否存在至少三个non-overlapping intervals. + +数non-overlapping intervals的经典算法就是将所有区间按照首端点排序。将第一个区间的未端点记作far,然后依次查看后续区间的首端点是否小于等于far,是的话就说明必然存在overlap。同时,每查看一个后续区间,我们都用该区间的尾端点区更新far值(取max)。直至下一个区间的首端点在far之后停止。此时我们之前考察的所有区间,必然都是存在partial overlap的,但是他们merge后的整体不会与其他区间再有重合。 + +之后我们再从下一个区间开始,重复上面的操作,找到另一个存在overlap的区间群。依次类推。 diff --git a/Greedy/3413.Maximum-Coins-From-K-Consecutive-Bags/3413.Maximum-Coins-From-K-Consecutive-Bags.cpp b/Greedy/3413.Maximum-Coins-From-K-Consecutive-Bags/3413.Maximum-Coins-From-K-Consecutive-Bags.cpp new file mode 100644 index 000000000..c94294323 --- /dev/null +++ b/Greedy/3413.Maximum-Coins-From-K-Consecutive-Bags/3413.Maximum-Coins-From-K-Consecutive-Bags.cpp @@ -0,0 +1,46 @@ +using LL = long long; +class Solution { +public: + long long maximumCoins(vector>& coins, int k) + { + LL ret = 0; + sort(coins.begin(), coins.end()); + ret = max(ret, helper(coins, k)); + + for (auto& coin: coins) + { + int a = coin[0], b = coin[1]; + coin[0] = -b; + coin[1] = -a; + } + sort(coins.begin(), coins.end()); + ret = max(ret, helper(coins, k)); + + return ret; + } + + LL helper(vector>& coins, int k) + { + int n = coins.size(); + int j = 0; + LL sum = 0; + LL ret = 0; + for (int i=0; i= coins[j][1]) + { + sum += (LL)(coins[j][1]-coins[j][0]+1)*coins[j][2]; + j++; + } + LL extra = 0; + if (j= coins[j][0]) + { + extra += (LL)(end - coins[j][0] + 1) * coins[j][2]; + } + ret = max(ret, sum + extra); + sum -= (LL)(coins[i][1]-coins[i][0]+1)*coins[i][2]; + } + return ret; + } +}; diff --git a/Greedy/3413.Maximum-Coins-From-K-Consecutive-Bags/Readme.md b/Greedy/3413.Maximum-Coins-From-K-Consecutive-Bags/Readme.md new file mode 100644 index 000000000..7c5469a3d --- /dev/null +++ b/Greedy/3413.Maximum-Coins-From-K-Consecutive-Bags/Readme.md @@ -0,0 +1,11 @@ +### 3413.Maximum-Coins-From-K-Consecutive-Bags + +此题和2271.Maximum-White-Tiles-Covered-by-a-Carpet的思路类似。 + +对于长度为k的跨度,如果其一个端点没有落在任何区间,那么显然是不划算的。我们必然有更优的策略:平移这段跨度直至一端接触到某个区间的边缘,这样可以在另一端覆盖到更多的有效区域得到更大的价值。注意“某个区间的端点”可以是左端点,也可以是右端点。 + +再考虑,对于长度为k的跨度,如果其两个端点分别都落在了区间A和区间B内,那么同样也是不划算的。只要区间A和B的价值密度不一样,那么我们必然能找到更优的解,即朝价值密度更高的那个方向平移即可。平移的最终结果是:完全离开价值密度低的区间(如果另一端依然在价值密度高的区间的话),或者触碰到价值密度高的区间的边缘。 + +所以上述的结论就是,最优解的情况,必然发生在所选跨度恰好触碰在某个区间边缘的时候。所以我们分两种情况。首先,从左往右遍历每个区间的左边缘,当做是所选跨度k的左边界,然后可以确定右边界的位置,这样就计算总价值;随着对左边界的挨个尝试,右边界也是单调移动的。所以这是一个典型的双指针。然后,反过来,从右往左遍历每个区间的右边缘,当做是所选跨度的右边界,然后可以确定左边界的位置,这样就计算总价值;随着对右边界的挨个尝试,左边界也是单调移动的。 + +对于第二次遍历,我们可以重复利用第一次遍历的函数。只要将每个区间的左右端点完全颠倒即可。即原区间范围是[a,b],那么我们构造一个新的区间范围[-b,-a]。这样我们依然可以重复利用从左往右遍历的代码,本质上实现了从右往左的遍历。 diff --git a/Greedy/3458.Select-K-Disjoint-Special-Substrings/3458.Select-K-Disjoint-Special-Substrings.cpp b/Greedy/3458.Select-K-Disjoint-Special-Substrings/3458.Select-K-Disjoint-Special-Substrings.cpp new file mode 100644 index 000000000..e452ea401 --- /dev/null +++ b/Greedy/3458.Select-K-Disjoint-Special-Substrings/3458.Select-K-Disjoint-Special-Substrings.cpp @@ -0,0 +1,55 @@ +class Solution { +public: + bool maxSubstringLength(string s, int k) + { + int n = s.size(); + vector>pos(26); + for (int i=0; i>intervals; + for (int letter=0; letter<26; letter++) + { + if (pos[letter].empty()) continue; + int start = pos[letter][0]; + int i = start; + int far = pos[letter].back(); + + bool flag = true; + while (i<=far) + { + far = max(far, pos[s[i]-'a'].back()); + if (pos[s[i]-'a'][0]=k; + } + + + int helper(vector> &intervals) { + sort(intervals.begin(), intervals.end(), [](pair a, pair b) { + return a.second < b.second; + }); + + int count = 0; + int far = INT_MIN; + + for (auto &interval : intervals) + { + if (interval.first > far) { + count++; + far = interval.second; + } + } + return count; + } +}; diff --git a/Greedy/3458.Select-K-Disjoint-Special-Substrings/3458.Select-K-Disjoint-Special-Substrings_v2.cpp b/Greedy/3458.Select-K-Disjoint-Special-Substrings/3458.Select-K-Disjoint-Special-Substrings_v2.cpp new file mode 100644 index 000000000..1cecdda41 --- /dev/null +++ b/Greedy/3458.Select-K-Disjoint-Special-Substrings/3458.Select-K-Disjoint-Special-Substrings_v2.cpp @@ -0,0 +1,60 @@ +class Solution { +public: + bool maxSubstringLength(string s, int k) + { + int n = s.size(); + vector>pos(26); + for (int i=0; i>intervals; + for (int letter=0; letter<26; letter++) + { + if (pos[letter].empty()) continue; + int start = pos[letter][0]; + int i = start; + int far = pos[letter].back(); + + bool flag = true; + while (i<=far) + { + far = max(far, pos[s[i]-'a'].back()); + if (pos[s[i]-'a'][0]=k; + } + + bool contains(paira, pairb) + { + return a.firstb.second; + } + + int helper(vector> &intervals) { + int n = intervals.size(); + vectorcheck(n, 1); + for (int i=0; i>& queries) + { + long long ret = 0; + for (auto&q : queries) + { + int a = q[0], b = q[1]; + ret += helper(a,b); + } + return ret; + } +}; diff --git a/Greedy/3495.Minimum-Operations-to-Make-Array-Elements-Zero/Readme.md b/Greedy/3495.Minimum-Operations-to-Make-Array-Elements-Zero/Readme.md new file mode 100644 index 000000000..ca3b7c41c --- /dev/null +++ b/Greedy/3495.Minimum-Operations-to-Make-Array-Elements-Zero/Readme.md @@ -0,0 +1,12 @@ +### 3495.Minimum-Operations-to-Make-Array-Elements-Zero + +对于[a,b]区间,理论上我们可以计算出将每个元素变为0需要有多少次“/4”的操作,也就是将其转化为一个递增数列p=[x1,x2,x3,...,xn],每个元素代表该数需要x次“/4”才能变成0。我们将该数列的总和记作M。根据题意,我们每个回合可以在p里找两个不同的元素减1,问至少需要多少回合将所有元素置零?直观上,我们只要贪心地做到每回合找到两处非零数,那么总回合大致就是M的一半。那么我们是否一定能在每个回合针对两个不同的数进行操作呢?事实上,只要不存在最大的x超过总数的一半,我们总能实现这样的配对。这里简单证明一下。 + +首先必要性。如果xn>M/2,那么即使其他所有元素每次都找xn配对,削减之后xn仍有剩余,接下来就无法在一个回合里做两次削减。根据逆否原理,要实现每个回合能削减两个数,必然要xn<=M/2. + +其次充分性。如果xn<=M/2,我们可以找到一个分解点xi恰好将M分成相等两半,将[x1..xi]与[xi...xn]放入两个抽屉,每次必然能各从每个抽屉里取出不一样的两个元素。特别地,对于xi而言,因为xi必然也小于M/2(回合总数),所以我们必然可以构造出每个回合的配对不同时出现xi。 + +由此,本题的基本思路是:计算[x1,x2,x3,...,xn]的总和M,以及最大元素需要的操作次数B。如果B小于等于M的一半,那么答案就是(M+1)/2,其中+1是为了处理M为奇数的情况。反之,我们令A=M-B表示能凑成配对的回合,答案就是A+(B-A). + +接下来我们思考如何求M。首先[a,b]里所有的元素都能至少除4一次。其次[4,b]里的所有的元素都至少能再做一次除4,所以我们需要判断[4,b]与[a,b]的交集部分。接下来[16,b]里的所有的元素都至少能再做一次除4,同里我们需要判断[16,b]与[a,b]的交集部分... 依次类推,我们维护一个起始点x=1,每次x都不断自乘4,M就可以增加max(x,a)到b这部分区间的元素个数。这样我们就统计了[a,b]里面所有的元素总共可以做几次除4的操作。 + diff --git a/Greedy/3551.Minimum-Swaps-to-Sort-by-Digit-Sum/3551.Minimum-Swaps-to-Sort-by-Digit-Sum.cpp b/Greedy/3551.Minimum-Swaps-to-Sort-by-Digit-Sum/3551.Minimum-Swaps-to-Sort-by-Digit-Sum.cpp new file mode 100644 index 000000000..55f83dff7 --- /dev/null +++ b/Greedy/3551.Minimum-Swaps-to-Sort-by-Digit-Sum/3551.Minimum-Swaps-to-Sort-by-Digit-Sum.cpp @@ -0,0 +1,51 @@ +class Solution { +public: + int helper(vector& A, vector& B) + { + int n = A.size(); + unordered_mapexpected; + for (int i=0; i& nums) + { + vector>arr; + int n = nums.size(); + for (int i=0; i0) + { + sum += x%10; + x/=10; + } + arr.push_back({sum, nums[i], i}); + } + + sort(arr.begin(), arr.end()); + + vectorA(n); + vectorB(n); + for (int i=0; i>& A, vector>& B) { + int n = A.size(); + int ret = INT_MAX; + int minEnd = INT_MAX; + for (int i=0; i=minEnd) + ret = min(ret, B[i].first+B[i].second); + else + ret = min(ret, minEnd+B[i].second); + } + return ret; + } + + int earliestFinishTime(vector& landStartTime, vector& landDuration, vector& waterStartTime, vector& waterDuration) { + vector>A; + vector>B; + for (int i=0; i& cards, char x) { + unordered_mapMap1, Map2; + int count3 = 0; + for (string s: cards) { + if (s[0]==x && s[1]!=x) + Map1[s]++; + else if (s[0]!=x && s[1]==x) + Map2[s]++; + else if (s[0]==x && s[1]==x) + count3++; + } + vectornums1; + for (auto [k,v]:Map1) nums1.push_back(v); + vectornums2; + for (auto [k,v]:Map2) nums2.push_back(v); + + int ret = 0; + for (int i=0; i<=count3; i++) + ret = max(ret, solve(nums1, nums2, i,count3)); + + return ret; + } + + int solve(vectornums1, vectornums2, int x, int count3) { + + nums1.push_back(x); + nums2.push_back(count3-x); + sort(nums1.begin(), nums1.end()); + sort(nums2.begin(), nums2.end()); + + return helper(nums1) + helper(nums2); + } + + int helper(vectornums) { + if (nums.empty()) return 0; + int total = accumulate(nums.begin(), nums.end(), 0); + int mx = nums.back(); + if (mx <= total -mx) + return total/2; + else + return total-mx; + } + +}; diff --git a/Greedy/3664.Two-Letter-Card-Game/Readme.md b/Greedy/3664.Two-Letter-Card-Game/Readme.md new file mode 100644 index 000000000..1fcbc5e2e --- /dev/null +++ b/Greedy/3664.Two-Letter-Card-Game/Readme.md @@ -0,0 +1,13 @@ +### 3664.Two-Letter-Card-Game + +根据题意,我们可以将所有字符串分为三类,“x.”,“.x”和“xx”,其中"."可以是除了x以外的任何其他字符。 + +对于第一类字符,我们可以统计每种字符串的频次,得到一个nums1=[x1,x2,...,xi]的数组。根据题意,在该数组内,我们每次操作可以将两个不同的元素同时减1. + +同理,对于第二类字符,我们可以统计每种字符串的频次,得到一个nums2=[y1,y2,...,yi]的数组。同样,根据题意,在该数组内部,我们每次操作可以将两个不同的元素同时减1. + +对于第三类字符,因为就是“xx”,我们直接统计它的频次count3. 根据题意,我们可以取任意第一类字符的频次和count3同时减1,也可以取任意第二类字符的频次和count3同时减1。 + +本题问可以最多做多少次这样的“同时减1”的操作。 + +因为第一类和第二类字符不能相消,本题的关键其实就在于对count3的分配,究竟给多少个到第一类字符里进行相消,给多少个到第二类字符里进行相消?事实上这里没有明确的最优方法。我们能做的就是枚举。假设我们分x个"xx"到第一类字符,剩下的y=count3-y个"xx"到第二类字符,那么我们就转化为分别求[x1,x2,...,xi,x]和[y1,y2,...,yi,y]两个数组里,各自最多能做多少次成对的减1消除。这就转化为了常规的majority voting的模版题。 diff --git a/Greedy/3696.Maximum-Distance-Between-Unequal-Words-in-Array-I/3696.Maximum-Distance-Between-Unequal-Words-in-Array-I.cpp b/Greedy/3696.Maximum-Distance-Between-Unequal-Words-in-Array-I/3696.Maximum-Distance-Between-Unequal-Words-in-Array-I.cpp new file mode 100644 index 000000000..18a620c2e --- /dev/null +++ b/Greedy/3696.Maximum-Distance-Between-Unequal-Words-in-Array-I/3696.Maximum-Distance-Between-Unequal-Words-in-Array-I.cpp @@ -0,0 +1,21 @@ +class Solution { +public: + int maxDistance(vector& words) { + int n = words.size(); + if (words[0]!=words.back()) return n; + + int ret = 0; + for (int i=0; i=0; i--) { + if (words[i]!=words.back()) + ret = max(ret, n-1-i+1); + } + + return ret; + + } +}; diff --git a/Greedy/3696.Maximum-Distance-Between-Unequal-Words-in-Array-I/Readme.md b/Greedy/3696.Maximum-Distance-Between-Unequal-Words-in-Array-I/Readme.md new file mode 100644 index 000000000..1afbed9bc --- /dev/null +++ b/Greedy/3696.Maximum-Distance-Between-Unequal-Words-in-Array-I/Readme.md @@ -0,0 +1,3 @@ +### 3696.Maximum-Distance-Between-Unequal-Words-in-Array-I + +此题有非常巧妙的贪心法。如果首尾元素不同,那么答案就是n。如果首尾元素不同,那么答案必然是在以下两种可能之中:首元素与某个与其不相同的元素;或者尾元素与某个与其不相同的元素。所以两遍one pass即可。 diff --git a/Greedy/3785.Minimum-Swaps-to-Avoid-Forbidden-Values/3785.Minimum-Swaps-to-Avoid-Forbidden-Values.cpp b/Greedy/3785.Minimum-Swaps-to-Avoid-Forbidden-Values/3785.Minimum-Swaps-to-Avoid-Forbidden-Values.cpp new file mode 100644 index 000000000..57c181ecd --- /dev/null +++ b/Greedy/3785.Minimum-Swaps-to-Avoid-Forbidden-Values/3785.Minimum-Swaps-to-Avoid-Forbidden-Values.cpp @@ -0,0 +1,36 @@ +class Solution { +public: + int minSwaps(vector& nums, vector& forbidden) { + int n = nums.size(); + unordered_mapcount; + unordered_mapforbid; + for (int i=0; i n - forbid[k]) return -1; + } + + unordered_mapMap; + for (int i=0; iarr; + for (auto [k,v]: Map) + arr.push_back(v); + if (arr.empty()) return 0; + + int total = accumulate(arr.begin(), arr.end(), 0); + sort(arr.begin(), arr.end()); + int mx = arr.back(); + + if (mx <= total-mx) + return (total+1)/2; + else + return mx; + } +}; diff --git a/Greedy/3785.Minimum-Swaps-to-Avoid-Forbidden-Values/Readme.md b/Greedy/3785.Minimum-Swaps-to-Avoid-Forbidden-Values/Readme.md new file mode 100644 index 000000000..7f335a0d3 --- /dev/null +++ b/Greedy/3785.Minimum-Swaps-to-Avoid-Forbidden-Values/Readme.md @@ -0,0 +1,7 @@ +### 3785.Minimum-Swaps-to-Avoid-Forbidden-Values + +首先考虑是否一定有解。我们比较容易想到,对于某个数值而言,如果forbidden里有x个禁放位置,但是nums有多于n-x个这样数值的元素。那么我们如何摆放,都必然会有元素落入禁放位置。反之,该数值的元素必然可以在非禁放位置妥善放置。 + +在排除掉无解的情况后,我们会想到最直观的贪心决策:我们只需要将两个处在违禁位置、且数值不同的元素交换,就可以一箭双雕,实现最高效的消除操作。这就类似于majority voting的模版题。具体的做法是:遍历每个位置,如果该位置不合规则(即nums[i]==forbidden[i]),我们就按数值统计频次。最终得到数组arr=[x1,x2,...,xi]表示每种数值有多少个违禁位置。每一次前述“一箭双雕”的swap,必然能同时解决两处的违禁问题,相当于在这个数组里找到一对pair都进行减1操作。 + +根据majority voting的原理,如果arr数组里没有超过半数的绝对多数元素,那么总可以将不同元素配对并消除。故总操作数是(total+1)/2. 其中total是arr的元素总和,+1是为了处理total为奇数的情况。另一种情况:如果存在绝对多数元素的频次mx大于total的半数,那么我们必须做满mx次swap才能将绝对多数的元素消除干净(其中前total-mx次可以与非绝对多数元素配对来实现非绝对多数元素的消除)。 diff --git a/Greedy/3800.Minimum-Cost-to-Make-Two-Binary-Strings-Equal/3800.Minimum-Cost-to-Make-Two-Binary-Strings-Equal.cpp b/Greedy/3800.Minimum-Cost-to-Make-Two-Binary-Strings-Equal/3800.Minimum-Cost-to-Make-Two-Binary-Strings-Equal.cpp new file mode 100644 index 000000000..db14fe022 --- /dev/null +++ b/Greedy/3800.Minimum-Cost-to-Make-Two-Binary-Strings-Equal/3800.Minimum-Cost-to-Make-Two-Binary-Strings-Equal.cpp @@ -0,0 +1,27 @@ +using ll = long long; +class Solution { +public: + long long minimumCost(string s, string t, int flipCost, int swapCost, int crossCost) { + int n = s.size(); + ll c01 = 0, c10 = 0; + for (int i=0; i& nums, int k, int m) { + int n = nums.size(); + int ret = 0; + for (int b=30; b>=0; b--) { + int target = ret | (1<costs; + for (int i=0; i=0; p--) { + if (((target>>p)&1) && (((num>>p)&1)==0)) { + long long mask = (1LL<<(p+1)) - 1; + long long r = num & mask; + delta = (target & mask)-(num & mask); + break; + } + } + costs.push_back(delta); + } + + sort(costs.begin(), costs.end()); + long long sum = 0 ; + for (int i=0; i k) break; + } + if (sum <= k ) { + ret = target; + } + } + return ret; + } +}; diff --git a/Greedy/3806.Maximum-Bitwise-AND-After-Increment-Operations/Readme.md b/Greedy/3806.Maximum-Bitwise-AND-After-Increment-Operations/Readme.md new file mode 100644 index 000000000..7e52aa848 --- /dev/null +++ b/Greedy/3806.Maximum-Bitwise-AND-After-Increment-Operations/Readme.md @@ -0,0 +1,12 @@ +### 3806.Maximum-Bitwise-AND-After-Increment-Operations + +因为bitwise AND的性质是数越多结果反而越小。要想最终结果最大,显然需要在尽量高的bit位置上,令所有数组元素在该处的bit值都是1. + +于是我们就有显然的贪心策略:优先探索能否把所有元素的最高(即第30号)bit位都变成1(注意:整型的第31号bit位是符号位),也就是将“100...0”定为目标值target。我们可以计算将所有元素都变成target的超集(即使得`target & num == target`)所需要的最少操作数,如果成功,那么实现该效果的所有操作是必须保留的。因为不做这些操作,bitwise AND的第30号位就不是1,结果不可能更优。 + +接下来在此基础上,我们自然就会考察下一个位置:能否把所有元素的第29号bit位都变成1。如果前一步的尝试是成功的,此时就将“110...0”定为目标值;如果前一步不能实现,此时就将“010...0”定为目标值。同样,我们可以计算出所有元素变成该target的超集所需要的最少操作数,以及是否能实现。 + +以此类推,我们可以找到最优且可行的target,使得所有数组元素可以在指定的操作范围内,都变成target的超集。自然最终的bitwise AND的结果也就是target。 + +想到这里,我们需要处理的一个子问题就是,对于一个数num,需要最少增加多少,才能使得`target & num == target`?我们只需要从最高位往低逐位找到第一个位置p,使得target的第p位是1,但是num的第p位是0。此时说明“num的后p位后缀”比“target的后p位后缀”小,二者之间的差值就是我们需要最少的对num的增1操作数量,使得num的bit 1位置与target能够恰好对齐。 + diff --git a/Greedy/386.Lexicographical-Numbers/386.Lexicographical-Numbers.cpp b/Greedy/386.Lexicographical-Numbers/386.Lexicographical-Numbers.cpp index 8f50d6c7c..abbe47eb0 100644 --- a/Greedy/386.Lexicographical-Numbers/386.Lexicographical-Numbers.cpp +++ b/Greedy/386.Lexicographical-Numbers/386.Lexicographical-Numbers.cpp @@ -2,24 +2,25 @@ class Solution { public: vector lexicalOrder(int n) { - int current=1; - vectorresults(n); + vectorrets = {1}; + int i=1; - for (int i=0; in) - current=current/10; - current++; - while (current % 10==0) - current/=10; + i=i*10; } - } - return results; + else + { + while (i+1>n || (i%10==9)) + i = i/10; + i+=1; + } + + rets.push_back(i); + } + + return rets; } }; diff --git a/Greedy/386.Lexicographical-Numbers/Readme.md b/Greedy/386.Lexicographical-Numbers/Readme.md index 39240a944..14fbd992e 100644 --- a/Greedy/386.Lexicographical-Numbers/Readme.md +++ b/Greedy/386.Lexicographical-Numbers/Readme.md @@ -1,12 +1,8 @@ ### 386.Lexicographical-Numbers -研究序列[1,10,11,12,13,2,3,4,5,6,7,8,9],找出字典序的规律。 +对于字典序列的next,核心就是 +1. 尝试往后加0, 否则 +2. 找最低的、加1不需要进位的位置,在该位置加1后,舍弃之后的位置即可。 -规律1:不考虑上限,元素1后面跟什么元素?10, 100 … 也就是不断乘以10。 -规律2:如果99是上限,那么10后面的元素不能是100了,该怎么办?答案是11,也就是加1,这样个位上的数变大了。如果加1导致进位的话,虽然个位数变0,但十位上的数会变大,总之肯定字典序往后移。但此时得到的并不是下一个的目标,因为把其末尾的0去掉会得到字典序相对更前的数。砍掉0之后就可以重复规律1的操作了。 - -规律3:如果上限是19,那么19后面的元素就不能是20了,该怎么办?答案是将19除以10,然后再重复规律2(也就是加1),也就是得到2,之后又可以重复规律1了。 - - -[Leetcode Link](https://leetcode.com/problems/lexicographical-numbers) \ No newline at end of file +[Leetcode Link](https://leetcode.com/problems/lexicographical-numbers) diff --git a/Greedy/3863.Minimum-Operations-to-Sort-a-String/3863.Minimum-Operations-to-Sort-a-String.cpp b/Greedy/3863.Minimum-Operations-to-Sort-a-String/3863.Minimum-Operations-to-Sort-a-String.cpp new file mode 100644 index 000000000..a2c85c945 --- /dev/null +++ b/Greedy/3863.Minimum-Operations-to-Sort-a-String/3863.Minimum-Operations-to-Sort-a-String.cpp @@ -0,0 +1,21 @@ +class Solution { +public: + int minOperations(string s) { + int n = s.size(); + if (n==2 && s[0]>s[1]) return -1; + + string t = s; + sort(t.begin(), t.end()); + if (t==s) return 0; + + { + if (s[0]==t[0]) return 1; + if (s.back()==t.back()) return 1; + } + { + if (s[0]==t.back() && s[0]!=t[n-2] && s.back()==t[0] && s.back()!=t[1]) + return 3; + } + return 2; + } +}; diff --git a/Greedy/3863.Minimum-Operations-to-Sort-a-String/Readme.md b/Greedy/3863.Minimum-Operations-to-Sort-a-String/Readme.md new file mode 100644 index 000000000..e814de898 --- /dev/null +++ b/Greedy/3863.Minimum-Operations-to-Sort-a-String/Readme.md @@ -0,0 +1,14 @@ +### 3863.Minimum-Operations-to-Sort-a-String + +此题有明显的贪心策略,最多三次操作一定能够实现有序:先将[1,n-1]排序,然后将[2,n]排序,此时保证全局的最大值一定已经放在了队尾;最后再将[1,n-1]排序,就保证了全局有序。 + +事实上,如果不必须将首元素搬运至队尾(即首元素不是全局唯一最大值),那么两次操作即可:先做后面[2,n]的排序,再做前面[1,n-1]的排序。这样保证了全局最小值一定能放在队首,并且[2,n]已经是有序的。 + +同理,如果不必须将尾元素搬运至队首(即尾元素不是全局唯一最小值),那么两次操作即可:先做前面[1,n-1]的排序,再做后面[2,n]的排序。这样保证了全局最大值一定能放在队尾,并且[1,n-1]已经是有序的。 + +另外,也有需要更少操作的情况。 + +1. 如果s只有两个元素且是逆序的,那么无解。 +2. 如果s已经有序,那么操作数是0. +3. 如果s的首元素已经是全局最小,那么只需要将[2,n]排序即可;或者反之,如果s的尾元素已经是全局最大,那么只需要将[1,n-1]排序即可。这两种情况只需要1次操作。 + diff --git a/Greedy/3872.Longest-Arithmetic-Sequence-After-Changing-At-Most-One-Element/3872.Longest-Arithmetic-Sequence-After-Changing-At-Most-One-Element.cpp b/Greedy/3872.Longest-Arithmetic-Sequence-After-Changing-At-Most-One-Element/3872.Longest-Arithmetic-Sequence-After-Changing-At-Most-One-Element.cpp new file mode 100644 index 000000000..d1435ad02 --- /dev/null +++ b/Greedy/3872.Longest-Arithmetic-Sequence-After-Changing-At-Most-One-Element/3872.Longest-Arithmetic-Sequence-After-Changing-At-Most-One-Element.cpp @@ -0,0 +1,43 @@ +class Solution { +public: + int longestArithmetic(vector& nums) { + int n = nums.size(); + int ret = 2; + + vectorleft(n, 2); + left[0] = 1; + for (int i=2; iright(n, 2); + right[n-1] = 1; + for (int i=n-3; i>=0; i--) { + int cur = nums[i+1]-nums[i]; + int pre = nums[i+2]-nums[i+1]; + if (cur==pre) + right[i] = right[i+1]+1; + else + right[i] = 2; + ret = max(ret, right[i] + (i!=0)); + } + + for (int i=1; i=0 && (nums[i-1]-nums[i-2])*2==cur) + ans += left[i-1] - 1; + ret = max(ret, ans); + } + + return ret; + } +}; diff --git a/Greedy/3872.Longest-Arithmetic-Sequence-After-Changing-At-Most-One-Element/Readmd.md b/Greedy/3872.Longest-Arithmetic-Sequence-After-Changing-At-Most-One-Element/Readmd.md new file mode 100644 index 000000000..39dc18265 --- /dev/null +++ b/Greedy/3872.Longest-Arithmetic-Sequence-After-Changing-At-Most-One-Element/Readmd.md @@ -0,0 +1,7 @@ +### 3872.Longest-Arithmetic-Sequence-After-Changing-At-Most-One-Element + +我们很容易构造两个数组left和right。left[i]表示从i往左最多能组成多长的连续的等差数列。right[i]表示从i往右最多能组成多长的连续的等差数列。此时再用到题目给的条件:可以再任意改动一个数,所以任意的left[i]+1都可能是解。同理任意的right[i]+1也可能是解。 + +此时再考虑改动i,是否能从左边和右边延长得到等差数列。如果`nums[i+1]-nums[i-1]==2*(nums[i-1]-nums[i-2])`,那么意味着等差数列的长度可以增加left[i]。类似的如果`nums[i+1]-nums[i-1]==2*(nums[i+2]-nums[i+1])`,那么意味着等差数列的长度可以增加right[i]。 + +遍历所有的i,取全局最大值作为答案。 diff --git a/Greedy/3920.Maximize-Fixed-Points-After-Deletions/3920.Maximize-Fixed-Points-After-Deletions.cpp b/Greedy/3920.Maximize-Fixed-Points-After-Deletions/3920.Maximize-Fixed-Points-After-Deletions.cpp new file mode 100644 index 000000000..49a8ec085 --- /dev/null +++ b/Greedy/3920.Maximize-Fixed-Points-After-Deletions/3920.Maximize-Fixed-Points-After-Deletions.cpp @@ -0,0 +1,30 @@ +class Solution { +public: + int LIS_LEN(vector>&arr) { + sort(arr.begin(), arr.end(), [](const auto&x, const auto&y){ + if (x.first!=y.first) + return x.firsty.second; + }); + vectorlis; + for (auto &[a,b]: arr) { + auto iter = upper_bound(lis.begin(), lis.end(), b); + if (iter == lis.end()) + lis.push_back(b); + else + *iter = b; + } + return lis.size(); + } + + int maxFixedPoints(vector& nums) { + int n = nums.size(); + vector>d; + for (int i=0; i=v,那么该元素就有机会左移成为fixed point. 所以我们可以将它们先过滤挑出来。 + +在这些候选元素里面,根据题意,我们需要找这样一个从左往右的子序列:首先,它们的数值必须是递增的,这显然。其次,它们左移到达fixed point所需的位移,也必须是递增的(更确切地说是非递减的)。举个例子,加入有两个元素数值a>& units) { + int m = units.size(); + int n = units[0].size(); + + if (n==1) { + ll ret = 0; + for (int i=0; ia, pairb) + static bool cmp(vector&a, vector&b) { - return a.second>& points) + int findMinArrowShots(vector>& points) { - sort(points.begin(),points.end(),cmp); - - int j=0; - int count=0; - while (jdigits; + while(n>0) + { + digits.push_back(n%10); + n=n/10; + } + int m = digits.size(); + + reverse(digits.begin(), digits.end()); + + int i = m-1; + while (i>=1 && digits[i-1] >= digits[i]) + i--; + if (i==0) return -1; + + i--; + int j = m-1; + while (digits[j] <= digits[i]) + j--; + swap(digits[i], digits[j]); + sort(digits.begin()+i+1, digits.end()); + + long long ret=0; + for (int i=0; iINT_MAX) return -1; + else return ret; + } +}; diff --git a/String/556.Next-Greater-Element-III/Readme.md b/Greedy/556.Next-Greater-Element-III/Readme.md similarity index 92% rename from String/556.Next-Greater-Element-III/Readme.md rename to Greedy/556.Next-Greater-Element-III/Readme.md index 3702936d9..aa3aff20a 100644 --- a/String/556.Next-Greater-Element-III/Readme.md +++ b/Greedy/556.Next-Greater-Element-III/Readme.md @@ -1,8 +1,10 @@ ### 556.Next-Greater-Element-III +此题和`031.next permuation`一模一样 + 首先,从低位到高位找到第一个不满足升序的数字。显然,如果从低位到高位都是升序的话,那么找不到任何可以比这个数字更大的变换了。 假设找到这样的数字在第n+1位(记做k),那么在1\~n这个n个低位数字中找到恰比k大的数字(记做m),交换k和m。于是变换后的第n+1位就这么定下来了(可以分析出这就是最小的改动)。剩下来的第1~n位(记得其中有一个是之前调换过来的k),我们让它们按照降序排列即可。 -[Leetcode Link](https://leetcode.com/problems/next-greater-element-iii) \ No newline at end of file +[Leetcode Link](https://leetcode.com/problems/next-greater-element-iii) diff --git a/Greedy/689.Maximum-Sum-of-3-Non-Overlapping-Subarrays/Readme.md b/Greedy/689.Maximum-Sum-of-3-Non-Overlapping-Subarrays/Readme.md index 6050ccedc..d9d0e136c 100644 --- a/Greedy/689.Maximum-Sum-of-3-Non-Overlapping-Subarrays/Readme.md +++ b/Greedy/689.Maximum-Sum-of-3-Non-Overlapping-Subarrays/Readme.md @@ -2,6 +2,6 @@ 这道题可以用动态规划很方便地求解最大值,但是如果要记录最大值所对应的区间的位置,则会略显麻烦。 -考虑到本题恰好只求三段区间,所以很容易想到three-pass的套路。我们在数组里遍历一段长度为k的滑窗作为中间的区间,假设范围是`[i:i+k-1]`,那么我们只需要求在`[0:i-1]`内最大的长度为k的区间,以及在`[i+1:n-1]`内最大的长度为k的区间。这两个分量都是可以提前计算好的。我们只要在数组上从前往后跑一遍长度为k的滑窗,就可以记录任意前缀里曾经出现过的最大值,记做leftMax[i];同理,在数组上从后往前跑一遍长度为k的滑窗,就可以记录任意后缀里曾经出现过的最大值,记做rightMax[i]。所以我们只要找到全局最大的`leftMax[i-1] + sum[i:i+k-1] + rightMax[i+k]`即可。 +考虑到本题恰好只求三段区间,所以很容易想到three-pass的套路。我们在数组里遍历一段长度为k的滑窗作为中间的区间,假设范围是`[i:i+k-1]`,那么我们只需要求在`[0:i-1]`内最大的长度为k的区间,以及在`[i+k:n-1]`内最大的长度为k的区间。这两个分量都是可以提前计算好的。我们只要在数组上从前往后跑一遍长度为k的滑窗,就可以记录任意前缀里曾经出现过的最大值,记做leftMax[i];同理,在数组上从后往前跑一遍长度为k的滑窗,就可以记录任意后缀里曾经出现过的最大值,记做rightMax[i]。所以我们只要找到全局最大的`leftMax[i-1] + sum[i:i+k-1] + rightMax[i+k]`即可。 除此之外,我们还需要记录下leftMax[i]所对应的最大滑窗的位置,即为leftIdx[i]。这里要注意一个细节,因为题意要求,如果有多个总和相同的解,取index位置最小的解。所以我们从左往右遍历的时候,只有在leftMax大于历史最大值的时候才更新leftIdx,这样在相同的leftMax的时候我们保留的是较小的index。同理,我们在从右往左遍历的时候,当rightMax大于等于历史最大值,就可以更新rightIdx,这样在相同的rightMax的时候我们保留的是较小的index。 diff --git a/Hash/2588.Count-the-Number-of-Beautiful-Subarrays/2588.Count-the-Number-of-Beautiful-Subarrays.cpp b/Hash/2588.Count-the-Number-of-Beautiful-Subarrays/2588.Count-the-Number-of-Beautiful-Subarrays.cpp new file mode 100644 index 000000000..2794ed76c --- /dev/null +++ b/Hash/2588.Count-the-Number-of-Beautiful-Subarrays/2588.Count-the-Number-of-Beautiful-Subarrays.cpp @@ -0,0 +1,25 @@ +class Solution { +public: + long long beautifulSubarrays(vector& nums) + { + unordered_mapMap; + Map[0] = 1; + int state = 0; + long long ret = 0; + for (int i=0; i>k)&1) + ((state>>k)&1); + t = t%2; + state = state - (((state>>k)&1)<& nums) + { + unordered_mapMap; + // Map[state] : how many times of state there have been + Map[0] = 1; + + int state = 0; + LL ret = 0; + for (int i=0; i& nums, int modulo, int k) + { + int n = nums.size(); + int count = 0; + unordered_mapMap; + Map[0]+=1; + LL ret = 0; + + for (int i=0; i& nums, int target) + { + LL total = accumulate(nums.begin(), nums.end(), 0LL); + + int n = nums.size(); + for (int i=0; ipresum(2*n, 0); + presum[0] = nums[0]; + for (int i=1; i<2*n; i++) + presum[i] = presum[i-1] + nums[i]; + + LL ret = INT_MAX/2; + + unordered_mapMap; // presum module, index + Map[0] = -1; + for (int i=0; i<2*n; i++) + { + LL r = ((presum[i] - target) % total + total) % total; + + if (Map.find(r)!=Map.end()) + { + int j = Map[r]; + LL k = ((j==-1? 0: presum[j]) - presum[i] + target) / total; + ret = min(ret, i-j+k*n); + } + + Map[presum[i]%total] = i; + } + + if (ret == INT_MAX/2) return -1; + else return ret; + } +}; diff --git a/Hash/2875.Minimum-Size-Subarray-in-Infinite-Array/Readme.md b/Hash/2875.Minimum-Size-Subarray-in-Infinite-Array/Readme.md new file mode 100644 index 000000000..5ad313659 --- /dev/null +++ b/Hash/2875.Minimum-Size-Subarray-in-Infinite-Array/Readme.md @@ -0,0 +1,12 @@ +### 2875.Minimum-Size-Subarray-in-Infinite-Array + +很显然,任何一个subarray,都可以表示成“nums的某个后缀 + 若干个重复的nums + nums的某个前缀”。 + +我们将nums重复一遍(长度变成2n)之后,上述的subarray sum就是:`nums[i:j] + k * total`. 其中total是nums[0:n-1]之和。[i:j]是在[0:2n-1]上的一个子区间。k是某个整数(可以是0). + +当我们遍历j的位置,考察某个j时,期望 `presum[j]-presum[i-1]+k*total = target`,转换一下就是`presum[i-1] = presum[j] - target + k*total`。显然,为了使这个式子有解,充要条件就是`presum[i-1]`和`presum[j] - target`关于total同余,并且需要iSet = {'a','e','i','o','u' }; + +public: + vectorEratosthenes(int n) + { + vectorq(n+1,0); + vectorprimes; + for (int i=2; i<=sqrt(n); i++) + { + if (q[i]==1) continue; + int j=i*2; + while (j<=n) + { + q[j]=1; + j+=i; + } + } + for (int i=2; i<=n; i++) + { + if (q[i]==0) + primes.push_back(i); + } + return primes; + } + + long long beautifulSubstrings(string s, int k) + { + vectorprimes = Eratosthenes(k); + int m = 1; + for (int p:primes) + { + int count = 0; + while (k%p==0) + { + count++; + k/=p; + } + if (count!=0 && count%2==1) + m *= pow(p, (count+1)/2); + else if (count!=0 && count%2==0) + m *= pow(p, count/2); + } + m*=2; + + int n = s.size(); + s.insert(s.begin(), '#'); + int ret = 0; + + map>Map; + Map[0][0]=1; + + int count = 0; + + for (int i=1; i<=n; i++) + { + if (Set.find(s[i])!=Set.end()) + count++; + else + count--; + + if (Map.find(count)!=Map.end() && Map[count].find(i%m)!=Map[count].end()) + ret += Map[count][i%m]; + + Map[count][i%m]+=1; + } + + return ret; + } +}; diff --git a/Hash/2949.Count-Beautiful-Substrings-II/Readme.md b/Hash/2949.Count-Beautiful-Substrings-II/Readme.md new file mode 100644 index 000000000..71075d0dd --- /dev/null +++ b/Hash/2949.Count-Beautiful-Substrings-II/Readme.md @@ -0,0 +1,11 @@ +### 2949.Count-Beautiful-Substrings-II + +对于求substring的问题,我们很容想到用前缀和之差来解决。显然,对于以i结尾的substring,我们想要找满足条件的起始位置j,需要满足两个条件: +1. [j+1:i]内的元音辅音个数相等 <-> [0:j]的元音辅音个数之差必须等于[0:i]的元音辅音个数之差。 +2. [j+1:i]内的元音辅音个数乘积能被k整除 <-> `[(i-j)/2]^2 % k ==0` <-> `[(i-j)]^2 % 4k ==0` + +对于第一个条件,我们只要根据[0:i]的元音辅音个数之差(假设为d),在hash表中查找之前有多少个前缀串的元音辅音个数之差也是d。 + +对于第二个条件,理论上只要i与j关于sqrt(4k)同余,那么(i-j)就能被sqrt(4k)整除,也就是说(i-j)^2能被4k整除。但是sqrt(4k)可能不是一个整数。所以我们需要将k分解质因数,对于出现奇数次的质因子,显然我们需要再补一个该质因子以便k能被开方。我们将这样“松弛”后的k的开方结果记做m,那么我们只要i与j关于2m同余。就保证[(i-j)]^2能被4k整除。 + +于是本题的思路就是建立包含两个key的hash,来记录每个前缀的两个信息:元音辅音的个数之差,前缀长度关于2m的余数。对于任意的位置i,如果在hash表里能找到两个key都相同的位置j,那么[j+1:i]就是符合要求的substring。 diff --git a/Hash/2950.Number-of-Divisible-Substrings/2950.Number-of-Divisible-Substrings.cpp b/Hash/2950.Number-of-Divisible-Substrings/2950.Number-of-Divisible-Substrings.cpp new file mode 100644 index 000000000..039eda813 --- /dev/null +++ b/Hash/2950.Number-of-Divisible-Substrings/2950.Number-of-Divisible-Substrings.cpp @@ -0,0 +1,29 @@ +class Solution { +public: + int countDivisibleSubstrings(string word) + { + int n = word.size(); + word = "#"+word; + vectorpresum(n+1); + for (int i=1; i<=n; i++) + presum[i] = presum[i-1] + ((word[i]-'a'+1)/3+1); + + map>Map; + for (int m=1; m<=9; m++) + Map[m][0] = 1; + + int ret = 0; + for (int j=1; j<=n; j++) + { + for (int m = 1; m <=9; m++) + { + int key = presum[j] - m*j; + if (Map.find(m)!=Map.end() && Map[m].find(key)!=Map[m].end()) + ret += Map[m][key]; + Map[m][key]+=1; + } + } + + return ret; + } +}; diff --git a/Hash/2950.Number-of-Divisible-Substrings/Readme.md b/Hash/2950.Number-of-Divisible-Substrings/Readme.md new file mode 100644 index 000000000..af0d0833b --- /dev/null +++ b/Hash/2950.Number-of-Divisible-Substrings/Readme.md @@ -0,0 +1,9 @@ +### 2950.Number-of-Divisible-Substrings + +此题有精彩的o(N)解法。 + +对于以j为结尾的substring,我们希望找到位置inums; + for (auto ch: s) + nums.push_back(ch-'0'); + nums.insert(nums.begin(), 0); + + LL ret = 0; + for (int k=1; k<=9; k++) + ret += helper(nums, k); + return ret; + } + + LL helper(vector&nums, int k) + { + vectorcount(k, 0); + vectorcount2(k,0); + LL ret = 0; + + int r = 0; + count[0] = 1; + for (int i=1; i<=n; i++) + { + for (int d=0; d=n) break; + int j = i; + while (j=2) { + unordered_mapfirst; + first[0] = i-1; + int diff = 0; + char c1 = s[i]; + for (int k=i; k, int>Map; + Map[{0,0}] = -1; + int ca=0, cb=0, cc=0; + for (int i=0; i factorial; +public: + vector GetFactorial(LL N) + { + vectorrets(N+1); + rets[0] = 1; + for (int i=1; i<=N; i++) + rets[i] = rets[i-1] * i % M; + return rets; + } + + long long quickPow(long long x, long long N) { + if (N == 0) { + return 1; + } + LL y = quickPow(x, N / 2) % M; + return N % 2 == 0 ? (y * y % M) : (y * y % M * x % M); + } + + LL comb(LL m, LL n) + { + if (n>m) return 0; + LL a = factorial[m]; + LL b = factorial[n] * factorial[m-n] % M; + LL inv_b = quickPow(b, (M-2)); + + return a * inv_b % M; + } + + int countGoodSubsequences(string s) + { + unordered_mapMap; + for (auto ch: s) + Map[ch] += 1; + + vectorcount; + for (auto [k,v]: Map) + count.push_back(v); + + int n = *max_element(count.begin(), count.end()); + + factorial = GetFactorial(n); + + LL ret = 0; + for (int f=1; f<=n; f++) + { + LL temp = 1; + for (int c: count) + temp = temp * (comb(c, f)+1) % M; + ret = (ret + temp -1) % M; + } + return ret; + } +}; diff --git a/Math/2539.Count-the-Number-of-Good-Subsequences/Readme.md b/Math/2539.Count-the-Number-of-Good-Subsequences/Readme.md new file mode 100644 index 000000000..5b41d7238 --- /dev/null +++ b/Math/2539.Count-the-Number-of-Good-Subsequences/Readme.md @@ -0,0 +1,25 @@ +### 2539.Count-the-Number-of-Good-Subsequences + +我们遍历频次f从1到n(其中n是所有字母里最高的频次)。对于固定的频次f,我们考察26个字母中,每个字母取f个的组合数comb(c,f),以及不取的决策(即+1),这样就可以保证所取的子序列里每个字母的频次都一致。 +```cpp +for (int f=1; f<=n; f++) +{ + LL temp = 1; + for (int c: count) + temp = temp * (comb(c, f)+1) % M; + ret = (ret + temp -1) % M; +} +``` +注意,对于频次f,我们其实都包括了“全不取”的策略,这相当于空串是不合法的,所以对于每个temp我们都要减一。 + +关于计算组合数,应为要对M取模,我们可以直接用组合数定义和费马小定理来硬算,即 +```cpp +LL comb(LL m, LL n) +{ + if (n>m) return 0; + LL a = factorial[m]; + LL b = factorial[n] * factorial[m-n] % M; + LL inv_b = quickPow(b, (M-2)); + return a * inv_b % M; +} +``` diff --git a/Math/2607.Make-K-Subarray-Sums-Equal/2607.Make-K-Subarray-Sums-Equal_v1.cpp b/Math/2607.Make-K-Subarray-Sums-Equal/2607.Make-K-Subarray-Sums-Equal_v1.cpp new file mode 100644 index 000000000..749347c81 --- /dev/null +++ b/Math/2607.Make-K-Subarray-Sums-Equal/2607.Make-K-Subarray-Sums-Equal_v1.cpp @@ -0,0 +1,34 @@ +using LL = long long; +class Solution { +public: + long long makeSubKSumEqual(vector& arr, int k) + { + int n = arr.size(); + LL ret = 0; + vectorvisited(n); + + for (int i=0; inums; + int j = i; + while (visited[j]==0) + { + visited[j] = 1; + nums.push_back(arr[j]); + j = (j+k)%n; + } + ret += helper(nums); + } + return ret; + } + + LL helper(vector&nums) + { + sort(nums.begin(), nums.end()); + int n = nums.size(); + LL ret = 0; + for (int i=0; i& arr, int k) + { + int n = arr.size(); + LL ret = 0; + vectorvisited(n); + int T = gcd(k, n); + + for (int i=0; inums; + int j = i; + while (j&nums) + { + sort(nums.begin(), nums.end()); + int n = nums.size(); + LL ret = 0; + for (int i=0; i& nums) + { + int n = nums.size(); + int g = nums[0]; + for (int i=0; i 0) + return (n - count); + + count = n; + for (int i=0; i& sick) + { + power[0] = 1; + for (int i=1; i<=n; i++) + power[i] = power[i-1]*2 % mod; + + fact[0] = 1; + for (int i=1; i<=n; i++) + fact[i] = fact[i-1]*i % mod; + + vectorgroups; + for (int i=0; i0) + ret = ret * power[x-1] % mod; + } + + return ret; + } + + LL quickPow(LL x, LL y) + { + LL ret = 1; + LL cur = x; + while (y) + { + if (y & 1) + { + ret = (LL)ret * cur % mod; + } + cur = (LL)cur * cur % mod; + y >>= 1; + } + return ret; + } + + LL inv(LL x) + { + return quickPow(x, mod - 2); + } +}; diff --git a/Math/2954.Count-the-Number-of-Infection-Sequences/Readme.md b/Math/2954.Count-the-Number-of-Infection-Sequences/Readme.md new file mode 100644 index 000000000..f0f783c25 --- /dev/null +++ b/Math/2954.Count-the-Number-of-Infection-Sequences/Readme.md @@ -0,0 +1,15 @@ +### 2954.Count-the-Number-of-Infection-Sequences + +对于两个相邻sick点之间的区间,他们被感染的次序看似很复杂,其实无非就是“感染左端点”和“感染右端点”两个选择里的随机选取。因此任意一个感染序列,都对应了一种LR的二值序列。假设区间内未被感染的点有m个,那么感染过程的序列种类(即排列)就是`2^(m-1)`。为什么是m-1?因为当只剩最后一个未感染点时,“感染左端点”和“感染右端点”这两个选择对应是同一个点。 + +此外需要注意,如果只有单边存在sick的区间(比如第一个区间或者最后一个区间),它的序列种类只有1. + +以上是一个区间的种类数。那么如何计算所有点的总的序列种类呢?假设前述的区间m1,m2,...mk的总数是n,那么这n个点的随机排列是n!种。但是,对于属于某个特定区间的点而言(比如说属于第k个区间的mk个点),它的顺序不应该是完全随机的,随意我们要再除以mk的阶乘抵消这种随机性。但是属于第k区间的点肯定也不是只有一种排列,而是有`2^(mk-1)`种方法(如果是单边存在sick的区间,那就只是1种),故需要再乘以该区间内点的排列数。 + +所以这道题的答案是 +``` +ret = n!; +for (int i=0; i& nums) + { + int n = nums.size(); + sort(nums.begin(), nums.end()); + int m; + if (n%2==1) + m = nums[n/2]; + else + m = (nums[n/2] + nums[n/2-1])/2; + + string a = nextSmallerOrEqual(to_string(m)); + string b = nextGreaterOrEqual(to_string(m)); + + long long ret = LLONG_MAX; + ret = min(ret, check(nums, stoll(a))); + ret = min(ret, check(nums, stoll(b))); + return ret; + } + + long long check(vector& nums, long long p) + { + long long sum = 0; + for (int i=0; i=0; i--) + { + int d = s[i] - '0' - carry; + if (d>=0) + { + s[i] = '0' + d; + carry = 0; + } + else + { + s[i] = '9'; + carry = 1; + } + s[m-1-i] = s[i]; + } + + if (s[0] == '0' && m>1) + return string(m-1, '9'); + else + return s; + } + + string nextGreaterOrEqual(string n) + { + int m = n.size(); + string s = n; + for (int i=0, j=m-1; i<=j; ) + s[j--] = s[i++]; + if (s >= n) return s; + + int carry = 1; + for (int i=(m-1)/2; i>=0; i--) + { + int d = s[i] - '0' + carry; + if (d<=9) + { + s[i] = '0' + d; + carry = 0; + } + else + { + s[i] = '0'; + carry = 1; + } + s[m-1-i] = s[i]; + } + + if (carry == 1) + { + s = string(m+1, '0'); + s[0] = s.back() = '1'; + return s; + } + else + return s; + } +}; diff --git a/Math/2967.Minimum-Cost-to-Make-Array-Equalindromic/Readme.md b/Math/2967.Minimum-Cost-to-Make-Array-Equalindromic/Readme.md new file mode 100644 index 000000000..93062ba42 --- /dev/null +++ b/Math/2967.Minimum-Cost-to-Make-Array-Equalindromic/Readme.md @@ -0,0 +1,5 @@ +### 2967.Minimum-Cost-to-Make-Array-Equalindromic + +根据中位数定理,Make Array Equal的最小代价就是将所有元素变成数组里的中位数(median)。本题中,我们的目标就是找到最接近中位数的回文数。可以借鉴`564. Find the Closest Palindrome`的算法,求得中位数M的next greater palindrome和next smaller palinedrome,然后选取两者较小的代价即可。 + +特别注意,单纯找nearest palinedrome是不对的。next greater palindrome和next smaller palinedrome相比,并不是更接近M就更好,而是与array里元素的分布有关。 diff --git a/Math/2968.Apply-Operations-to-Maximize-Frequency-Score/2968.Apply-Operations-to-Maximize-Frequency-Score_v1.cpp b/Math/2968.Apply-Operations-to-Maximize-Frequency-Score/2968.Apply-Operations-to-Maximize-Frequency-Score_v1.cpp new file mode 100644 index 000000000..70989f56b --- /dev/null +++ b/Math/2968.Apply-Operations-to-Maximize-Frequency-Score/2968.Apply-Operations-to-Maximize-Frequency-Score_v1.cpp @@ -0,0 +1,39 @@ +using LL = long long; +class Solution { +public: + int maxFrequencyScore(vector& nums, long long k) + { + int n = nums.size(); + sort(nums.begin(), nums.end()); + + nums.insert(nums.begin(), 0); + vectorpresum(n+1); + for (int i=1; i<=n; i++) + presum[i] = presum[i-1] + nums[i]; + + int left = 1, right = n; + while (left < right) + { + int mid = right-(right-left)/2; + if (isOK(nums, presum, k, mid)) + left = mid; + else + right = mid-1; + } + return left; + } + + bool isOK(vector&nums, vectorpresum, LL k, int len) + { + int n = nums.size()-1; + for (int i=1; i+len-1<=n; i++) + { + LL m = i+len/2; + LL j = i+len-1; + LL sum1 = nums[m]*(m-i+1) - (presum[m] - presum[i-1]); + LL sum2 = (presum[j] - presum[m-1]) - nums[m]*(j-m+1); + if (sum1+sum2<=k) return true; + } + return false; + } +}; diff --git a/Math/2968.Apply-Operations-to-Maximize-Frequency-Score/2968.Apply-Operations-to-Maximize-Frequency-Score_v2.cpp b/Math/2968.Apply-Operations-to-Maximize-Frequency-Score/2968.Apply-Operations-to-Maximize-Frequency-Score_v2.cpp new file mode 100644 index 000000000..72a3937f3 --- /dev/null +++ b/Math/2968.Apply-Operations-to-Maximize-Frequency-Score/2968.Apply-Operations-to-Maximize-Frequency-Score_v2.cpp @@ -0,0 +1,35 @@ +using LL = long long; +class Solution { +public: + int maxFrequencyScore(vector& nums, long long k) + { + int n = nums.size(); + sort(nums.begin(), nums.end()); + + nums.insert(nums.begin(), 0); + vectorpresum(n+1); + for (int i=1; i<=n; i++) + presum[i] = presum[i-1] + nums[i]; + + int j = 1; + int ret = 0; + for (int i=1; i<=n; i++) + { + while (j<=n && isOK(nums, presum, i, j, k)) + { + ret = max(ret, j-i+1); + j++; + } + } + return ret; + } + + bool isOK(vector&nums, vector&presum, int i, int j, LL k) + { + int m = (i+j)/2; + LL sum1 = (presum[j]-presum[m]) - (LL)nums[m]*(j-m); + LL sum2 = (LL)nums[m]*(m-i) - (presum[m-1]-presum[i-1]); + return sum1+sum2 <= k; + } + +}; diff --git a/Math/2968.Apply-Operations-to-Maximize-Frequency-Score/Readme.md b/Math/2968.Apply-Operations-to-Maximize-Frequency-Score/Readme.md new file mode 100644 index 000000000..f592523db --- /dev/null +++ b/Math/2968.Apply-Operations-to-Maximize-Frequency-Score/Readme.md @@ -0,0 +1,13 @@ +### 2968.Apply-Operations-to-Maximize-Frequency-Score + +#### 解法1:二分+固定滑窗 +为了通过有限的操作得到更多相等的元素,我们必然会将这些操作集中在原本已经接近的元素上。所以我们将nums排序之后,必然是选取其中的一段subarray,将其变成同一个数。显然,由中位数的性质,想将一个数组中的所有元素变成同一个元素,那么变成他们的中位数median能够使得改动之和最小。 + +我们可以二分搜索最大的subarray长度len。对于选定的len,我们在nums上走一遍固定长度的滑窗。对于每一个滑窗范围[i:j],根据median性质,我们将其变为nums[(i+j)/2]是最高效的做法。令中位数的index是m,那么我们就可以知道区间[i:j]所需要的改动就是两部分之和 `sum[m:j]-nums[m]*(j-m+1) + nums[m]*(m-i+1)-sum[i:m]`. 其中区间和可以用前缀和数组来实现。 + +如果存在一个滑窗使得其需要的改动小于等于k,那么说明len是可行的。我们可以再尝试更大的滑窗,否则尝试更小的滑窗。 + +#### 解法2:动态滑窗 +上述的思想也可以用动态滑窗来实现。固定左边界i之后,我们可以右移右边界j,直至区间[i:j]所需要的改动大于k。此时j-i就是一个可行的区间长度。然后再移动一格左边界i,找到下一个合适的j。 + +此题类似`1838.Frequency-of-the-Most-Frequent-Element` diff --git a/Math/3086.Minimum-Moves-to-Pick-K-Ones/3086.Minimum-Moves-to-Pick-K-Ones.cpp b/Math/3086.Minimum-Moves-to-Pick-K-Ones/3086.Minimum-Moves-to-Pick-K-Ones.cpp new file mode 100644 index 000000000..b8f3c195f --- /dev/null +++ b/Math/3086.Minimum-Moves-to-Pick-K-Ones/3086.Minimum-Moves-to-Pick-K-Ones.cpp @@ -0,0 +1,54 @@ +using LL = long long; +class Solution { +public: + long long minimumMoves(vector& nums, int k, int maxChanges) + { + vectorarr; + for (int i=0; i=0) + ret = min(ret, helper(arr, k-maxChanges) + maxChanges*2); + + if (k-maxChanges+1 <= m && maxChanges-1>=0) + ret = min(ret, helper(arr, k-maxChanges+1) + (maxChanges-1)*2); + + if (k-maxChanges+2 <= m && maxChanges-2>=0) + ret = min(ret, helper(arr, k-maxChanges+2) + (maxChanges-2)*2); + + if (k-maxChanges+3 <= m && maxChanges-3>=0) + ret = min(ret, helper(arr, k-maxChanges+3) + (maxChanges-3)*2); + + return ret; + } + + LL helper(vector&arr, int k) + { + if (k==0) return 0; + + int m = arr.size(); + + LL sum = 0; + for (int i=0; i>& points) + { + vector>arr(4); + + for (auto& p: points) + { + arr[0].insert(p[0]+p[1]); + arr[1].insert(p[0]-p[1]); + arr[2].insert(-p[0]+p[1]); + arr[3].insert(-p[0]-p[1]); + } + + int ret = INT_MAX/2; + for (auto& p: points) + { + arr[0].erase(arr[0].find(p[0]+p[1])); + arr[1].erase(arr[1].find(p[0]-p[1])); + arr[2].erase(arr[2].find(-p[0]+p[1])); + arr[3].erase(arr[3].find(-p[0]-p[1])); + + int ans = 0; + ans = max(ans, *prev(arr[0].end()) - *arr[0].begin()); + ans = max(ans, *prev(arr[1].end()) - *arr[1].begin()); + ans = max(ans, *prev(arr[2].end()) - *arr[2].begin()); + ans = max(ans, *prev(arr[3].end()) - *arr[3].begin()); + + ret = min(ret, ans); + + arr[0].insert(p[0]+p[1]); + arr[1].insert(p[0]-p[1]); + arr[2].insert(-p[0]+p[1]); + arr[3].insert(-p[0]-p[1]); + } + + return ret; + } +}; diff --git a/Math/3102.Minimize-Manhattan-Distances/Readme.md b/Math/3102.Minimize-Manhattan-Distances/Readme.md new file mode 100644 index 000000000..16a0ec2b2 --- /dev/null +++ b/Math/3102.Minimize-Manhattan-Distances/Readme.md @@ -0,0 +1,7 @@ +### 3102.Minimize-Manhattan-Distances + +此题的本质就是1131.Maximum-of-Absolute-Value-Expression,求二维点集里的最大曼哈顿距离。 + +我们需要维护四个有序容器,分别盛装所有点的(x+y), (x-y), (-x+y), (-x-y)。记每个容器中的最大值减去最小值为t,那么四个t中的最大值就是二维点集里的最大曼哈顿距离。 + +根据上述原理,我们遍历所有的点,每次从容器里面将一个点去除,再求此时的最大曼哈顿距离,只需要logN的时间。这样遍历N个点之后,用NlogN的时间就可以求出最优解。 diff --git a/Math/3164.Find-the-Number-of-Good-Pairs-II/3164.Find-the-Number-of-Good-Pairs-II.cpp b/Math/3164.Find-the-Number-of-Good-Pairs-II/3164.Find-the-Number-of-Good-Pairs-II.cpp new file mode 100644 index 000000000..12f85d9e4 --- /dev/null +++ b/Math/3164.Find-the-Number-of-Good-Pairs-II/3164.Find-the-Number-of-Good-Pairs-II.cpp @@ -0,0 +1,34 @@ +class Solution { +public: + long long numberOfPairs(vector& nums1, vector& nums2, int k) + { + vectornums; + for (int x: nums1) + if (x%k==0) + nums.push_back(x/k); + + unordered_map count; + for (int num : nums2) { + count[num]++; + } + long long ret = 0; + + for (int x : nums) + { + for (int d = 1; d * d <= x; ++d) + { + if (x % d == 0) + { + if (count.find(d) != count.end()) { + ret += count[d]; + } + if (d != x / d && count.find(x / d) != count.end()) { + ret += count[x / d]; + } + } + } + } + + return ret; + } +}; diff --git a/Math/3164.Find-the-Number-of-Good-Pairs-II/Readme.md b/Math/3164.Find-the-Number-of-Good-Pairs-II/Readme.md new file mode 100644 index 000000000..3ba3a7b96 --- /dev/null +++ b/Math/3164.Find-the-Number-of-Good-Pairs-II/Readme.md @@ -0,0 +1,5 @@ +### 3164.Find-the-Number-of-Good-Pairs-II + +首先,我们必然将nums1里不能被k整除的去除掉。 + +接下来,我们就是要寻找有多少个pair,使得nums1的元素能被nums2里的元素整除。看上去似乎没有比o(MN)更好的方法了。但是我们可以换一个角度就豁然开朗。我们可以枚举nums1[i]的约数,只需要sqrt(V)次。对于每个约数我们只需要在hash表里查看是否存在这样的nums2元素即可。这样时间复杂度就是`N*sqrt(V)`. diff --git a/Math/3197.Find-the-Minimum-Area-to-Cover-All-Ones-II/3197.Find-the-Minimum-Area-to-Cover-All-Ones-II.cpp b/Math/3197.Find-the-Minimum-Area-to-Cover-All-Ones-II/3197.Find-the-Minimum-Area-to-Cover-All-Ones-II.cpp new file mode 100644 index 000000000..4ca3aa9ad --- /dev/null +++ b/Math/3197.Find-the-Minimum-Area-to-Cover-All-Ones-II/3197.Find-the-Minimum-Area-to-Cover-All-Ones-II.cpp @@ -0,0 +1,101 @@ +class Solution { +public: + int minimumSum(vector>& grid) + { + int ret = INT_MAX; + + int m = grid.size(), n = grid[0].size(); + + /*************************/ + 1. 2. 3. + + ┌-┐ ┌┐┌┐ ┌-┐ + └-┘ └┘└┘ └-┘ + ┌-┐ ┌-┐ ┌┐┌┐ + └-┘ └-┘ └┘└┘ + ┌-┐ + └-┘ + + 4. 5. 6. + ┌┐┌┐┌┐ ┌ ┐┌┐ ┌┐┌ ┐ + └┘└┘└┘ │ │└┘ └┘│ │ + │ │┌┐ ┌┐│ │ + └ ┘└┘ └┘└ ┘ + /*************************/ + + for (int i=1; i>& grid, int a, int b, int c, int d) + { + if (a>c || b>d) return INT_MAX/3; + int left = INT_MAX, top = INT_MAX, bottom = INT_MIN, right = INT_MIN; + for (int i=a; i<=c; i++) + for (int j=b; j<=d; j++) + { + if (grid[i][j]==0) continue; + left = min(left, j); + right = max(right, j); + top = min(top, i); + bottom = max(bottom, i); + } + if (bottom>=top && right>=left) + return (bottom-top+1)*(right-left+1); + else + return INT_MAX/3; + } +}; diff --git a/Math/3197.Find-the-Minimum-Area-to-Cover-All-Ones-II/Readme.md b/Math/3197.Find-the-Minimum-Area-to-Cover-All-Ones-II/Readme.md new file mode 100644 index 000000000..c791a7e39 --- /dev/null +++ b/Math/3197.Find-the-Minimum-Area-to-Cover-All-Ones-II/Readme.md @@ -0,0 +1,21 @@ +### 3197.Find-the-Minimum-Area-to-Cover-All-Ones-II + +事实上将一个矩阵分成三个互不相交的子矩形,只有如下六种形式: +``` + 1. 2. 3. + + ┌-┐ ┌┐┌┐ ┌-┐ + └-┘ └┘└┘ └-┘ + ┌-┐ ┌-┐ ┌┐┌┐ + └-┘ └-┘ └┘└┘ + ┌-┐ + └-┘ + + 4. 5. 6. + ┌┐┌┐┌┐ ┌ ┐┌┐ ┌┐┌ ┐ + └┘└┘└┘ │ │└┘ └┘│ │ + │ │┌┐ ┌┐│ │ + └ ┘└┘ └┘└ ┘ +``` +对于每种形式,只有两条分割线。我们可以用o(MN)的时间遍历分割线的位置,就可以确定三个子矩阵的边界。对于每一个子矩阵,我们再遍历其中的元素,确定包含所有元素1的最小矩阵即可(同3135)。 + diff --git a/Math/3395.Subsequences-with-a-Unique-Middle-Mode-I/3395.Subsequences-with-a-Unique-Middle-Mode-I.cpp b/Math/3395.Subsequences-with-a-Unique-Middle-Mode-I/3395.Subsequences-with-a-Unique-Middle-Mode-I.cpp new file mode 100644 index 000000000..782dafd92 --- /dev/null +++ b/Math/3395.Subsequences-with-a-Unique-Middle-Mode-I/3395.Subsequences-with-a-Unique-Middle-Mode-I.cpp @@ -0,0 +1,69 @@ +using LL = long long; +LL M = 1e9+7; +class Solution { + long long comb[1005][6]; +public: + LL getComb(int m, int n) + { + if (m& nums) + { + int n = nums.size(); + for (int i = 0; i <= n; ++i) + { + comb[i][0] = 1; + if (i==0) continue; + for (int j = 1; j <= 5; ++j) + { + comb[i][j] = comb[i - 1][j - 1] + comb[i - 1][j]; + comb[i][j] %= M; + } + } + unordered_setSet(nums.begin(), nums.end()); + + LL ret = 0; + + unordered_mapleft; + unordered_mapright; + for (int x: nums) right[x]++; + + for (int i=0; i=1) left[nums[i-1]]++; + + ret += getComb(i - left[a], 2) * getComb(n-i-1-right[a], 2) %M; + ret %= M; + + for (int b: Set) + { + if (a==b) continue; + ret += getComb(left[b],2) * getComb(right[a], 1) * getComb(n-i-1-right[a]-right[b], 1) %M; + ret += getComb(right[b],2) * getComb(left[a], 1) * getComb(i-left[a]-left[b], 1) %M; + ret %= M; + } + + for (int b: Set) + { + if (a==b) continue; + ret += getComb(left[b],1) * getComb(i-left[b]-left[a], 1) * getComb(right[a], 1) * getComb(right[b], 1) %M; + ret += getComb(right[b],1) * getComb(n-i-1-right[b]-right[a], 1) * getComb(left[a], 1) * getComb(left[b], 1) %M; + ret %= M; + } + + for (int b: Set) + { + if (a==b) continue; + ret += getComb(left[b],2) * getComb(right[a], 1) * getComb(right[b], 1) %M; + ret += getComb(right[b],2) * getComb(left[a], 1) * getComb(left[b], 1) %M; + ret %= M; + } + } + + return (getComb(n, 5) - ret + M) % M; + } +}; diff --git a/Math/3395.Subsequences-with-a-Unique-Middle-Mode-I/Readme.md b/Math/3395.Subsequences-with-a-Unique-Middle-Mode-I/Readme.md new file mode 100644 index 000000000..62ea183c9 --- /dev/null +++ b/Math/3395.Subsequences-with-a-Unique-Middle-Mode-I/Readme.md @@ -0,0 +1,37 @@ +### 3395.Subsequences-with-a-Unique-Middle-Mode-I + +所有任取5个元素的子序列有comb(n,5)个。我们枚举nums[i]=a为中间元素,考虑计算不符合条件的子序列的个数。 + +如果a出现了3次或以上,那么该序列必然是符合条件的,不用考虑。 + +如果a只出现了1次,那么该序列必然不符合条件。这样的子序列有多少个呢?只需要在左区间里任选两个非a的元素,在右区间里任选两个非a的元素。故可构造这样的子序列的个数是`comb(i-left[a],2) * comb(n-i-1-right[a], 2)`。其中left表示统计i左边的元素的频次hash,right表示统计i右边的元素的频次hash。 + +如果a出现了2次,那么必然有一种元素b出现了两次或三次,才能是不符合条件的子序列。我们可以枚举b,并分为三种情况: +1. b出现了两次,且两次都出现在同一侧(假设是左边),那么右边必须要出现另一个a,以及一个非a也非b的元素(假设是c)。故写为`b b a a c`的类型。这样的概率是 +``` +comb(left[a],2) * (right[a],1) * (n-i-1-right[a]-right[b], 1) +``` +类似的,如果两个b都在另一侧,只需将上面的left和right相反即可。 +``` +comb(right[a],2) * (left[a],1) * (i-left[a]-left[b], 1) +``` + +2. b出现了两次,且出现在两侧,那么nums[i]的左右两边分别需要再出现另一个a,以及一个非a也非b的元素(假设是c)。故写为`b a a b c`的类型。这样的概率是 +``` +comb(left[a],1) * (left[b],1) * right([b],1) * (n-i-1-right[a]-right[b], 1) +``` +或者反过来 +``` +comb(right[a],1) * (right[b],1) * left([b],1) * (i-left[a]-left[b], 1) +``` + +3. b出现了三次,占据了除两个a之外的全部位置。故写为`b b a a b`的类型。这样的概率是 +``` +comb(left[b],2) * (right[b],1) * right([a],1) +``` +或者反过来 +``` +comb(right[b],2) * (left[b],1) * left([a],1) +``` + +上述算法用了两重循环枚举a和b。另外,因为n只有1000,我们可以用n^2时间(或者只需要n*5)提前计算好所有1000以内的组合数。所以总的时间复杂度是n^2. diff --git a/Math/3405.Count-the-Number-of-Arrays-with-K-Matching-Adjacent-Elements/3405.Count-the-Number-of-Arrays-with-K-Matching-Adjacent-Elements.cpp b/Math/3405.Count-the-Number-of-Arrays-with-K-Matching-Adjacent-Elements/3405.Count-the-Number-of-Arrays-with-K-Matching-Adjacent-Elements.cpp new file mode 100644 index 000000000..3cf1a6303 --- /dev/null +++ b/Math/3405.Count-the-Number-of-Arrays-with-K-Matching-Adjacent-Elements/3405.Count-the-Number-of-Arrays-with-K-Matching-Adjacent-Elements.cpp @@ -0,0 +1,38 @@ +using LL = long long; +class Solution { +public: + const LL MOD = 1e9 + 7; + vector factorial; + vector GetFactorial(LL N) + { + vectorrets(N+1); + rets[0] = 1; + for (int i=1; i<=N; i++) + rets[i] = rets[i-1] * i % MOD; + return rets; + } + + long long quickPow(long long x, long long N) { + if (N == 0) { + return 1; + } + LL y = quickPow(x, N / 2) % MOD; + return N % 2 == 0 ? (y * y % MOD) : (y * y % MOD * x % MOD); + } + + LL comb(LL m, LL n) + { + if (n>m) return 0; + LL a = factorial[m]; + LL b = factorial[n] * factorial[m-n] % MOD; + LL inv_b = quickPow(b, (MOD-2)); + + return a * inv_b % MOD; + } + + int countGoodArrays(int n, int m, int k) + { + factorial = GetFactorial(n); + return comb(n-1,k) * m % MOD * quickPow(m-1, n-k-1) % MOD; + } +}; diff --git a/Math/3405.Count-the-Number-of-Arrays-with-K-Matching-Adjacent-Elements/Readme.md b/Math/3405.Count-the-Number-of-Arrays-with-K-Matching-Adjacent-Elements/Readme.md new file mode 100644 index 000000000..037aa8903 --- /dev/null +++ b/Math/3405.Count-the-Number-of-Arrays-with-K-Matching-Adjacent-Elements/Readme.md @@ -0,0 +1,11 @@ +### 3405.Count-the-Number-of-Arrays-with-K-Matching-Adjacent-Elements + +考虑到恰有k个位置的元素与其左边元素相同,那么将其与其左边元素“合并”后,数组里可看做只有n-k个元素,并且这些元素在相邻的位置上不重复。证明很显然,如果“合并”后依然存在相邻的相同元素,那么原数组里必然不止k处相邻的相同元素。 + +从原数组里挑出k个位置,有comb(n,k)种方案。 + +任何一种上述的方案,对于“合并”后的数组的n-k个元素,要求相邻之间不重复,有多少种方案?第一个位置有m种选择,之后每一个位置都只有m-1种选择。故总共有`m*m^(n-k-1)`种方案。 + +所以最终答案就是简单的数学表达式 `comb(n,k)*m*m^(n-k-1)`. + +因为n和k是1e5,所以不能用o(n*k)的复杂度计算组合数任何n以内的组合数。我们可以直接硬算`comb(n,k) = n!/k!/(n-k)!`. 其中阶乘的复杂度就是o(n),但是涉及到了除法,故需要介入逆元。逆元的计算公式是`inv_x = quickPow(x, (MOD-2))`. diff --git a/Math/3428.Maximum-and-Minimum-Sums-of-at-Most-Size-K-Subsequences/3428.Maximum-and-Minimum-Sums-of-at-Most-Size-K-Subsequences.cpp b/Math/3428.Maximum-and-Minimum-Sums-of-at-Most-Size-K-Subsequences/3428.Maximum-and-Minimum-Sums-of-at-Most-Size-K-Subsequences.cpp new file mode 100644 index 000000000..992e3202f --- /dev/null +++ b/Math/3428.Maximum-and-Minimum-Sums-of-at-Most-Size-K-Subsequences/3428.Maximum-and-Minimum-Sums-of-at-Most-Size-K-Subsequences.cpp @@ -0,0 +1,38 @@ +using LL = long long; +class Solution { + LL MOD = 1e9 + 7; + LL comb[100005][75]; +public: + int minMaxSums(vector& nums, int k) + { + int n = nums.size(); + for (int i = 0; i <= n; ++i) + { + comb[i][0] = 1; + if (i==0) continue; + for (int j = 1; j <= k; ++j) + { + comb[i][j] = comb[i - 1][j - 1] + comb[i - 1][j]; + comb[i][j] %= MOD; + } + } + + sort(nums.begin(), nums.end()); + + LL ret = 0; + for (int i=0; i& nums) { + + int total = accumulate(nums.begin(), nums.end(), 0); + + LL ret = 1; + for (int i=0; i<26; i++) + { + if (nums[i]!=0) + { + ret *= getComb(total, nums[i]); + if (ret >= INT_MAX/2) return INT_MAX/2; + total -= nums[i]; + } + } + return ret; + } + + string smallestPalindrome(string s, int k) + { + int n = s.size(); + vectornums(26); + for (int i=0; i&nums, string& ret, LL& curCount) + { + int total = accumulate(nums.begin(), nums.end(), 0); + if (total == 0) { + curCount+=1; + return; + } + + for (int i=0; i<26; i++) + { + if (nums[i]==0) continue; + nums[i]-=1; + LL temp = countPermutations(nums); + + if (curCount + temp < k) + { + curCount += temp; + nums[i]++; + } + else + { + ret.push_back('a'+i); + dfs(k, nums, ret, curCount); + break; + } + } + } +}; diff --git a/Math/3518.Smallest-Palindromic-Rearrangement-II/Readme.md b/Math/3518.Smallest-Palindromic-Rearrangement-II/Readme.md new file mode 100644 index 000000000..6a95c3eb1 --- /dev/null +++ b/Math/3518.Smallest-Palindromic-Rearrangement-II/Readme.md @@ -0,0 +1,35 @@ +### 3518.Smallest-Palindromic-Rearrangement-II + +很明显本题的核心就是,我们取原字符串前一半的字母,问这些字母所能组成的从小到大的第k个字典序的排列是什么。因为有重复的字符,这里要求重复的排列只算一种。 + +关于n个字母的排列,求第k大的字典序,是一个比较常规的题目了。我们只需要从按头到尾的顺序、把候选字母从小到大地进行尝试即可。比如说,我们尝试第一个位置填写a,并且计算出后面n-1个位置如果有m种排列,那么就可以根据m和k的大小关系进行决策:如果m=k,那么我们就能确定第一个位置必然是a,此时进行递归处理第二个位置即可。 + +由此,我们需要一个函数`countPermutations(vector&nums)`,表示给定一系列字母及其个数的情况下,有多少种不同的排列。其中nums是一个长度为26的数组,记录每种字符的个数。假设总共的字符个数是n。此时最容易想到的算法就是 `count = n!/(t0!)*(t1!)...*(t25!)`,其中ti表示每种字符的个数。因为n达到了1e4,其阶乘是天文数字,必然会溢出;并且这涉及到了大数的除法,我们无法保证精度。此时就应该想到第二种算法,就是 `count = C(n,t0)*C(n-t0,t1)*C(n-t0-t1,t2)*...`这里就规避了除法的精度问题,但是溢出问题依然得不到解决。 + +此时注意到k不超过1e6。一旦count的计算需要涉及到超大的阶乘数,那么必然会远远超过k。当这种情况发生时,我们其实并不需要知道count的精确数值,因为根据之前的说法,我们对字母选择的决策其实是很明显的了。因此我们在计算count甚至在计算组合数本身时,如果能预期到它很大,我们直接就返回INT_MAX/2之类的超大数来影响决策即可,而不需要计算它实际的数值。 + +此外,本题对于空间的利用需要达到极致。我们知道,可以用n^2的时间和空间来提前处理Comb(n,x)级别的组合数。通常我们只能开到comb[1000][1000]的规模。但是利用到`C(m,n)=C(m,m-n)`的性质,我们可以最大开辟数组空间达到comb[5001][2501]。代码如下: +```cpp + int comb[5001][2501]; + int getComb(int m, int n) + { + if (m& nums) { + vectorbasis(32, 0); + for (int x: nums) { + for (int i=31; i>=0; i--) { + if (!(x>>i)&1) continue; + if (!basis[i]) { + basis[i] = x; + break; + } + x ^= basis[i]; + } + } + + int ans = 0; + for (int i = 31; i >= 0; i--) { + ans = max(ans, ans ^ basis[i]); + } + return ans; + } +}; diff --git a/Math/3681.Maximum-XOR-of-Subsequences/Readme.md b/Math/3681.Maximum-XOR-of-Subsequences/Readme.md new file mode 100644 index 000000000..03c345b4f --- /dev/null +++ b/Math/3681.Maximum-XOR-of-Subsequences/Readme.md @@ -0,0 +1,50 @@ +### 3681.Maximum-XOR-of-Subsequences + +这是一道“异或空间线性基”的模版题。 + +首先定义“异或空间线性基”。对于一组数字集合nums,任意选取若干进行异或操作所能构成的结果记作空间S。如果另一组数字Basis同样也能构成S,并且Basis所需的数字最少,那么Basis就成为原数字集合的一组“异或空间线性基”。举个例子,比如nums={9,11,12,14},它所能构成的异或空间S={2,5,7,9,11,12,14}. 通过某种算法,我们可以找到一组线性基B={12,5,2},并且可以验证B能构造的异或空间也是S。另外,我们所构造的线性基有一个特点,每个元素的leading one的位置都不相同。比如{12,5,2},其二进制表达就是{1100,0101,0010},可以观察到最高位的位置依次降低。 + +假设我们能找到nums的一组线性基B,那么nums能组成的最大异或值,等价于其线性基B能组成的最大异或值。而对于B,由于有前述的性质(最高位的位置依次降低),我们可以用贪心法来从大到小进行选择:第i个元素的选择取决于答案的第i个bit位是否会退化。 +```cpp +int ret = 0; +for (int b: basis) { + ret = max(ret, ret^b); +} +``` + +接下来我们学习如何构造这样的异或空间线性基。举个例子:a=2, b=5, c=11, d=6 +``` + 3 2 1 0 +a 0 0 1 0 +b 0 1 0 1 +c 1 0 1 1 +d 0 1 1 0 +``` +首先考察a=0010,最高位在第1位,且我们还没有最高位在第1位的基存在,所以我们可以确定一个最高位在第1位的基:b(1) = 0010. + +接着考察b=0101,最高位在第2位,且我们还没有最高位在第2位的基存在,所以我们可以确定一个最高位在第2位的基:b(2) = 0101. + +然后考察c=1011,最高位在第3位,且我们还没有最高位在第3位的基存在,所以我们可以确定一个最高位在第3位的基:b(3) = 1011. + +最后考察d=0110,最高位在第2位,我们已经确定了b(2),所以我们与b(2)结合`d^b(2) = 0110^0101 = 0011`。它的最高位在第1位,而我们已经确定了b(1),我们就再与b(1)结合`0011^0010=0001`,此时它的最高位在第0位,且我们还没有最高位在第0位的基存在,所以我们可以确定一个最高位在第0位的基:b(3) = 0001. + +综上,我们得到了一组线性基`b(3)=11, b(2)=5, b(1)=2, b(0)=1`. + +以上的算法总结成代码模板就是: +```cpp +vectorbasis(32, 0); +for (int x: nums) { + for (int i=31; i>=0; i--) { + if (!(x>>i)&1) continue; + if (!basis[i]) { + basis[i] = x; + break; + } + x ^= basis[i]; + } +} +``` + + + + diff --git a/Math/3700.Number-of-ZigZag-Arrays-II/3700.Number-of-ZigZag-Arrays-II.cpp b/Math/3700.Number-of-ZigZag-Arrays-II/3700.Number-of-ZigZag-Arrays-II.cpp new file mode 100644 index 000000000..32f50988f --- /dev/null +++ b/Math/3700.Number-of-ZigZag-Arrays-II/3700.Number-of-ZigZag-Arrays-II.cpp @@ -0,0 +1,90 @@ +using ll = long long; +ll MOD = 1e9+7; + +// 矩阵乘法 C = A * B +vector> matMul(const vector>& A, const vector>& B) { + int n = A.size(); + int m = B[0].size(); + int K = B.size(); // A: n x K, B: K x m + vector> C(n, vector(m, 0)); + for (int i = 0; i < n; ++i) { + for (int k = 0; k < K; ++k) { + ll aik = A[i][k]; + if (aik == 0) continue; + for (int j = 0; j < m; ++j) { + C[i][j] += (aik * B[k][j]) % MOD; + if (C[i][j] >= MOD) C[i][j] -= MOD; + } + } + } + return C; +} + +// 矩阵自乘 (square) +vector> matMulSquare(const vector>& A, const vector>& B) { + return matMul(A, B); +} + +// 矩阵快速幂 T^e +vector> matPow(vector> base, long long e) { + int K = base.size(); + // initialize res = identity + vector> res(K, vector(K, 0)); + for (int i = 0; i < K; ++i) res[i][i] = 1; + while (e > 0) { + if (e & 1) res = matMul(res, base); + base = matMul(base, base); + e >>= 1; + } + return res; +} + +// 矩阵乘向量 v' = M * v +vector matVecMul(const vector>& M, const vector& v) { + int n = M.size(); + int m = v.size(); + vector r(n, 0); + for (int i = 0; i < n; ++i) { + ll sum = 0; + for (int j = 0; j < m; ++j) { + if (M[i][j] == 0) continue; + sum += (M[i][j] * v[j]) % MOD; + if (sum >= MOD) sum -= MOD; + } + r[i] = sum % MOD; + } + return r; +} + +class Solution { +public: + int zigZagArrays(int n, int l, int r) { + int m = r-l+1; + + int K = 2*m; + vector>T(K, vector(K,0)); + + for (int i=1; is(K,0); + for (int i=0; i<2*m; i++) + s[i] = 1; + + vector>T_p = matPow(T, n-1); + + vectorsn = matVecMul(T_p, s); + + ll ans = 0; + for (ll v: sn) { + ans += v; + ans%=MOD; + } + return ans; + } +}; diff --git a/Math/3700.Number-of-ZigZag-Arrays-II/Readme.md b/Math/3700.Number-of-ZigZag-Arrays-II/Readme.md new file mode 100644 index 000000000..20138f213 --- /dev/null +++ b/Math/3700.Number-of-ZigZag-Arrays-II/Readme.md @@ -0,0 +1,27 @@ +### 3700.Number-of-ZigZag-Arrays-II + +此题是3699的进阶版,时间复杂度是o(NM)的动态规划是不可行的。考虑到n如此巨大,必然是用到了类似于快速幂的算法。 + +此题的思路还是基于之前的DP思想: +``` +up[i][x] = sum(down[i-1][y]) for y=x+1,...,m +down[i][x] = sum(up[i-1][y]) for y=1,2,...,x-1 +``` +突破点在于第i轮的状态只依赖于第i-1轮,即{up[i]和down[i]}是{up[i-1],down[i-1]}的线性组合。因此我们令向量`vec[i]={up[1],up[2],...,up[m],down[1],down[2],...,down[m]}`,它与vec[i-1]之间必然存在转移矩阵T使得`vec[i]=T*vec[i-1]`. 这样的矩阵是一个2m*2m的方阵。 + +我们不难构造出T的样子: +``` +............... 0 + 1 0 + 1 1 0 + ... + 1 1 1 .. 1 1 0 +1 1 1 .. 1 1 0 ........... +... +1 1 0 +1 0 +0 +``` +右上部分体现了up[i]与down[i-1]的关系,左下部分体现了down[i]与up[i-1]的关系。 + +我们易知初始状态v[1] = {1,..0,0..,1},就可以得到 `v[n] = T*T*T...*T*v[1]`,其中T要连乘n-1次幂,然后再与向量v[1]想乘,即得最后一轮的v。其向量的所有元素之和就是答案。 diff --git a/Others/1503.Last-Moment-Before-All-Ants-Fall-Out-of-a-Plank/1503.Last-Moment-Before-All-Ants-Fall-Out-of-a-Plank.cpp b/Others/1503.Last-Moment-Before-All-Ants-Fall-Out-of-a-Plank/1503.Last-Moment-Before-All-Ants-Fall-Out-of-a-Plank.cpp new file mode 100644 index 000000000..8f2f9dd12 --- /dev/null +++ b/Others/1503.Last-Moment-Before-All-Ants-Fall-Out-of-a-Plank/1503.Last-Moment-Before-All-Ants-Fall-Out-of-a-Plank.cpp @@ -0,0 +1,9 @@ +class Solution { +public: + int getLastMoment(int n, vector& left, vector& right) + { + sort(left.begin(), left.end()); + sort(right.begin(), right.end()); + return max(left.size()==0?0:left.back(), right.size()==0?0:n-right[0]); + } +}; diff --git a/Others/1503.Last-Moment-Before-All-Ants-Fall-Out-of-a-Plank/Readme.md b/Others/1503.Last-Moment-Before-All-Ants-Fall-Out-of-a-Plank/Readme.md new file mode 100644 index 000000000..57a956b02 --- /dev/null +++ b/Others/1503.Last-Moment-Before-All-Ants-Fall-Out-of-a-Plank/Readme.md @@ -0,0 +1,3 @@ +### 1503.Last-Moment-Before-All-Ants-Fall-Out-of-a-Plank + +很明显,任何碰撞事件都不影响宏观上蚂蚁的运动状态(只不过身份调换一下)。所以最后一个从左边掉落的蚂蚁,一定对应着初始时最靠右的、向左运动的蚂蚁。反之,最后一个从右边掉落的蚂蚁,一定对应着初始时最靠左的、向右运动的蚂蚁。 diff --git a/Greedy/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array_Greedy.cpp b/Others/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array_Greedy.cpp similarity index 100% rename from Greedy/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array_Greedy.cpp rename to Others/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array_Greedy.cpp diff --git a/Greedy/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array_SegmentTree.cpp b/Others/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array_SegmentTree.cpp similarity index 100% rename from Greedy/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array_SegmentTree.cpp rename to Others/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array_SegmentTree.cpp diff --git a/Greedy/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array/Readme.md b/Others/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array/Readme.md similarity index 100% rename from Greedy/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array/Readme.md rename to Others/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array/Readme.md diff --git a/Others/2211.Count-Collisions-on-a-Road/2211.Count-Collisions-on-a-Road.cpp b/Others/2211.Count-Collisions-on-a-Road/2211.Count-Collisions-on-a-Road.cpp new file mode 100644 index 000000000..6743dc398 --- /dev/null +++ b/Others/2211.Count-Collisions-on-a-Road/2211.Count-Collisions-on-a-Road.cpp @@ -0,0 +1,27 @@ +class Solution { +public: + int countCollisions(string directions) + { + int count = 0; + int n = directions.size(); + + int flag = 0; + for (int i=0; i=0; i--) + { + if (flag == 0 && (directions[i]=='L' || directions[i]=='S')) + flag = 1; + if (flag == 1 && directions[i]=='R') + count++; + } + return count; + } +}; diff --git a/Others/2211.Count-Collisions-on-a-Road/Readme.md b/Others/2211.Count-Collisions-on-a-Road/Readme.md new file mode 100644 index 000000000..6f5965d4f --- /dev/null +++ b/Others/2211.Count-Collisions-on-a-Road/Readme.md @@ -0,0 +1,3 @@ +### 2211.Count-Collisions-on-a-Road + +很显然,只要左边缘有一个静止或者向右运动的车辆,那么它右边任何向左运动的车辆注定都会相撞。同理,只要右边缘有一个静止或者向左运动的车辆,那么它做边任何向右运动的车辆注定都会相撞。 diff --git a/Others/2359.Find-Closest-Node-to-Given-Two-Nodes/2359.Find-Closest-Node-to-Given-Two-Nodes.cpp b/Others/2359.Find-Closest-Node-to-Given-Two-Nodes/2359.Find-Closest-Node-to-Given-Two-Nodes.cpp index 7acab1c41..aea7c61a8 100644 --- a/Others/2359.Find-Closest-Node-to-Given-Two-Nodes/2359.Find-Closest-Node-to-Given-Two-Nodes.cpp +++ b/Others/2359.Find-Closest-Node-to-Given-Two-Nodes/2359.Find-Closest-Node-to-Given-Two-Nodes.cpp @@ -34,8 +34,6 @@ class Solution { ret = max(dist1[i], dist2[i]); ans = i; } - else if (max(dist1[i], dist2[i]) == ret) - ans = min(ans, i); } } if (ret!=INT_MAX) diff --git a/Others/2584.Split-the-Array-to-Make-Coprime-Products/2584.Split-the-Array-to-Make-Coprime-Products.cpp b/Others/2584.Split-the-Array-to-Make-Coprime-Products/2584.Split-the-Array-to-Make-Coprime-Products.cpp new file mode 100644 index 000000000..b26f692ff --- /dev/null +++ b/Others/2584.Split-the-Array-to-Make-Coprime-Products/2584.Split-the-Array-to-Make-Coprime-Products.cpp @@ -0,0 +1,78 @@ +vectorEratosthenes(int n) // NlogNlogN +{ + vectorq(n+1,0); + vectorprimes; + for (int i=2; i<=sqrt(n); i++) + { + if (q[i]==1) continue; + int j=i*2; + while (j<=n) + { + q[j]=1; + j+=i; + } + } + for (int i=2; i<=n; i++) + { + if (q[i]==0) + primes.push_back(i); + } + return primes; +} + +class Solution { +public: + int findValidSplit(vector& nums) + { + int K = *max_element(nums.begin(), nums.end()); + vectorprimes = Eratosthenes(K); + unordered_setSet(primes.begin(), primes.end()); + + unordered_map>Map; + + for (int i=0; i x) + { + if (Map.find(x)==Map.end()) + Map[x].first = i; + Map[x].second = i; + break; + } + + if (x%p==0) + { + if (Map.find(p)==Map.end()) + Map[p].first = i; + Map[p].second = i; + } + while (x%p==0) x/=p; + } + } + + int n = nums.size(); + vectordiff(n+1); + for (auto& [k, v]: Map) + { + // cout< children * 8) return children-1; + + int d = money - children; + int k = d / 7; + int r = d % 7; + + if (r==3 && (children-k)==1) + return k-1; + else + return k; + + } +}; diff --git a/Others/2591.Distribute-Money-to-Maximum-Children/Readme.md b/Others/2591.Distribute-Money-to-Maximum-Children/Readme.md new file mode 100644 index 000000000..3e0bb1fee --- /dev/null +++ b/Others/2591.Distribute-Money-to-Maximum-Children/Readme.md @@ -0,0 +1,7 @@ +### 2591.Distribute-Money-to-Maximum-Children + +首先考虑无解的情况。当money distance(vector& nums) + { + int n = nums.size(); + unordered_map>Map; + for (int i=0; irets(n); + for (auto& [_, arr]: Map) + { + int m = arr.size(); + LL sum = 0; + for (int x: arr) + sum += abs(x - arr[0]); + rets[arr[0]] = sum; + + for (int i=0; i+1; // {pos, val} +class Solution { +public: + int minimumVisitedCells(vector>& grid) + { + int m = grid.size(), n = grid[0].size(); + + vector>dp(m, vector(n, INT_MAX/2)); + vector, greater<>>> row_diff(m); + vector, greater<>>> col_diff(n); + vector>row_set(m); + vector>col_set(n); + + for (int i=0; i0) row_set[i].insert(x); + if (x<0) row_set[i].erase(row_set[i].find(-x)); + } + while (!col_diff[j].empty() && col_diff[j].top().first == i) + { + int x = col_diff[j].top().second; + col_diff[j].pop(); + if (x>0) col_set[j].insert(x); + if (x<0) col_set[j].erase(col_set[j].find(-x)); + } + + int min_val = INT_MAX/2; + if (!row_set[i].empty()) min_val = min(min_val, *row_set[i].begin()); + if (!col_set[j].empty()) min_val = min(min_val, *col_set[j].begin()); + dp[i][j] = min_val; + if (i==0 && j==0) dp[0][0] = 0; + + int step = grid[i][j]; + if (step == 0) continue; + row_diff[i].push({j+1, dp[i][j]+1}); + row_diff[i].push({j+1+step, -(dp[i][j]+1)}); + col_diff[j].push({i+1, dp[i][j]+1}); + col_diff[j].push({i+1+step, -(dp[i][j]+1)}); + } + + if (dp[m-1][n-1] == INT_MAX/2) + return -1; + return dp[m-1][n-1] + 1; + + } +}; diff --git a/Others/2617.Minimum-Number-of-Visited-Cells-in-a-Grid/Readme.md b/Others/2617.Minimum-Number-of-Visited-Cells-in-a-Grid/Readme.md new file mode 100644 index 000000000..11245bec5 --- /dev/null +++ b/Others/2617.Minimum-Number-of-Visited-Cells-in-a-Grid/Readme.md @@ -0,0 +1,15 @@ +### 2617.Minimum-Number-of-Visited-Cells-in-a-Grid + +按照正常的DP思路,我们令dp[i][j]表示到达(i,j)的最短时间。当更新dp[i][j]的时候,我们发现它的前驱状态会有很多,包括同行里左边的若干格子(不一定相连),同列上面的若干格子(不一定相连)。我们发现遍历这些前驱状态最多需要花费o(m)和o(n)的时间,再配上遍历全体的o(mn),时间复杂度是超的。 + +我们换个DP的角度,如果已知dp[i][j],那么我们可以更新未来的一些状态,包括同行右边的若干格子(一定相连),以及同列下边的若干格子(一定相连)。但是同理,这也是`o(m)*o(mn)`的时间复杂度。但是我们发现从这个角度考虑的话,你可以更新的格子是一个连续的subarray。举个例子,如果dp[i][j]=4,grid[i][j]=3,那么意味着(i,j+1)到(i,j+3)这三个格子的dp都可以更新到5,此外(i+1,j)到(i+3,j)这三个格子的dp也都可以更新到5. 我们立马就想到了差分数组的性质,可以避免将整个区间的元素逐个更新,只需要对这个连续区间的首尾进行标记即可。 + +具体的步骤是:我们首先给每一行和每一列配一个“差分点”的优先队列。按照上面的例子,假设我们得到`dp[i][j]=4`,且`grid[i][j]=step`, 那么意味着优先队列row_diff[i]里需要添加两个差分点,分别是`{j+1, 5}`和`{j+step+1, -5}`,表示第i行从第j列开始的格子,dp值可以是5,但是从第i行第j+step+1列开始的格子,dp值不能再有5. 同理,我们对于另一个优先队列col_diff[j]也添加类似的差分点,分别是`{i+1, 5}`和`{i+step+1, -5}`. + +以上讲的是已知dp[i][j],如何更新row_diff[i]与col_diff[j]。那么我们如何计算dp[i][j]呢?我们同样需要给每一行和每一列配一个multiset,表示当前可以选取的dp值,但是显然我们只会挑最小的。举个例子,当我们遍历到(i,j)点时,有序集合row[i]会从row_diff[i]里看是否在(i,j)有差分点,有的话就从row[i]里加入或者减去相应的dp值。同理,另一个有序集合col[j]会从col_diff[j]里看是否在(i,j)有差分点,有的话就从col[j]里加入或者减去相应的dp值。最终dp[i][j]必然是在row[i]和col[j]里里面挑最小的元素(即在一堆可选的dp值里挑最小的)。 + +综上,我们对每个(i,j),先从从row_diff[i]和col_diff[j]读入差分点,更新row[i]和col[j],然后选最小值得到dp[i][j],然后往row_diff[i]和col_diff[j]再加入后续的差分点。 + +最终的答案就是dp[m-1][n-1]. + +类型的思路可以借鉴2158,2218,253。 diff --git a/Others/2647.Color-the-Triangle-Red/2647.Color-the-Triangle-Red.cpp b/Others/2647.Color-the-Triangle-Red/2647.Color-the-Triangle-Red.cpp new file mode 100644 index 000000000..f057641ed --- /dev/null +++ b/Others/2647.Color-the-Triangle-Red/2647.Color-the-Triangle-Red.cpp @@ -0,0 +1,57 @@ +class Solution { +public: + vector> colorRed(int n) + { + vector>rets; + vector>val(n+1, vector(2*n+2)); + + for (int j=1; j<=2*n-1; j+=2) + { + val[n][j] = 1; + rets.push_back({n,j}); + } + + bool forward = 1; + for (int i=n-1; i>=2; i--) + { + int j, end, delta; + if (forward) { + j = 1; end = 2*i; delta = 1; + } else { + j = 2*i-1; end = 0; delta = -1; + } + + while (j != end) + { + if (val[i][j]==0){ + if (j%2 == 1) { // a normal triangle cell. Its bottom neighbour must have been filled. + if (val[i][j-delta]==0) { + // Noramlly, the previous row neighbour must have been filled. The exception is the case when (i,j) is already the edge. + val[i][j+delta] = 1; + rets.push_back({i, j+delta}); + } + } else { // a up-side-down triangle cell. Its up neighbour must have not been filled. + if (val[i][j+delta]==0) { + // Noramlly, the next row neighbour must have not been filled. + // The exception is the case when the next row neighbour is filled by the previous row. + // Favor upper cell, as its next neighbour must be filled in the next round. + val[i-1][j-1] = 1; + rets.push_back({i-1, j-1}); + } + } + val[i][j] = 1; + } + + j+= delta; + } + + forward = !forward; + } + + if (rets.back()[0]!=1 && rets.back()[1]!=1) { + rets.push_back({1,1}); + } + + return rets; + } +}; diff --git a/Others/2647.Color-the-Triangle-Red/Readme.md b/Others/2647.Color-the-Triangle-Red/Readme.md new file mode 100644 index 000000000..b9275d5b3 --- /dev/null +++ b/Others/2647.Color-the-Triangle-Red/Readme.md @@ -0,0 +1,11 @@ +### 2647.Color-the-Triangle-Red + +纯粹的贪心找规律。 + +初始,先将最后一行从左边开始,每隔一个cell进行染色。 + +然后,从最后一行开始,逐行扫描,按照顺序和逆序交替进行检查每个cell。如果已经被染色,则跳过。下面分情况讨论: +1. 如果该cell的列编号是奇数,说明是个正三角,它的下邻居必然已经染色(我们是逐行处理)。此时如果它之前的行邻居被染色了(通常情况下必然是的),那它自身必然会”被动“染色,故只标记,不加入答案。相反,如果它的左右行邻居都还没有被染色(意味着它本身是该行的第一个),则它无法被动染色。为了最大化效率,我们不直接染色它本身,而是染色它的下一个行邻居,这样它自身也可以”被动“染色。 +2. 如果该cell的列编号是偶数,说明是个倒三角,它的上邻居必然还没有被染色。此时如果它的左右行邻居都已经被染色了(通常情况下前一个行邻居必然已经被染色,而下一个行邻居也有可能被跨行染色过),那它自身必然会”被动“染色,故只标记,不加入答案。相反,如果它的左右行邻居有任何一个没有被染色,意味着它无法被动染色。为了最大化效率,我们不直接染色它本身,而是染色它的上邻居,这样它自身也可以”被动“染色*(因为已经有一个行邻居)。 + +这种算法可能会收录{1,2},我们要将其去掉,换成{1,1}。 diff --git a/Others/2681.Power-of-Heroes/2681.Power-of-Heroes.cpp b/Others/2681.Power-of-Heroes/2681.Power-of-Heroes.cpp new file mode 100644 index 000000000..edf467eee --- /dev/null +++ b/Others/2681.Power-of-Heroes/2681.Power-of-Heroes.cpp @@ -0,0 +1,25 @@ +using LL = long long; +LL M = 1e9+7; +class Solution { +public: + int sumOfPower(vector& nums) + { + sort(nums.begin(), nums.end()); + + LL sum = 0; + LL ret = 0; + + for (int i=0; i=1) + sum = sum * 2 % M + (LL)nums[i-1]; + + ret += mx * sum % M + mx * nums[i] % M; + ret %= M; + } + + return ret; + } +}; diff --git a/Others/2681.Power-of-Heroes/Readme.md b/Others/2681.Power-of-Heroes/Readme.md new file mode 100644 index 000000000..2a92eba56 --- /dev/null +++ b/Others/2681.Power-of-Heroes/Readme.md @@ -0,0 +1,15 @@ +### 2681.Power-of-Heroes + +我们将所有元素排序之后,假设nums[i]是所选子集的最大值,那么意味着子集的其他元素必然是在[0:i-1]里面选择。我们依次枚举最小值的话,那么所有子集的最小值的和 +``` +sum = nums[0]* 2^(i-2) + nums[1] * 2^(i-1) + ... + nums[i-1]* 2^0; +``` +别忘了nums[i]本身也可以是最小值(子集只有一个元素)。所以答案就是`sum * nums[i]^2 + nums[i] * nums[i]^2`。 + +当我们右移i,考虑新的nums[i]是所选自己的最大值时,sum依然是 +``` +sum = nums[0]* 2^(i-2) + nums[1] * 2^(i-1) + ... + nums[i-1]* 2^0; +``` +和之前的sum相比,变动就是`sum = (sum + nums[i-1]) * 2`,o(1)时间就可以更新sum。 + +所以本题的算法就是:排序后,假设nums[i]是所选子集的最大值,更新sum,然后最终答案加上`sum * nums[i]^2 + nums[i] * nums[i]^2` diff --git a/Others/2718.Sum-of-Matrix-After-Queries/2718.Sum-of-Matrix-After-Queries.cpp b/Others/2718.Sum-of-Matrix-After-Queries/2718.Sum-of-Matrix-After-Queries.cpp new file mode 100644 index 000000000..4babc2cfa --- /dev/null +++ b/Others/2718.Sum-of-Matrix-After-Queries/2718.Sum-of-Matrix-After-Queries.cpp @@ -0,0 +1,32 @@ +using LL = long long; +class Solution { +public: + long long matrixSumQueries(int n, vector>& queries) + { + vectorrow(n, -1); + vectorcol(n, -1); + LL rowLeft = n; + LL colLeft = n; + LL ret = 0; + reverse(queries.begin(), queries.end()); + for (auto & q: queries) + { + int type = q[0], idx = q[1], val = q[2]; + if (type==0) + { + if (row[idx]!=-1) continue; + row[idx] = val; + ret += rowLeft * val; + colLeft--; + } + else + { + if (col[idx]!=-1) continue; + col[idx] = val; + ret += colLeft * val; + rowLeft--; + } + } + return ret; + } +}; diff --git a/Others/2718.Sum-of-Matrix-After-Queries/Readme.md b/Others/2718.Sum-of-Matrix-After-Queries/Readme.md new file mode 100644 index 000000000..23cdc4007 --- /dev/null +++ b/Others/2718.Sum-of-Matrix-After-Queries/Readme.md @@ -0,0 +1,5 @@ +### 2718.Sum-of-Matrix-After-Queries + +很明显,后面的操作会覆盖前者,我们必然会从后往前复盘,这样已经被填充的格子就不会再更改,更方便分析。 + +假设我们第一步是将某一行填充数字a,那么我们发现,以后的任何一次列操作都只会影响到n-1个格子。再假设第二步是将某一列填充数字b,然后我们发现,以后的任何一次列操作也都只会影响到n-1个格子。所以我们只需要维护两个量来记录当前任何一行还剩多少格子需要填充,以及任何一列还剩多少格子需要填充,这样当我们复盘操作的时候,就可以知道实际该行或该列只增加了多少sum。 diff --git a/Others/2731.Movement-of-Robots/2731.Movement-of-Robots.cpp b/Others/2731.Movement-of-Robots/2731.Movement-of-Robots.cpp new file mode 100644 index 000000000..c00939397 --- /dev/null +++ b/Others/2731.Movement-of-Robots/2731.Movement-of-Robots.cpp @@ -0,0 +1,29 @@ +using LL = long long; +LL M = 1e9+7; +class Solution { +public: + int sumDistance(vector& nums, string s, int d) + { + int n = nums.size(); + vectorpos; + for (int i=0; i& nums) + { + int n = nums.size(); + int ret = 0; + + for (int i=0; ivals(1005); + for (int j=i; j& nums) + { + int n = nums.size(); + + int ret = 0; + for (int i=0; i=0; j--) + { + if (nums[j]==nums[i]+1) + { + prevInvalid = j; + break; + } + if ((nums[j]>nums[i]+1) && prevLargerThanOne==-1) + prevLargerThanOne = j; + } + + int afterInvalid = n; + int afterLargerThanOne = n; + for (int j=i+1; jnums[i]+1) && afterLargerThanOne==n) + afterLargerThanOne = j; + } + + int a = i - prevInvalid; + int b = afterInvalid - i; + int c = i - max(prevInvalid, prevLargerThanOne); + int d = min(afterInvalid, afterLargerThanOne) - i; + + ret += max(0, a*b - c*d); + } + + return ret; + } +}; diff --git a/Others/2763.Sum-of-Imbalance-Numbers-of-All-Subarrays/2763.Sum-of-Imbalance-Numbers-of-All-Subarrays_v3.cpp b/Others/2763.Sum-of-Imbalance-Numbers-of-All-Subarrays/2763.Sum-of-Imbalance-Numbers-of-All-Subarrays_v3.cpp new file mode 100644 index 000000000..6f1bcce7b --- /dev/null +++ b/Others/2763.Sum-of-Imbalance-Numbers-of-All-Subarrays/2763.Sum-of-Imbalance-Numbers-of-All-Subarrays_v3.cpp @@ -0,0 +1,56 @@ +class Solution { +public: + int sumImbalanceNumbers(vector& nums) + { + int n = nums.size(); + + vectorprevInvalid(n, -1); + vectorval2pos(1005,-1); + for (int i=0; iafterInvalid(n+1, n); + for (int i=0; i<1005; i++) val2pos[i] = n; + for (int i=n-1; i>=0; i--) + { + afterInvalid[i] = min(val2pos[nums[i]], val2pos[nums[i]+1]); + val2pos[nums[i]] = i; + } + + vectorprevLargerThanOne(n, -1); + stackst; + for (int i=0; iafterLargerThanOne(n, n); + while (!st.empty()) st.pop(); + for (int i=n-1; i>=0; i--) + { + while (!st.empty() && nums[st.top()] <= nums[i]+1) + st.pop(); + if (!st.empty()) afterLargerThanOne[i] = st.top(); + st.push(i); + } + + int ret = 0; + for (int i=0; inums[i]+1·)。同时从i往后推,找到第一个afterLargerThanOne的位置。这样就有`(i-prevLargerThanOne)*(afterLargerThanOne-i)`个必然包含nums[i]的subarray,使得nums[i]在排序后是最后一个(因为没有其他合法元素可以排在它后面)。我们记做`c*d`. + +所以`a*b-c*d`就是nums[i]可以贡献的subarray的个数,使得它在这些subarray里面贡献的是一个合法的index. + +特别注意,之前计算的prevLargerThanOne不能往前超越prevInvalid;同理afterLargerThanOne不能往后超越afterInvalid,这是因为`c*d`的计数前提依然是valid(即不能有与nums[i]相同数值或者相同数值+1的元素存在)。 + +#### 解法3: +上述的解法2是`o(N^2)`的复杂度。运用预处理可以进一步优化到o(N)。 + +我们用Hash表(记录val->pos)从前往后扫一遍,就可以知道任何nums[i]的prevInvalid的位置。 + +我们再利用单调栈的计数从前往后扫一遍,就可以知道任何nums[i]的prevLargerThanOne的位置,具体做法和求prevGreaterElement几乎一样。 diff --git a/Others/2768.Number-of-Black-Blocks/2768.Number-of-Black-Blocks.cpp b/Others/2768.Number-of-Black-Blocks/2768.Number-of-Black-Blocks.cpp new file mode 100644 index 000000000..1e20e5394 --- /dev/null +++ b/Others/2768.Number-of-Black-Blocks/2768.Number-of-Black-Blocks.cpp @@ -0,0 +1,35 @@ +using LL = long long; +class Solution { + int n; +public: + LL encode(LL x, LL y) + { + return x*n + y; + } + + vector countBlackBlocks(int m, int n, vector>& coordinates) + { + unordered_mapMap; + this->n = n; + + int count = 0; + for (auto& c: coordinates) + { + int x = c[0], y = c[1]; + for (int i=x-1; i<=x; i++) + for (int j=y-1; j<=y; j++) + { + if (i>=0 && i=0 && jrets(5); + for (auto [k,v]: Map) + rets[v]+=1; + + rets[0] = LL(m-1)*LL(n-1) - rets[1] - rets[2] - rets[3] -rets[4]; + + return rets; + } +}; diff --git a/Others/2768.Number-of-Black-Blocks/Readme.md b/Others/2768.Number-of-Black-Blocks/Readme.md new file mode 100644 index 000000000..a10a8cbc6 --- /dev/null +++ b/Others/2768.Number-of-Black-Blocks/Readme.md @@ -0,0 +1,7 @@ +### 2768.Number-of-Black-Blocks + +为了不重不漏地数block,我们需要定义cell与block的关系。我们令每个block左上角的cell作为该block的“代表”,那么数block就转换成了数cell。 + +对于每个black cell,我们设想它可能属于block。显然,它最多属于四个不同的block,这些block对应的“代表”就是(x-1,y-1),(x,y-1),(x-1,y),(x,y).于是我们只需要给这四个block(的代表)各自加上一票即可。最终,每个block(的代表)所得的票数就意味着它所包含的black cell的个数。 + +注意,在右边界和下边界的cell是不能代表一个合法的block的。 diff --git a/Others/2772.Apply-Operations-to-Make-All-Array-Elements-Equal-to-Zero/2772.Apply-Operations-to-Make-All-Array-Elements-Equal-to-Zero.cpp b/Others/2772.Apply-Operations-to-Make-All-Array-Elements-Equal-to-Zero/2772.Apply-Operations-to-Make-All-Array-Elements-Equal-to-Zero.cpp new file mode 100644 index 000000000..8abebd858 --- /dev/null +++ b/Others/2772.Apply-Operations-to-Make-All-Array-Elements-Equal-to-Zero/2772.Apply-Operations-to-Make-All-Array-Elements-Equal-to-Zero.cpp @@ -0,0 +1,22 @@ +class Solution { +public: + bool checkArray(vector& nums, int k) + { + if (k==1) return true; + int n = nums.size(); + vectordiff(n+1, 0); + + int cur = 0; + for (int i=0; inums[i]) return false; + int delta = nums[i] - cur; + if (delta > 0 && i+k < n) + diff[i+k] -= delta; + cur += delta; + } + + return cur+diff[n-1] == nums[n-1]; + } +}; diff --git a/Others/2772.Apply-Operations-to-Make-All-Array-Elements-Equal-to-Zero/Readme.md b/Others/2772.Apply-Operations-to-Make-All-Array-Elements-Equal-to-Zero/Readme.md new file mode 100644 index 000000000..ff794a194 --- /dev/null +++ b/Others/2772.Apply-Operations-to-Make-All-Array-Elements-Equal-to-Zero/Readme.md @@ -0,0 +1,13 @@ +### 2745.Construct-the-Longest-New-String + +我们将问题反过来看,就是问是否能将一个长度为n的全0数组,通过若干次的“k-size subarray +1”操作变成nums。 + +显然,对于第一个元素,想实现0->nums[0],相差`delta=nums[0]-0`,我们必须通过将[0:k-1]整体增加`delta`来实现。 + +此时观察第二个元素,已经是nums[0]了。如果这个数值大于nums[1],显然我们无法通过任何只增不减的操作实现变换,故返回false。否则意味着我们还差`delta=nums[1]-nums[0]`,必须需要将[1:k]整体提升`delta`。 + +从上面的过程我们已经发现规律。从前往后遍历时,每个位置i可能已经有了某个数值(受之前操作的影响)。为了实现与预定目标nums[i]的匹配(假设相差delta),那必须进行操作将[i:i+k-1]整体提升delta。而这些操作会影响到后续位置的数值。我们通过当前值与预期的nums[i]的大小关系,可以判定是否无解。 + +最终,如果最后一个元素的当前值与nums[n-1]完全一致时,说明整套操作能够实现目标。 + +很明显,对于区间整体的增减,我们需要差分数组来标记。比如,我们要将[i:i+k-1]整体提升d,那么只需要标记`diff[i]+=d, diff[i+k]-=d`即可. 从零开始,一路前往后累积diff差分即可恢复每个位置上的数值。 diff --git a/Others/2808.Minimum-Seconds-to-Equalize-a-Circular-Array/2808.Minimum-Seconds-to-Equalize-a-Circular-Array.cpp b/Others/2808.Minimum-Seconds-to-Equalize-a-Circular-Array/2808.Minimum-Seconds-to-Equalize-a-Circular-Array.cpp new file mode 100644 index 000000000..1b50322d7 --- /dev/null +++ b/Others/2808.Minimum-Seconds-to-Equalize-a-Circular-Array/2808.Minimum-Seconds-to-Equalize-a-Circular-Array.cpp @@ -0,0 +1,24 @@ +class Solution { +public: + int minimumSeconds(vector& nums) + { + unordered_map>Map; + for (int i=0; i& nums, int m) + { + if (nums.size()<=2) return true; + for (int i=1; i=m) return true; + return false; + } +}; diff --git a/Others/2811.Check-if-it-is-Possible-to-Split-Array/2811.Check-if-it-is-Possible-to-Split-Array_v2.cpp b/Others/2811.Check-if-it-is-Possible-to-Split-Array/2811.Check-if-it-is-Possible-to-Split-Array_v2.cpp new file mode 100644 index 000000000..a07c81f19 --- /dev/null +++ b/Others/2811.Check-if-it-is-Possible-to-Split-Array/2811.Check-if-it-is-Possible-to-Split-Array_v2.cpp @@ -0,0 +1,21 @@ +class Solution { +public: + bool canSplitArray(vector& nums, int m) + { + int n = nums.size(); + nums.insert(nums.begin(), 0); + vectorpresum(n+1); + for (int i=1; i<=n; i++) + presum[i] = presum[i-1]+nums[i]; + + vector>dp(n+1, vector(n+1, 1)); + for (int len=3; len<=n; len++) + for (int i=1; i+len-1<=n; i++) + { + int j = i+len-1; + dp[i][j] = (dp[i][j-1]&&(presum[j-1]-presum[i-1]>=m)) || (dp[i+1][j]&&(presum[j]-presum[i]>=m)); + } + + return dp[1][n]; + } +}; diff --git a/Others/2811.Check-if-it-is-Possible-to-Split-Array/Readme.md b/Others/2811.Check-if-it-is-Possible-to-Split-Array/Readme.md new file mode 100644 index 000000000..592839086 --- /dev/null +++ b/Others/2811.Check-if-it-is-Possible-to-Split-Array/Readme.md @@ -0,0 +1,15 @@ +### 2811.Check-if-it-is-Possible-to-Split-Array + +#### 解法1 +本题的线性解法其实非常简单,只需要检查是否存在两个连续的元素之和大于等于m即可。 + +充分性:假设存在,那么我们在每一步切除的过程中保留上述两个元素,就能使操作不断进行下去。 + +必要性:假设不存在,那么无论用什么方法,当我们切到只剩三个元素的块时,根据题意一定无法继续切下去。 + +#### 解法2 +有动态规划的N^2解法。令dp[i][j]表示区间[i:j]是否可以根据规则切到最后。那么我们就有转移方程: +``` +dp[i][j] = (dp[i+1][j] && sum[i+1:j]>=m) || (dp[i][j-1] && sum[i:j-1]>=m) +``` +我们用二维循环,从小窗口的dp推导出大窗口的dp,最终返回dp[0][n-1]. diff --git a/Others/2818.Apply-Operations-to-Maximize-Score/2818.Apply-Operations-to-Maximize-Score.cpp b/Others/2818.Apply-Operations-to-Maximize-Score/2818.Apply-Operations-to-Maximize-Score.cpp new file mode 100644 index 000000000..9ce734fb6 --- /dev/null +++ b/Others/2818.Apply-Operations-to-Maximize-Score/2818.Apply-Operations-to-Maximize-Score.cpp @@ -0,0 +1,90 @@ +using LL = long long; +LL M = 1e9+7; +using PII=pair; +class Solution { +public: + LL quickMul(LL x, LL N) { + if (N == 0) { + return 1; + } + LL y = quickMul(x, N / 2) % M; + return N % 2 == 0 ? (y * y % M) : (y * y % M * x % M); + } + + vectorEratosthenes(int n) + { + vectorq(n+1,0); + for (int i=2; i<=n; i++) + { + if (q[i]>=1) continue; + q[i] = 1; + int j=i*2; + while (j<=n) + { + q[j]+=1; + j+=i; + } + } + return q; + } + + int maximumScore(vector& nums, int k) + { + LL n = nums.size(); + int MAX = *max_element(nums.begin(), nums.end()); + vectors = Eratosthenes(MAX); + + vectorscores(n); + for (int i=0; iprevLarger(n, -1); + stackStack; + for (int i=0; inextLarger(n, n); + while (!Stack.empty()) Stack.pop(); + for (int i=n-1; i>=0; i--) + { + while (!Stack.empty() && scores[Stack.top()] <= scores[i]) + Stack.pop(); + if (!Stack.empty()) + nextLarger[i] = Stack.top(); + Stack.push(i); + } + + vector temp(n); + for (int i=0; i= t) + { + ret = ret * quickMul(num, t) % M; + k -= t; + } + else + { + ret = ret * quickMul(num, k) % M; + k = 0; + } + if (k==0) break; + } + + return ret; + } +}; diff --git a/Others/2818.Apply-Operations-to-Maximize-Score/Readme.md b/Others/2818.Apply-Operations-to-Maximize-Score/Readme.md new file mode 100644 index 000000000..e7274f5dd --- /dev/null +++ b/Others/2818.Apply-Operations-to-Maximize-Score/Readme.md @@ -0,0 +1,17 @@ +### 2818.Apply-Operations-to-Maximize-Score + +这道题是很多套路和知识点的大杂烩。 + +首先,根据题意,我们要在n^2个区间里挑选k个区间。这n^2个区间里,有的x可以很大,有的x会很小。我们不会去遍历所有这n^2个区间、再根据他们的x排序。相比之下,x的取值范围只有n种(即nums里的n个元素),通过遍历x来枚举区间的效率更高。 + +显然,我们必然会贪心地使用“x最大”的那些区间,我们将nums数组里最大元素记做nums[i]。那么有多少区间的`highest prime score`是nums[i]呢?假设每个元素的`prime score`我们都已经提前计算好了,记做scores[i],那么我们寻找i左边第一个大于等于scores[i]的位置left,以及右边第一个大于scores[i]的位置right,那么符合条件的区间的左边界就可以在(left,i)之间任意选取,右边界就可以在(i,right)之间任意选取,任意配对之后总共的区间个数就是`(i-left)*(right-i)`. 也就是说,在最终选取的k个区间里,我们优先选取这`(i-left)*(right-i)`个区间,因为每次都可以让结果乘以nums[i](全局最大的x)。 + +以此类推,我们再贪心地使用“x第二大”的那些区间,记做nums[j]。同理计算出有多少个区间满足scores[j]是最大元素。当k还没选够时,我们就会优先使用这些区间。 + +再找nums第三大元素、第四大元素... 直至把k个区间都用完。 + +以上就是本题的大致思路。其中还有不少小问题。比如 + +1. 怎么预处理得到scores数组?可以用埃氏筛的思路,在根据某个质因数向上筛除合数时,可以顺便给该合数增1,就可以记录下每个数的distinct质因数的个数了。 +2. 如何求`previous larger or equal number`和`next larger number`,这是单调栈的经典应用了。 +3. 假设某个数x对应的区间有P个,那么我们就要`ret *= x^P`,其中P可能很大,所以需要调用快速幂的libary。 diff --git a/Others/2857.Count-Pairs-of-Points-With-Distance-k/2857.Count-Pairs-of-Points-With-Distance-k.cpp b/Others/2857.Count-Pairs-of-Points-With-Distance-k/2857.Count-Pairs-of-Points-With-Distance-k.cpp new file mode 100644 index 000000000..79b1b0eb2 --- /dev/null +++ b/Others/2857.Count-Pairs-of-Points-With-Distance-k/2857.Count-Pairs-of-Points-With-Distance-k.cpp @@ -0,0 +1,30 @@ +class Solution { +public: + int countPairs(vector>& coordinates, int k) + { + int ret = 0; + unordered_mapMap; + for (int i=0; i& prices) + { + int n = prices.size(); + vectorarr(n); + for (int i=0; iMap; + for (int i=0; i& nums) + { + int n = nums.size(); + unordered_mapright; + for (int i=0; ileft; + for (int i=n-1; i>=0; i--) + left[nums[i]] = i; + + vectordiff(n); + for (auto [k,v]: left) + { + diff[left[k]]+=1; + diff[right[k]]-=1; + } + + int count = 0; + int sum = 0; + for (int i=0; iarr; + while (a>0) + { + arr.push_back(a%2); + a/=2; + } + if (arr[i]==1) + { + LL b = 0; + for (int j=arr.size()-1; j>i; j--) + b = b*2+arr[j]; + ret += b * pow(2, i); + b = 0; + for (int j=i-1; j>=0; j--) + b = b*2+arr[j]; + ret += b+1; + } + else + { + LL b = 0; + for (int j=arr.size()-1; j>i; j--) + b = b*2+arr[j]; + ret += b * pow(2, i); + } + + if (ret > k) return false; + } + + return true; + } +}; diff --git a/Others/3007.Maximum-Number-That-Sum-of-the-Prices-Is-Less-Than-or-Equal-to-K/Readme.md b/Others/3007.Maximum-Number-That-Sum-of-the-Prices-Is-Less-Than-or-Equal-to-K/Readme.md new file mode 100644 index 000000000..a77e2165e --- /dev/null +++ b/Others/3007.Maximum-Number-That-Sum-of-the-Prices-Is-Less-Than-or-Equal-to-K/Readme.md @@ -0,0 +1,13 @@ +### 3007.Maximum-Number-That-Sum-of-the-Prices-Is-Less-Than-or-Equal-to-K + +首先我们很容易看出此题的答案具有单调性。答案越大,就有越多的bit 1能够被计入;反之,被计入的bit 1会越少。所以整体的框架就是一个二分,核心就是制定一个上限A,想问从1到A的所有自然数的二进制表达里,总共有多少个bit 1出现在第x位、第2x位、... + +这个问题和`LC 233.Number-of-Digit-One`非常相似。我们不会固定一个数,再数里面有多少个bit 1;而是相反的策略,对于某位bit,我们计算有多少个数会在该bit的值是1. + +我们令上限A表达为`XXX i YYY`,考虑从1到A总共多少个自然数在第i位bit上的值是1呢? + +如果A[i]==0,那么高位部分可以是任意000 ~ (XXX-1),低位部分可以是任意 000 ~ 999。两处的任意组合,都可以保证整体的数值不超过上限A。这样的数有`XXX * 2^t`种,其中t表示`YYY`的位数。此外没有任何数可以满足要求。 + +如果A[i]==1,那么高位部分可以是任意000 ~ (XXX-1),低位部分可以是任意 000 ~ 999。两处的任意组合,都可以保证整体的数值不超过上限A。同样,这样的数有`XXX * 2^t`种,其中t表示`YYY`的位数。。此外,当高位恰好是`XXX`时,低位可以是从000~YYY,这样就额外有`YYY+1`种。 + +以上就统计了从1-A的所有自然数有多少个在第i位bit是1。我们再循环处理下一个的bit(隔x位)即可。 diff --git a/Others/3009.Maximum-Number-of-Intersections-on-the-Chart/3009.Maximum-Number-of-Intersections-on-the-Chart.cpp b/Others/3009.Maximum-Number-of-Intersections-on-the-Chart/3009.Maximum-Number-of-Intersections-on-the-Chart.cpp new file mode 100644 index 000000000..e9e27cee7 --- /dev/null +++ b/Others/3009.Maximum-Number-of-Intersections-on-the-Chart/3009.Maximum-Number-of-Intersections-on-the-Chart.cpp @@ -0,0 +1,34 @@ +class Solution { +public: + int maxIntersectionCount(vector& y) + { + int n = y.size(); + mapMap; + for (int i=1; i& nums, int k, vector>& edges) + { + vector>diff; + for (int x: nums) + { + diff.push_back({(x^k)-x, x}); + } + + sort(diff.rbegin(), diff.rend()); + + LL max_diff = 0; + LL total_diff = 0; + for (int i=0; i+1 max_diff) + { + max_diff = total_diff; + } + } + + LL total = 0; + for (int x: nums) total += x; + + return total + max_diff; + + } +}; diff --git a/Others/3068.Find-the-Maximum-Sum-of-Node-Values/Readme.md b/Others/3068.Find-the-Maximum-Sum-of-Node-Values/Readme.md new file mode 100644 index 000000000..824bc4941 --- /dev/null +++ b/Others/3068.Find-the-Maximum-Sum-of-Node-Values/Readme.md @@ -0,0 +1,5 @@ +### 3068.Find-the-Maximum-Sum-of-Node-Values + +此题本质和树的结构没有任何关系,假设a与b相连,b与c相连。那么我们对(a,b)和(b,c)同时操作的话,就相当于直接对(a,c),而b没有变化。故题意转换一下,就是在数组nums里,每次可以任意选取两个元素同时与k进行异或操作(不用考虑edge的关系)。问最终可以得到的最大的数组之和是多少。 + +考虑到任何一个元素,对k进行偶数次的异或操作,等同于没有进行操作。所以我们只会对每个元素进行最多一次操作。显然,我们会选择那些“增量”更大的元素进行操作。所以我们将每个元素的增量`(x^k)-x`按照从大到小的顺序排序,理论上将所有增量为正的元素进行操作,得到的和肯定最大。但是题意要求每次同时对两个元素进行操作,所以我们依次尝试前2个、前4个、前6个...所有取前偶数个元素的方案进行尝试。挑选其中“增量之和”最大的。最终的答案,就是sum(nums)加上最大的“增量之和”。 diff --git a/Others/3169.Count-Days-Without-Meetings/3169.Count-Days-Without-Meetings.cpp b/Others/3169.Count-Days-Without-Meetings/3169.Count-Days-Without-Meetings.cpp new file mode 100644 index 000000000..a208395fd --- /dev/null +++ b/Others/3169.Count-Days-Without-Meetings/3169.Count-Days-Without-Meetings.cpp @@ -0,0 +1,33 @@ +class Solution { +public: + int countDays(int days, vector>& meetings) + { + mapMap; + for (auto& meeting: meetings) + { + int a = meeting[0], b = meeting[1]; + Map[a]++; + Map[b+1]--; + } + Map[days+1]+=1; + + int sum = 0; + int cur = 1; + int total = 0; + for (auto [k,v]:Map) + { + if (sum==0 && sum+v>0) + { + total += k-cur; + } + else if (sum>0 && sum+v==0) + { + cur = k; + } + + sum += v; + } + + return total; + } +}; diff --git a/Others/3169.Count-Days-Without-Meetings/Readme.md b/Others/3169.Count-Days-Without-Meetings/Readme.md new file mode 100644 index 000000000..71ed74e6c --- /dev/null +++ b/Others/3169.Count-Days-Without-Meetings/Readme.md @@ -0,0 +1,7 @@ +### 3169.Count-Days-Without-Meetings + +很明显这是一道扫描线的题目。对于每个区间[s,e]的会议,我们记录`Map[s]++`和`Map[e+1]--`. 最后我们将Map里的所有key按照时间顺序走一遍,累加差分值至count。 + +当count从正数降为零时,说明没有任何会议,此时记录当前日期cur。当count从零变成正数时,说明出现了会议,那么就将当前日期减去cur,即为最近一段没有会议的时长。 + +最终统计所有无会议的时长之和。 diff --git a/Others/3400.Maximum-Number-of-Matching-Indices-After-Right-Shifts/3400.Maximum-Number-of-Matching-Indices-After-Right-Shifts.cpp b/Others/3400.Maximum-Number-of-Matching-Indices-After-Right-Shifts/3400.Maximum-Number-of-Matching-Indices-After-Right-Shifts.cpp new file mode 100644 index 000000000..2fc898808 --- /dev/null +++ b/Others/3400.Maximum-Number-of-Matching-Indices-After-Right-Shifts/3400.Maximum-Number-of-Matching-Indices-After-Right-Shifts.cpp @@ -0,0 +1,19 @@ +class Solution { +public: + int maximumMatchingIndices(vector& nums1, vector& nums2) + { + int ret = 0; + int n = nums1.size(); + unordered_map>Map; + for (int i=0; iscores(n); + for (int i=0; i& nums) + { + unordered_map>Map; + int n = nums.size(); + for (int i=0; i assignElements(vector& groups, vector& elements) + { + int n = *max_element(groups.begin(), groups.end()); + vectorarr(n+1, -1); + + for (int j=0; jn) continue; + + if (arr[x0]!=-1) continue; + + int x = x0; + while (x<=n) + { + if (arr[x]==-1) + arr[x] = j; + x+=x0; + } + } + + vectorrets; + for (int g: groups) + rets.push_back(arr[g]); + return rets; + } +}; diff --git a/Others/3447.Assign-Elements-to-Groups-with-Constraints/Readme.md b/Others/3447.Assign-Elements-to-Groups-with-Constraints/Readme.md new file mode 100644 index 000000000..3e6d9e611 --- /dev/null +++ b/Others/3447.Assign-Elements-to-Groups-with-Constraints/Readme.md @@ -0,0 +1,7 @@ +### 3447.Assign-Elements-to-Groups-with-Constraints + +突破点在于groups里的元素的数值不超过1e5.在这个范围是,如果枚举所有1的倍数,然后枚举所有2的倍数,然后枚举所有3的倍数,直至枚举n的倍数,那么总共的时间复杂度是`n+n/2+n/3+...n/n = n*(1+1/2+1/3+...1/n)`.这个级数虽然不收敛,但是它是趋近于nlog(n)的。所以本题可以用暴力枚举。 + +所以本题的算法很简单。我们开一个长度为1e5的数组assign,来记录每个自然数最早能被哪个element所assign。我们依次考察element里的每个元素,比如说elements[j]=x,然后枚举x的所有倍数(直至1e5),比如说kx,那样就有`assign[kx] = j`,当然根据题意,我们对于每个assign我们只更新一次。 + +最后根据groups的数值,从assgin里把答案拷贝过去即可。 diff --git a/Others/3628.Maximum-Number-of-Subsequences-After-One-Inserting/3628.Maximum-Number-of-Subsequences-After-One-Inserting.cpp b/Others/3628.Maximum-Number-of-Subsequences-After-One-Inserting/3628.Maximum-Number-of-Subsequences-After-One-Inserting.cpp new file mode 100644 index 000000000..5520f4486 --- /dev/null +++ b/Others/3628.Maximum-Number-of-Subsequences-After-One-Inserting/3628.Maximum-Number-of-Subsequences-After-One-Inserting.cpp @@ -0,0 +1,43 @@ +class Solution { +public: + long long numOfSubsequences(string s) { + int n = s.size(); + s = "#"+s; + vectorleftL(n+2); + vectorrightT(n+2); + for (int i=1; i<=n; i++) + leftL[i] = leftL[i-1]+(s[i-1]=='L'); + for (int i=n; i>=1; i--) + rightT[i] = rightT[i+1]+(s[i+1]=='T'); + + long long count = 0; + for (int i=1; i<=n; i++) { + if (s[i]=='C') + count += leftL[i]*rightT[i]; + } + + long long ret = 0; + + long long CT = 0; + long long T = 0; + for (int i=n; i>=1; i--) { + T += s[i]=='T'; + if (s[i]=='C') CT += T; + ret = max(ret, count+CT); + } + + long long LC = 0; + long long L= 0; + for (int i=1; i<=n; i++) { + L += s[i]=='L'; + if (s[i]=='C') LC += L; + ret = max(ret, count+LC); + } + + for (int i=1; i<=n-1; i++) { + ret = max(ret, count+leftL[i+1]*rightT[i]); + } + + return ret; + } +}; diff --git a/Others/3628.Maximum-Number-of-Subsequences-After-One-Inserting/Readme.md b/Others/3628.Maximum-Number-of-Subsequences-After-One-Inserting/Readme.md new file mode 100644 index 000000000..ecdca9f4e --- /dev/null +++ b/Others/3628.Maximum-Number-of-Subsequences-After-One-Inserting/Readme.md @@ -0,0 +1,16 @@ +### 3628.Maximum-Number-of-Subsequences-After-One-Inserting + +这道题的迷惑性在于很容易误导用DP来解。我们容易设计成dp[i][j][k]表示前i个元素里、用了j次insertion(0,1),能出现多少个以字符k结尾(L,C,T)的合法subsequence。但是这个dp值实际上允许我们在任意位置插入,并且统计了所有的subsequence的总和。这与题意要求是不一样。题目里要求的是确定一个insertion位置的情况下,能出现多少个合法的subsequence。在所有插入的位置中,再选一个最大的结果。 + +本题的正解是利用subsequence的长度仅有3,以中心字符为C为入手点,考察左边L和右边T的分布与组合。具体分情况讨论如下: + +1. 不考虑任何插入操作。我们预处理得到leftL[i]表示i左边有多少个L,rightT[i]表示i右边有多少个T。于是对于每个字符如果s[i]='C',那么`leftL[i]*rightT[i]`就是以i为中心的subsequence的个数。然后全局累加,结果记为count。 + +2. 考虑插入L,我们需要计算以这个新插入的L开头的合法子序列的个数。假设我们将L插在i的左边,那么等价于计算i右边(包括i)存在多少个CT。我们可以从右往左遍历,对于每个C,统计它右边有多少个T,这样就可以加入CT的统计。 + +3. 同理考虑插入T,我们需要计算以这个新插入的T结尾的合法子序列的个数。假设我们将T插在i的右边,那么等价于计算i左边(包括i)存在多少个LC。我们可以从左往右遍历,对于每个C,统计它左边有多少个L,这样就可以加入LC的统计。 + +4. 最后考虑插入C。这个比较简单,假设我们将C插在i的左边,那么`leftL[i]*rightT[i-1]`就是以该C为中心的subsequence的个数。 + +注意,最终我们需要取2,3,4三种答案的最大值,与第一种情况的count相加。 + diff --git a/Others/3640.Trionic-Array-II/3640.Trionic-Array-II.cpp b/Others/3640.Trionic-Array-II/3640.Trionic-Array-II.cpp new file mode 100644 index 000000000..91a00bf10 --- /dev/null +++ b/Others/3640.Trionic-Array-II/3640.Trionic-Array-II.cpp @@ -0,0 +1,56 @@ +using ll = long long; +class Solution { +public: + vector>split(vector&arr) { + int n = arr.size(); + vector> result; + int i = 0; + while (i < n) { + int j = i; + while (j + 1 < n && arr[j + 1] < arr[j]) { + j++; + } + if (j > i) { + result.push_back({i, j}); + } + i = j+1; + } + return result; + } + + long long maxSumTrionic(vector& nums) { + int n = nums.size(); + vector>arr = split(nums); + + ll ret = LLONG_MIN/2; + + for (int i=0; i=n) continue; + if(nums[x-1]>=nums[x]) continue; + if(nums[y+1]<=nums[y]) continue; + + ll sum = nums[x-1]; + ll maxSum1 = nums[x-1]; + for (int j=x-2; j>=0; j--) { + if (nums[j]>=nums[j+1]) break; + sum += nums[j]; + maxSum1 = max(maxSum1, sum); + } + + sum = nums[y+1]; + ll maxSum2 = nums[y+1]; + for (int j=y+2; j& nums, vector>& queries) { + int n = nums.size(); + vectorm(n, 1); + + int B = 320; + + vector>small_k_queries[B+1]; + + for (auto q: queries) { + int l = q[0], r = q[1], k = q[2], v = q[3]; + if (k>B) { + for (int i=l; i<=r; i+=k) { + m[i] = m[i] * v % M; + } + } else { + small_k_queries[k].push_back(q); + } + } + + for (int k=1; k<=B; k++) { + if (small_k_queries[k].empty()) continue; + vectordiff(n+1, 1); + for (auto&q: small_k_queries[k]) { + int l = q[0], r = q[1], k = q[2], v = q[3]; + r = (r-l)/k*k + l; + diff[l] = diff[l] * v % M; + if (r+k<=n) diff[r+k] = diff[r+k] * inv(v) % M; + } + + vectorthis_round_m(n+1, 1); + for (int i=0; i=k?this_round_m[i-k]:1) * diff[i] % M; + } + + for (int i=0; i& nums) { + int n = nums.size(); + vectordivisors; + for (int d=1; 1LL*d*d<=n; d++) { + if (n%d==0) { + divisors.push_back(d); + if (d*d!=n) divisors.push_back(n/d); + } + } + int ret = 0; + for (int k: divisors) { + if (checkOK(nums, k)) { + ret+=k; + } + + } + return ret; + } + + bool checkOK(vector&nums, int k) { + int n = nums.size(); + bool first = true; + int prevMax = 0; + + for (int s=0; snums[j]) + desc++; + if (desc>1) return false; + + mn = min(mn, nums[i]); + mx = max(mx, nums[i]); + } + + if (!first && prevMax > mn) return false; + first = false; + prevMax = mx; + } + return true; + } +}; diff --git a/Others/3886.Sum-of-Sortable-Integers/Readme.md b/Others/3886.Sum-of-Sortable-Integers/Readme.md new file mode 100644 index 000000000..0dc615477 --- /dev/null +++ b/Others/3886.Sum-of-Sortable-Integers/Readme.md @@ -0,0 +1,7 @@ +### 3886.Sum-of-Sortable-Integers + +这道题只需暴力枚举n的所有约数,对于每个约数k进行尝试即可。这是因为n的约数个数的渐近行为为ln(n)。因此我们首先用sqrt(n)的复杂度列出所有的divisors,再对每个divisor切分数组进行判定,总时间复杂度只需要nlog(n)。 + +有一处代码细节。对于一个区间[s,s+k)而言,如何高效地判定它是Cyclically递增的呢?有一个很巧妙的做法,就是将区间首元素append到最后一位,然后对于这个长度为k+1的区间,两两比较相邻元素,只允许出现一次下降。出现一次以上的下降,就肯定不是Cyclically递增的。 + + diff --git a/Others/440.K-th-Smallest-in-Lexicographical-Order/440.K-th-Smallest-in-Lexicographical-Order.cpp b/Others/440.K-th-Smallest-in-Lexicographical-Order/440.K-th-Smallest-in-Lexicographical-Order.cpp new file mode 100644 index 000000000..a6a2f39ac --- /dev/null +++ b/Others/440.K-th-Smallest-in-Lexicographical-Order/440.K-th-Smallest-in-Lexicographical-Order.cpp @@ -0,0 +1,54 @@ +class Solution { +public: + int findKthNumber(int n, int k) + { + return FindKthNUmberBeginWith(0, n, k); + } + + // return the Lexicographically k-th element that begins with prefix + // if k==0, return prefix itself + int FindKthNUmberBeginWith(int prefix, int n, int k) + { + if (k==0) return prefix; + + int start = (prefix == 0) ? 1 : 0; + for (int i=start; i<=9; i++) + { + int count = 1 + TotalNumbersBeginWith(prefix*10+i, n); + if (count < k) + k = k-count; + else + return FindKthNUmberBeginWith(prefix*10+i, n, k-1); + } + + return -1; + } + + // return how many numbers that begin with prefix (and <= n) + // excluding prefix itself + int TotalNumbersBeginWith(long prefix, int n) + { + long count = 0; + long exp = 10; + while (1) + { + long lower = prefix * exp; + long upper = prefix * exp + exp - 1; + if (lower > n) break; + if (lower <= n && upper >= n) + { + count += (n - lower +1); + break; + } + else + { + count += exp; + } + + exp *= 10; + } + + return count; + } +}; + diff --git a/Others/440.K-th-Smallest-in-Lexicographical-Order/Readme.md b/Others/440.K-th-Smallest-in-Lexicographical-Order/Readme.md new file mode 100644 index 000000000..244cbdcfc --- /dev/null +++ b/Others/440.K-th-Smallest-in-Lexicographical-Order/Readme.md @@ -0,0 +1,31 @@ +### 440.K-th-Smallest-in-Lexicographical-Order + +本题初看和```386.Lexicographical-Numbers```非常相似,但解法大不相同.在386题中,因为需要将按照字典序从小到大所有的元素打印出来,所以可以用构造法把这些数都找出来.但本题中,如果K很大,要将从1到K个的字典序元素都生成是很费时的. + +此题可以用递归的思路来拆解每个digit,逐步将k化小。我们先考察所有以1开头的数字`1xx..xx`,它们必然在字典序里是最靠前的一拨。如果它们的个数count1小于k,那么就意味着答案的首数字必然不会是1,我们就可以`k-=count1`。我们再考察所有以2开头的数字`2xx..xx`,同理此时它们必然在字典序里是最靠前的一拨。如果它们的个数count1大于k,说明我们的答案的首数字必然就是2! + +接下来我们同理,处理第二位数字。我们先考察所有以20开头的数字`20xx..xx`,如果它们的个数count20小于k,那么就意味着答案的首两位数字必然不会是20,我们就可以`k-=count20`。我们再考察所有以21开头的数字`21xx..xx`,同理此时它们必然在字典序里是最靠前的一拨。如果它们的个数count21小于k,说明答案的首两位数字必然不会是21,我们继续`k-=count21`。直至我们发现`22xx..xx`的个数count22大于k,说明最终答案的首二位数字就是22. + +所以我们可以重复调用主函数`FindKthNumberBeginWith(prefix, k)`,表示求以prefix为前缀的第k个字典序排列的元素。如果k为0,就输出prefix本身。 + +代码的流程大致如下: +```cpp +int FindKthNumberBeginWith(prefix,k) +{ + if (k==0) return prefix; + + for i=0 to 9 + { + count = TotalNumbersBeginWith(prefix+[i], n); + if (count& A) { int N = A.size(); - vectordiff(N,0); + vectordiff(N+1,0); for (int i=0; i=N次会重复之前的结果,我们只需要开长度为N+1的diff数组即可(多留一个是为了在某些情况下设置“下降沿”的时候保证diff不越界,本身diff[N]的数值并不会用到)。至于为什么取模之后能AC,是因为题目问的是最大score所对应的rotation index k,随意乱写任何值的diff[0]都不会改变sum的变化趋势,也就不会影响对最优k的判定。 + [Leetcode Link](https://leetcode.com/problems/smallest-rotation-with-highest-score) diff --git a/Others/853.Car-Fleet/853.Car-Fleet.cpp b/Others/853.Car-Fleet/853.Car-Fleet.cpp new file mode 100644 index 000000000..766111ce3 --- /dev/null +++ b/Others/853.Car-Fleet/853.Car-Fleet.cpp @@ -0,0 +1,24 @@ +class Solution { +public: + int carFleet(int target, vector& position, vector& speed) + { + int N= position.size(); + + vector>q; + for (int i=0; i=0; i--) + { + double T = (target-q[i].first)*1.0/q[i].second; + int j = i-1; + while (j>=0 && (target-q[j].first)*1.0/q[j].second <= T) + j--; + count++; + i = j+1; + } + return count; + } +}; diff --git a/Others/853.Car-Fleet/Readme.md b/Others/853.Car-Fleet/Readme.md new file mode 100644 index 000000000..67f7f3e3e --- /dev/null +++ b/Others/853.Car-Fleet/Readme.md @@ -0,0 +1,5 @@ +### 853.Car-Fleet + +我们判断一辆车是否会与前车相撞,只要考察该车到达终点的时间是否小于前车到达终点的时间。所以最终能组成一个fleet的车辆,必然是一段连续区间的车,且该区间里的所有车都会撞上此区间最右边的领头车。 + +所以本题的算法是,从右往左遍历每辆车A,考察它作为领头车的话,它后面会有连续几辆车能在到达终点前撞上它,这个区间就是一个fleet。如果发现后面的某辆车B不会撞上它,那么B就是另一个fleet的领头车了。 diff --git a/Priority_Queue/2599.Make-the-Prefix-Sum-Non-negative/2599.Make-the-Prefix-Sum-Non-negative.cpp b/Priority_Queue/2599.Make-the-Prefix-Sum-Non-negative/2599.Make-the-Prefix-Sum-Non-negative.cpp new file mode 100644 index 000000000..ffeb7dd93 --- /dev/null +++ b/Priority_Queue/2599.Make-the-Prefix-Sum-Non-negative/2599.Make-the-Prefix-Sum-Non-negative.cpp @@ -0,0 +1,30 @@ +class Solution { +public: + int makePrefSumNonNegative(vector& nums) + { + priority_queuepq; + long long sum = 0; + int ret = 0; + + for (int x: nums) + { + if (x >= 0) + sum += x; + else if (sum + x >=0) + { + sum += x; + pq.push(abs(x)); + } + else + { + pq.push(abs(x)); + sum += x; + int y = pq.top(); + pq.pop(); + sum += y; + ret++; + } + } + return ret; + } +}; diff --git a/Priority_Queue/2599.Make-the-Prefix-Sum-Non-negative/Readme.md b/Priority_Queue/2599.Make-the-Prefix-Sum-Non-negative/Readme.md new file mode 100644 index 000000000..0299a2d5d --- /dev/null +++ b/Priority_Queue/2599.Make-the-Prefix-Sum-Non-negative/Readme.md @@ -0,0 +1,13 @@ +### 2599.Make-the-Prefix-Sum-Non-negative + +本题是典型的反悔贪心。 + +我们一路维护前缀和sum。假设遇到当前的元素x,那么分一下几种情况。 + +场景1,如果x是正数,那么无脑收录。 + +场景2,如果x是负数,并且sum+x>=0,那么我们也会贪心地将其收入前缀,从而减少一次扔元素的操作。 + +场景3,就是如果x是负数,且sum+x<0,那么我们别我他法,必须将x扔走。但是将x扔走的同时,能捞一些什么好处呢?假设之前有某个负数y没有被扔走而是收录进了前缀,并且绝对值的y大于x,那么显然将y扔走比将x扔走更合算,并且将y扔走可以保证可以将x顺利保留在前缀里。 + +所以我们需要将场景2里所有收录过的负数,按照绝对值大小放入一个PQ。当遇到场景3的时候,我们将PQ里的最大值代替x去扔掉(如果大于x的话),这样同样用一次操作,可以获取最大的收益(尽可能地提升前缀和)。 diff --git a/Priority_Queue/2931.Maximum-Spending-After-Buying-Items/2931.Maximum-Spending-After-Buying-Items.cpp b/Priority_Queue/2931.Maximum-Spending-After-Buying-Items/2931.Maximum-Spending-After-Buying-Items.cpp new file mode 100644 index 000000000..aef3ff4e0 --- /dev/null +++ b/Priority_Queue/2931.Maximum-Spending-After-Buying-Items/2931.Maximum-Spending-After-Buying-Items.cpp @@ -0,0 +1,31 @@ +using PII = pair; +class Solution { +public: + long long maxSpending(vector>& values) + { + int m = values.size(); + int n = values[0].size(); + + priority_queue, greater<>>pq; + vectorp(m, n-1); + for (int i=0; i0`. + +所以我们将所有shop里当前available的物品放入一个小顶堆,每次取最小值与当前的d相乘即可。取完一个最小值,就把它对应的shop的下一件available的物品放入PQ。直至取完所有m*n件物品。 diff --git a/Priority_Queue/3645.Maximum-Total-from-Optimal-Activation-Order/3645.Maximum-Total-from-Optimal-Activation-Order.cpp b/Priority_Queue/3645.Maximum-Total-from-Optimal-Activation-Order/3645.Maximum-Total-from-Optimal-Activation-Order.cpp new file mode 100644 index 000000000..69bbc9ba6 --- /dev/null +++ b/Priority_Queue/3645.Maximum-Total-from-Optimal-Activation-Order/3645.Maximum-Total-from-Optimal-Activation-Order.cpp @@ -0,0 +1,51 @@ +using PII = pair; + +class Solution { +public: + long long maxTotal(vector& value, vector& limit) { + int n = value.size(); + + unordered_setpurged; + unordered_mapMap; // value -> How many active elements whose limit is the key + + vector arr; + for (int i = 0; i < n; ++i) { + arr.push_back({value[i], limit[i]}); + } + sort(arr.begin(), arr.end(), [](PII&a, PII&b){ + if (a.second!=b.second) + return a.second < b.second; + else + return a.first > b.first; + }); + priority_queue, greater> pq; + + long long ret = 0; + int active = 0; + + for (auto& [V,L] : arr) { + if (purged.find(L)!=purged.end()) + continue; + + if (L > active) { + ret += V; + pq.push(V); + active += 1; + Map[L]+=1; + + purged.insert(active); + int temp = Map[active]; + Map[active] = 0; + active -= temp; + } else if (!pq.empty() && V > pq.top()) { + ret -= pq.top(); + pq.pop(); + ret += V; + pq.push(V); + Map[L]+=1; + } + } + + return ret; + } +}; diff --git a/Priority_Queue/3645.Maximum-Total-from-Optimal-Activation-Order/Readme.md b/Priority_Queue/3645.Maximum-Total-from-Optimal-Activation-Order/Readme.md new file mode 100644 index 000000000..7ab3a1723 --- /dev/null +++ b/Priority_Queue/3645.Maximum-Total-from-Optimal-Activation-Order/Readme.md @@ -0,0 +1,10 @@ +### 3649.Maximum-Total-from-Optimal-Activation-Order + +基本的思路是反悔贪心。 + +我们将所有元素按照limit从小到大排序(limit相同的情况下,value更大的优先),因为limit小的必须先挑,否则当active_number很大的时候就无法再取了。排序之后逐个考察每个元素: +1. 如果`L>active_number`,那么就可以无脑选取该元素的value。 +2. 反之,意味着我们无法再新加这个元素,但是我们依然有机会更新ret,那就是将已经选取的元素里踢掉最小的一个,替换成当前的这个元素(如果更优的话)。这样的操作是合法的,因为被替换的元素的L更小,在它被选中的那个回合我们“回溯地”为这个L更大的元素是符合规则的。 + +以上就是本题的基本贪心规则。除此之外,题目还有一个规则,就是active_number其实是可以减少的。所以我们需要用一个Hash表,记录每种limit的元素已经有多少被选中了(即active的状态)。当我们的active_number增长到某个数值A的时候,需要再减去Map[A],同时将Map[A]清零,并且以后再次遇到limit是A的元素都直接跳过。 + diff --git a/Readme.md b/Readme.md index a2ae119e9..790526dc7 100644 --- a/Readme.md +++ b/Readme.md @@ -38,6 +38,7 @@ [930.Binary-Subarrays-With-Sum](https://github.com/wisdompeak/LeetCode/tree/master/Hash/930.Binary-Subarrays-With-Sum) (M+) [1004.Max-Consecutive-Ones-III](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/1004.Max-Consecutive-Ones-III) (M) [1052.Grumpy-Bookstore-Owner](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/1052.Grumpy-Bookstore-Owner) (M) +[1358.Number-of-Substrings-Containing-All-Three-Characters](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/1358.Number-of-Substrings-Containing-All-Three-Characters) (M) [1838.Frequency-of-the-Most-Frequent-Element](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/1838.Frequency-of-the-Most-Frequent-Element) (H-) [395.Longest-Substring-with-At-Least-K-Repeating-Characters](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/395.Longest-Substring-with-At-Least-K-Repeating-Characters) (H) [1763.Longest-Nice-Substring](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/1763.Longest-Nice-Substring) (H) @@ -49,15 +50,27 @@ [2411.Smallest-Subarrays-With-Maximum-Bitwise-OR](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/2411.Smallest-Subarrays-With-Maximum-Bitwise-OR) (H-) [2516.Take-K-of-Each-Character-From-Left-and-Right](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/2516.Take-K-of-Each-Character-From-Left-and-Right) (M+) [2564.Substring-XOR-Queries](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/2564.Substring-XOR-Queries) (H-) +[2730.Find-the-Longest-Semi-Repetitive-Substring](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/2730.Find-the-Longest-Semi-Repetitive-Substring) (M+) +[2747.Count-Zero-Request-Servers](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/2747.Count-Zero-Request-Servers) (H-) +[2831.Find-the-Longest-Equal-Subarray](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/2831.Find-the-Longest-Equal-Subarray) (M) +[2953.Count-Complete-Substrings](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/2953.Count-Complete-Substrings) (H) +[2958.Length-of-Longest-Subarray-With-at-Most-K-Frequency](https://github.com/wisdompeak/LeetCode/blob/master/Two_Pointers/2958.Length-of-Longest-Subarray-With-at-Most-K-Frequency) (M) +[2968.Apply-Operations-to-Maximize-Frequency-Score](https://github.com/wisdompeak/LeetCode/tree/master/Math/2968.Apply-Operations-to-Maximize-Frequency-Score) (H-) +[3234.Count-the-Number-of-Substrings-With-Dominant-Ones](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/3234.Count-the-Number-of-Substrings-With-Dominant-Ones) (H-) +[3634.Minimum-Removals-to-Balance-Array](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/3634.Minimum-Removals-to-Balance-Array) (M+) * ``Sliding window : Distinct Characters`` [076.Minimum-Window-Substring](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/076.Minimum-Window-Substring) (M+) [003.Longest-Substring-Without-Repeating-Character](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/003.Longest%20Substring%20Without%20Repeating%20Characters) (E+) [159.Longest-Substring-with-At-Most-Two-Distinct-Characters](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/159.Longest-Substring-with-At-Most-Two-Distinct-Characters)(H-) [340.Longest-Substring-with-At-Most-K-Distinct-Characters](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/340.Longest-Substring-with-At-Most-K-Distinct-Characters) (H) -[992.Subarrays-with-K-Different-Integers](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/992.Subarrays-with-K-Different-Integers) (H-) +[992.Subarrays-with-K-Different-Integers](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/992.Subarrays-with-K-Different-Integers) (H-) +[3134.Find-the-Median-of-the-Uniqueness-Array](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/3134.Find-the-Median-of-the-Uniqueness-Array) (H-) [2461.Maximum-Sum-of-Distinct-Subarrays-With-Length-K](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/2461.Maximum-Sum-of-Distinct-Subarrays-With-Length-K) (M) [2537.Count-the-Number-of-Good-Subarrays](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/2537.Count-the-Number-of-Good-Subarrays) (M+) -* ``Two pointers for two seuqences`` +[3298.Count-Substrings-That-Can-Be-Rearranged-to-Contain-a-String-II](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/3298.Count-Substrings-That-Can-Be-Rearranged-to-Contain-a-String-II) (M+) +[3306.Count-of-Substrings-Containing-Every-Vowel-and-K-Consonants-II](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/3306.Count-of-Substrings-Containing-Every-Vowel-and-K-Consonants-II) (H-) +[3641.Longest-Semi-Repeating-Subarray](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/3641.Longest-Semi-Repeating-Subarray) (H-) +* ``Two pointers for two sequences`` [986.Interval-List-Intersections](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/986.Interval-List-Intersections) (M) [1229.Meeting-Scheduler](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/1229.Meeting-Scheduler) (M+) [1537.Get-the-Maximum-Score](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/1537.Get-the-Maximum-Score) (H-) @@ -88,10 +101,20 @@ [1712.Ways-to-Split-Array-Into-Three-Subarrays](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/1712.Ways-to-Split-Array-Into-Three-Subarrays) (H) [1889.Minimum-Space-Wasted-From-Packaging](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/1889.Minimum-Space-Wasted-From-Packaging) (H-) [1901.Find-a-Peak-Element-II](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/1901.Find-a-Peak-Element-II) (H) -[2563.Count-the-Number-of-Fair-Pairs](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/2563.Count-the-Number-of-Fair-Pairs) (M+) -* ``Binary Processing`` -[1483.Kth-Ancestor-of-a-Tree-Node](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/1483.Kth-Ancestor-of-a-Tree-Node) (H) -[1922.Count-Good-Numbers](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/1922.Count-Good-Numbers) (M) +[2563.Count-the-Number-of-Fair-Pairs](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/2563.Count-the-Number-of-Fair-Pairs) (M+) +[2819.Minimum-Relative-Loss-After-Buying-Chocolates](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/2819.Minimum-Relative-Loss-After-Buying-Chocolates) (H) +[2972.Count-the-Number-of-Incremovable-Subarrays-II](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/2972.Count-the-Number-of-Incremovable-Subarrays-II) (H-) +* ``Binary Lifting`` +[1483.Kth-Ancestor-of-a-Tree-Node](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/1483.Kth-Ancestor-of-a-Tree-Node) (H) +[2277.Closest-Node-to-Path-in-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Tree/2277.Closest-Node-to-Path-in-Tree) (H) +[2836.Maximize-Value-of-Function-in-a-Ball-Passing-Game](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/2836.Maximize-Value-of-Function-in-a-Ball-Passing-Game) (H) +[2846.Minimum-Edge-Weight-Equilibrium-Queries-in-a-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/2846.Minimum-Edge-Weight-Equilibrium-Queries-in-a-Tree) (H) +[3534.Path-Existence-Queries-in-a-Graph-II](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/3534.Path-Existence-Queries-in-a-Graph-II) (H) +[3553.Minimum-Weighted-Subgraph-With-the-Required-Paths-II](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/3553.Minimum-Weighted-Subgraph-With-the-Required-Paths-II) (H) +[3559.Number-of-Ways-to-Assign-Edge-Weights-II](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/3559.Number-of-Ways-to-Assign-Edge-Weights-II) (H-) +[3585.Find-Weighted-Median-Node-in-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/3585.Find-Weighted-Median-Node-in-Tree) (H) +* ``RMQ / Sparse Table`` +[3691.Maximum-Total-Subarray-Value-II](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/3691.Maximum-Total-Subarray-Value-II) (H) * ``Binary Search by Value`` [410.Split-Array-Largest-Sum](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/410.Split-Array-Largest-Sum) (H-) [774.Minimize-Max-Distance-to-Gas-Station](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/774.Minimize-Max-Distance-to-Gas-Station) (H) @@ -124,6 +147,19 @@ [2528.Maximize-the-Minimum-Powered-City](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/2528.Maximize-the-Minimum-Powered-City) (H-) [2557.Maximum-Number-of-Integers-to-Choose-From-a-Range-II](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/2557.Maximum-Number-of-Integers-to-Choose-From-a-Range-II) (H-) [2560.House-Robber-IV](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/2560.House-Robber-IV) (H-) +[2594.Minimum-Time-to-Repair-Cars](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/2594.Minimum-Time-to-Repair-Cars) (M) +[2604.Minimum-Time-to-Eat-All-Grains](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/2604.Minimum-Time-to-Eat-All-Grains) (H-) +[2616.Minimize-the-Maximum-Difference-of-Pairs](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/2616.Minimize-the-Maximum-Difference-of-Pairs) (H-) +[2702.Minimum-Operations-to-Make-Numbers-Non-positive](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/2702.Minimum-Operations-to-Make-Numbers-Non-positive) (H-) +[2861.Maximum-Number-of-Alloys](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/2861.Maximum-Number-of-Alloys) (M+) +[3048.Earliest-Second-to-Mark-Indices-I](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/3048.Earliest-Second-to-Mark-Indices-I) (M+) +[3049.Earliest-Second-to-Mark-Indices-II](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/3049.Earliest-Second-to-Mark-Indices-II) (H) +[3097.Shortest-Subarray-With-OR-at-Least-K-II](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/3097.Shortest-Subarray-With-OR-at-Least-K-II) (M) +[3399.Smallest-Substring-With-Identical-Characters-II](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/3399.Smallest-Substring-With-Identical-Characters-II) (H-) +[3449.Maximize-the-Minimum-Game-Score](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/3449.Maximize-the-Minimum-Game-Score) (H-) +[3464.Maximize-the-Distance-Between-Points-on-a-Square](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/3464.Maximize-the-Distance-Between-Points-on-a-Square) (H) +[3639.Minimum-Time-to-Activate-String](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/3639.Minimum-Time-to-Activate-String) (M) +[3677.Count-Binary-Palindromic-Numbers](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/3677.Count-Binary-Palindromic-Numbers) (H-) * ``Find K-th Element`` [215.Kth-Largest-Element-in-an-Array](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/215.Kth-Largest-Element-in-an-Array) (M) [287.Find-the-Duplicate-Number](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/287.Find-the-Duplicate-Number) (H-) @@ -138,7 +174,11 @@ [793.Preimage-Size-of-Factorial-Zeroes-Function](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/793.Preimage-Size-of-Factorial-Zeroes-Function) (H-) [1201.Ugly-Number-III](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/1201.Ugly-Number-III) (H-) [1539.Kth-Missing-Positive-Number](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/1539.Kth-Missing-Positive-Number) (H-) -[2387.Median-of-a-Row-Wise-Sorted-Matrix](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/2387.Median-of-a-Row-Wise-Sorted-Matrix) (H-) +[2387.Median-of-a-Row-Wise-Sorted-Matrix](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/2387.Median-of-a-Row-Wise-Sorted-Matrix) (H-) +[3116.Kth-Smallest-Amount-With-Single-Denomination-Combination](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/3116.Kth-Smallest-Amount-With-Single-Denomination-Combination) (H) +[3670.Maximum-Product-of-Two-Integers-With-No-Common-Bits](https://github.com/wisdompeak/LeetCode/tree/master/Trie/3670.Maximum-Product-of-Two-Integers-With-No-Common-Bits) (H) +[3134.Find-the-Median-of-the-Uniqueness-Array](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/3134.Find-the-Median-of-the-Uniqueness-Array) (H-) +[3485.Longest-Common-Prefix-of-K-Strings-After-Removal](https://github.com/wisdompeak/LeetCode/tree/master/3485.Longest-Common-Prefix-of-K-Strings-After-Removal) (H) #### [Hash Map](https://github.com/wisdompeak/LeetCode/tree/master/Hash) [049.Group-Anagrams](https://github.com/wisdompeak/LeetCode/tree/master/Hash/049.Group-Anagrams) (M+) @@ -180,34 +220,52 @@ [2025.Maximum-Number-of-Ways-to-Partition-an-Array](https://github.com/wisdompeak/LeetCode/tree/master/Hash/2025.Maximum-Number-of-Ways-to-Partition-an-Array) (H) [2488.Count-Subarrays-With-Median-K](https://github.com/wisdompeak/LeetCode/tree/master/Hash/2488.Count-Subarrays-With-Median-K) (H-) [2489.Number-of-Substrings-With-Fixed-Ratio](https://github.com/wisdompeak/LeetCode/tree/master/Hash/2489.Number-of-Substrings-With-Fixed-Ratio) (H-) +[2588.Count-the-Number-of-Beautiful-Subarrays](https://github.com/wisdompeak/LeetCode/tree/master/Hash/2588.Count-the-Number-of-Beautiful-Subarrays) (M+) +[2845.Count-of-Interesting-Subarrays](https://github.com/wisdompeak/LeetCode/tree/master/Hash/2845.Count-of-Interesting-Subarrays) (M+) +[2875.Minimum-Size-Subarray-in-Infinite-Array](https://github.com/wisdompeak/LeetCode/tree/master/Hash/2875.Minimum-Size-Subarray-in-Infinite-Array) (H-) +[2949.Count-Beautiful-Substrings-II](https://github.com/wisdompeak/LeetCode/tree/master/Hash/2949.Count-Beautiful-Substrings-II) (H-) +[2950.Number-of-Divisible-Substrings](https://github.com/wisdompeak/LeetCode/tree/master/Hash/2950.Number-of-Divisible-Substrings) (H-) +[3448.Count-Substrings-Divisible-By-Last-Digit](https://github.com/wisdompeak/LeetCode/tree/master/Hash/3448.Count-Substrings-Divisible-By-Last-Digit) (H-) +[3714.Longest-Balanced-Substring-II](https://github.com/wisdompeak/LeetCode/tree/master/Hash/3714.Longest-Balanced-Substring-II) (H-) -#### [Heap](https://github.com/wisdompeak/LeetCode/tree/master/Heap) -[220.Contains-Duplicate-III](https://github.com/wisdompeak/LeetCode/tree/master/Heap/220.Contains-Duplicate-III) (M) -[295.Find-Median-from-Data-Stream](https://github.com/wisdompeak/LeetCode/tree/master/Heap/295.Find-Median-from-Data-Stream) (M) -[363.Max-Sum-of-Rectangle-No-Larger-Than-K](https://github.com/wisdompeak/LeetCode/tree/master/Heap/363.Max-Sum-of-Rectangle-No-Larger-Than-K) (H) -[352.Data-Stream-as-Disjoint-Intervals](https://github.com/wisdompeak/LeetCode/tree/master/Heap/352.Data-Stream-as-Disjoint-Intervals) (H) -[480.Sliding-Window-Median](https://github.com/wisdompeak/LeetCode/blob/master/Heap/480.Sliding-Window-Median) (H) +#### [Sorted Container](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container) +[220.Contains-Duplicate-III](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/220.Contains-Duplicate-III) (M) +[363.Max-Sum-of-Rectangle-No-Larger-Than-K](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/363.Max-Sum-of-Rectangle-No-Larger-Than-K) (H) +[352.Data-Stream-as-Disjoint-Intervals](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/352.Data-Stream-as-Disjoint-Intervals) (H) +[480.Sliding-Window-Median](https://github.com/wisdompeak/LeetCode/blob/master/Sorted_Container/480.Sliding-Window-Median) (H) [699.Falling-Squares](https://github.com/wisdompeak/LeetCode/tree/master/Segment_Tree/699.Falling-Squares) (H) -[729.My-Calendar-I](https://github.com/wisdompeak/LeetCode/tree/master/Heap/729.My-Calendar-I) (M) -[855.Exam-Room](https://github.com/wisdompeak/LeetCode/tree/master/Heap/855.Exam-Room) (M+) -[975.Odd-Even-Jump](https://github.com/wisdompeak/LeetCode/tree/master/Heap/975.Odd-Even-Jump) (H-) -[632.Smallest-Range-Covering-Elements-from-K-Lists](https://github.com/wisdompeak/LeetCode/tree/master/Heap/632.Smallest-Range-Covering-Elements-from-K-Lists) (H-) -[1675.Minimize-Deviation-in-Array](https://github.com/wisdompeak/LeetCode/tree/master/Heap/1675.Minimize-Deviation-in-Array) (H) -[1296.Divide-Array-in-Sets-of-K-Consecutive-Numbers](https://github.com/wisdompeak/LeetCode/tree/master/Heap/1296.Divide-Array-in-Sets-of-K-Consecutive-Numbers) (M) +[729.My-Calendar-I](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/729.My-Calendar-I) (M) +[855.Exam-Room](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/855.Exam-Room) (M+) +[975.Odd-Even-Jump](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/975.Odd-Even-Jump) (H-) +[632.Smallest-Range-Covering-Elements-from-K-Lists](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/632.Smallest-Range-Covering-Elements-from-K-Lists) (H-) +[1675.Minimize-Deviation-in-Array](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/1675.Minimize-Deviation-in-Array) (H) +[1296.Divide-Array-in-Sets-of-K-Consecutive-Numbers](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/1296.Divide-Array-in-Sets-of-K-Consecutive-Numbers) (M) 1348.Tweet-Counts-Per-Frequency (H-) -[1488.Avoid-Flood-in-The-City](https://github.com/wisdompeak/LeetCode/tree/master/Heap/1488.Avoid-Flood-in-The-City) (H-) -[1606.Find-Servers-That-Handled-Most-Number-of-Requests](https://github.com/wisdompeak/LeetCode/tree/master/Heap/1606.Find-Servers-That-Handled-Most-Number-of-Requests) (M) +[1488.Avoid-Flood-in-The-City](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/1488.Avoid-Flood-in-The-City) (H-) +[1606.Find-Servers-That-Handled-Most-Number-of-Requests](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/1606.Find-Servers-That-Handled-Most-Number-of-Requests) (M) 1797.Design Authentication Manager (M) -[1825.Finding-MK-Average](https://github.com/wisdompeak/LeetCode/tree/master/Heap/1825.Finding-MK-Average) (H) -[1847.Closest-Room](https://github.com/wisdompeak/LeetCode/tree/master/Heap/1847.Closest-Room) (M+) -[1912.Design-Movie-Rental-System](https://github.com/wisdompeak/LeetCode/tree/master/Heap/1912.Design-Movie-Rental-System) (M+) +[1847.Closest-Room](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/1847.Closest-Room) (M+) +[1912.Design-Movie-Rental-System](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/1912.Design-Movie-Rental-System) (M+) 2034.Stock Price Fluctuation (M) [2071.Maximum-Number-of-Tasks-You-Can-Assign](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/2071.Maximum-Number-of-Tasks-You-Can-Assign) (H) -[2382.Maximum-Segment-Sum-After-Removals](https://github.com/wisdompeak/LeetCode/tree/master/Heap/2382.Maximum-Segment-Sum-After-Removals) (M+) +[2612.Minimum-Reverse-Operations](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/2612.Minimum-Reverse-Operations) (H) +[2736.Maximum-Sum-Queries](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/2736.Maximum-Sum-Queries) (H) +* ``Dual Multiset`` +[295.Find-Median-from-Data-Stream](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/295.Find-Median-from-Data-Stream) (M) +[1825.Finding-MK-Average](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/1825.Finding-MK-Average) (H) +[2653.Sliding-Subarray-Beauty](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/2653.Sliding-Subarray-Beauty) (M+) +[3013.Divide-an-Array-Into-Subarrays-With-Minimum-Cost-II](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/3013.Divide-an-Array-Into-Subarrays-With-Minimum-Cost-II) (H-) * ``Maintain intervals`` [715.Range-Module](https://github.com/wisdompeak/LeetCode/tree/master/Segment_Tree/715.Range-Module) (H) -[2213.Longest-Substring-of-One-Repeating-Character](https://github.com/wisdompeak/LeetCode/tree/master/Heap/2213.Longest-Substring-of-One-Repeating-Character) (H) -[2276.Count-Integers-in-Intervals](https://github.com/wisdompeak/LeetCode/tree/master/Heap/2276.Count-Integers-in-Intervals) (H-) +[2213.Longest-Substring-of-One-Repeating-Character](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/2213.Longest-Substring-of-One-Repeating-Character) (H) +[2276.Count-Integers-in-Intervals](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/2276.Count-Integers-in-Intervals) (H-) +[2382.Maximum-Segment-Sum-After-Removals](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/2382.Maximum-Segment-Sum-After-Removals) (M+) +* ``Sorted_Container w/ monotonic mapping values`` +[2940.Find-Building-Where-Alice-and-Bob-Can-Meet](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/2940.Find-Building-Where-Alice-and-Bob-Can-Meet) (H) +[2926.Maximum-Balanced-Subsequence-Sum](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/2926.Maximum-Balanced-Subsequence-Sum) (H) +[2907.Maximum-Profitable-Triplets-With-Increasing-Prices-I](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/2907.Maximum-Profitable-Triplets-With-Increasing-Prices-I) (H) +[2945.Find-Maximum-Non-decreasing-Array-Length](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/2945.Find-Maximum-Non-decreasing-Array-Length) (H) +[3672.Sum-of-Weighted-Modes-in-Subarrays](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/3672.Sum-of-Weighted-Modes-in-Subarrays) (M+) #### [Tree](https://github.com/wisdompeak/LeetCode/tree/master/Tree) [144.Binary-Tree-Preorder-Traversal](https://github.com/wisdompeak/LeetCode/tree/master/Tree/144.Binary-Tree-Preorder-Traversal) (M+) @@ -247,12 +305,17 @@ [1666.Change-the-Root-of-a-Binary-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Tree/1666.Change-the-Root-of-a-Binary-Tree) (H-) [1932.Merge-BSTs-to-Create-Single-BST](https://github.com/wisdompeak/LeetCode/tree/master/Tree/1932.Merge-BSTs-to-Create-Single-BST) (H) [2003.Smallest-Missing-Genetic-Value-in-Each-Subtree](https://github.com/wisdompeak/LeetCode/tree/master/Tree/2003.Smallest-Missing-Genetic-Value-in-Each-Subtree) (H) -[2277.Closest-Node-to-Path-in-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Tree/2277.Closest-Node-to-Path-in-Tree) (H-) -[2313.Minimum-Flips-in-Binary-Tree-to-Get-Result](https://github.com/wisdompeak/LeetCode/tree/master/Tree/2313.Minimum-Flips-in-Binary-Tree-to-Get-Result) (H) +[2445.Number-of-Nodes-With-Value-One](https://github.com/wisdompeak/LeetCode/tree/master/Tree/2445.Number-of-Nodes-With-Value-One) (M+) +* ``Regular DFS`` [2322.Minimum-Score-After-Removals-on-a-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Tree/2322.Minimum-Score-After-Removals-on-a-Tree) (H-) -[2458.Height-of-Binary-Tree-After-Subtree-Removal-Queries](https://github.com/wisdompeak/LeetCode/tree/master/Tree/2458.Height-of-Binary-Tree-After-Subtree-Removal-Queries) (M+) +[2313.Minimum-Flips-in-Binary-Tree-to-Get-Result](https://github.com/wisdompeak/LeetCode/tree/master/Tree/2313.Minimum-Flips-in-Binary-Tree-to-Get-Result) (H) [2467.Most-Profitable-Path-in-a-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Tree/2467.Most-Profitable-Path-in-a-Tree) (M+) -[2445.Number-of-Nodes-With-Value-One](https://github.com/wisdompeak/LeetCode/tree/master/Tree/2445.Number-of-Nodes-With-Value-One) (M+) +[2458.Height-of-Binary-Tree-After-Subtree-Removal-Queries](https://github.com/wisdompeak/LeetCode/tree/master/Tree/2458.Height-of-Binary-Tree-After-Subtree-Removal-Queries) (M+) +[2646.Minimize-the-Total-Price-of-the-Trips](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/2646.Minimize-the-Total-Price-of-the-Trips) (M+) +[2920.Maximum-Points-After-Collecting-Coins-From-All-Nodes](https://github.com/wisdompeak/LeetCode/tree/master/Tree/2920.Maximum-Points-After-Collecting-Coins-From-All-Nodes) (H-) +[2925.Maximum-Score-After-Applying-Operations-on-a-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Tree/2925.Maximum-Score-After-Applying-Operations-on-a-Tree) (M) +[2973.Find-Number-of-Coins-to-Place-in-Tree-Nodes](https://github.com/wisdompeak/LeetCode/tree/master/Tree/2973.Find-Number-of-Coins-to-Place-in-Tree-Nodes) (H-) +[3715.Sum-of-Perfect-Square-Ancestors](https://github.com/wisdompeak/LeetCode/tree/master/Tree/3715.Sum-of-Perfect-Square-Ancestors) (M+) * ``Path in a tree`` [543.Diameter-of-Binary-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Tree/543.Diameter-of-Binary-Tree) (M) [124.Binary-Tree-Maximum-Path-Sum](https://github.com/wisdompeak/LeetCode/tree/master/Tree/124.Binary-Tree-Maximum-Path-Sum) (M) @@ -260,7 +323,8 @@ [1522.Diameter-of-N-Ary-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Tree/1522.Diameter-of-N-Ary-Tree) (M) [2049.Count-Nodes-With-the-Highest-Score](https://github.com/wisdompeak/LeetCode/tree/master/Tree/2049.Count-Nodes-With-the-Highest-Score) (M+) [2246.Longest-Path-With-Different-Adjacent-Characters](https://github.com/wisdompeak/LeetCode/tree/master/Tree/2246.Longest-Path-With-Different-Adjacent-Characters) (M+) -[2538.Difference-Between-Maximum-and-Minimum-Price-Sum](https://github.com/wisdompeak/LeetCode/tree/master/Tree/2538.Difference-Between-Maximum-and-Minimum-Price-Sum) (H) +[2538.Difference-Between-Maximum-and-Minimum-Price-Sum](https://github.com/wisdompeak/LeetCode/tree/master/Tree/2538.Difference-Between-Maximum-and-Minimum-Price-Sum) (H) +[3203.Find-Minimum-Diameter-After-Merging-Two-Trees](https://github.com/wisdompeak/LeetCode/tree/master/Tree/3203.Find-Minimum-Diameter-After-Merging-Two-Trees) (H-) * ``Serialization & Hashing`` [297.Serialize-and-Deserialize-Binary-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Tree/297.Serialize-and-Deserialize-Binary-Tree) (H-) [652.Find-Duplicate-Subtrees](https://github.com/wisdompeak/LeetCode/tree/master/Tree/652.Find-Duplicate-Subtrees) (H) @@ -292,6 +356,9 @@ [1516.Move-Sub-Tree-of-N-Ary-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Tree/1516.Move-Sub-Tree-of-N-Ary-Tree) (H-) * ``Re-Root`` [834.Sum-of-Distances-in-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Tree/834.Sum-of-Distances-in-Tree) (H) +[2581.Count-Number-of-Possible-Root-Nodes](https://github.com/wisdompeak/LeetCode/tree/master/Tree/2581.Count-Number-of-Possible-Root-Nodes) (H) +[2858.Minimum-Edge-Reversals-So-Every-Node-Is-Reachable](https://github.com/wisdompeak/LeetCode/tree/master/Tree/2858.Minimum-Edge-Reversals-So-Every-Node-Is-Reachable) (H-) +[3772.Maximum-Subgraph-Score-in-a-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Tree/3772.Maximum-Subgraph-Score-in-a-Tree) (H-) * ``似树非树`` [823](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/823.Binary-Trees-With-Factors), [1902](https://github.com/wisdompeak/LeetCode/tree/master/Tree/1902.Depth-of-BST-Given-Insertion-Order), @@ -307,12 +374,25 @@ [715.Range-Module](https://github.com/wisdompeak/LeetCode/tree/master/Segment_Tree/715.Range-Module) (H) [2286.Booking-Concert-Tickets-in-Groups](https://github.com/wisdompeak/LeetCode/tree/master/Segment_Tree/2286.Booking-Concert-Tickets-in-Groups) (H-) [2407.Longest-Increasing-Subsequence-II](https://github.com/wisdompeak/LeetCode/tree/master/Segment_Tree/2407.Longest-Increasing-Subsequence-II) (H-) +[2569.Handling-Sum-Queries-After-Update](https://github.com/wisdompeak/LeetCode/tree/master/Segment_Tree/2569.Handling-Sum-Queries-After-Update) (H) +[2907.Maximum-Profitable-Triplets-With-Increasing-Prices-I](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/2907.Maximum-Profitable-Triplets-With-Increasing-Prices-I) (H) +[2916.Subarrays-Distinct-Element-Sum-of-Squares-II](https://github.com/wisdompeak/LeetCode/tree/master/Segment_Tree/2916.Subarrays-Distinct-Element-Sum-of-Squares-II) (H+) +[3072.Distribute-Elements-Into-Two-Arrays-II](https://github.com/wisdompeak/LeetCode/tree/master/Segment_Tree/3072.Distribute-Elements-Into-Two-Arrays-II) (H-) +[3161.Block-Placement-Queries](https://github.com/wisdompeak/LeetCode/tree/master/Segment_Tree/3161.Block-Placement-Queries) (H) +[3165.Maximum-Sum-of-Subsequence-With-Non-adjacent-Elements](https://github.com/wisdompeak/LeetCode/tree/master/Segment_Tree/3165.Maximum-Sum-of-Subsequence-With-Non-adjacent-Elements) (H) +[3187.Peaks-in-Array](https://github.com/wisdompeak/LeetCode/tree/master/Segment_Tree/3187.Peaks-in-Array) (M+) +[3261.Count-Substrings-That-Satisfy-20K-Constraint-II](https://github.com/wisdompeak/LeetCode/blob/master/Segment_Tree/3261.Count-Substrings-That-Satisfy-K-Constraint-II) (H-) +[3721.Longest-Balanced-Subarray-II](https://github.com/wisdompeak/LeetCode/tree/master/Segment_Tree/3721.Longest-Balanced-Subarray-II) (H+) +[3915.Maximum-Sum-of-Alternating-Subsequence-With-Distance-at-Least-K](https://github.com/wisdompeak/LeetCode/tree/master/Segment_Tree/3915.Maximum-Sum-of-Alternating-Subsequence-With-Distance-at-Least-K) (H-) #### [Binary Index Tree] [307.Range-Sum-Query-Mutable](https://github.com/wisdompeak/LeetCode/blob/master/Segment_Tree/307.Range-Sum-Query-Mutable/) (M) [1649.Create-Sorted-Array-through-Instructions](https://github.com/wisdompeak/LeetCode/tree/master/Divide_Conquer/1649.Create-Sorted-Array-through-Instructions) (H) [2031.Count-Subarrays-With-More-Ones-Than-Zeros](https://github.com/wisdompeak/LeetCode/tree/master/Segment_Tree/2031.Count-Subarrays-With-More-Ones-Than-Zeros) (H) [2179.Count-Good-Triplets-in-an-Array](https://github.com/wisdompeak/LeetCode/tree/master/Segment_Tree/2179.Count-Good-Triplets-in-an-Array) (H) +[2659.Make-Array-Empty](https://github.com/wisdompeak/LeetCode/tree/master/Segment_Tree/2659.Make-Array-Empty) (H) +[3624.Number-of-Integers-With-Popcount-Depth-Equal-to-K-II](https://github.com/wisdompeak/LeetCode/tree/master/Segment_Tree/3624.Number-of-Integers-With-Popcount-Depth-Equal-to-K-II) (H-) +[3671.Sum-of-Beautiful-Subsequences](https://github.com/wisdompeak/LeetCode/tree/master/Segment_Tree/3671.Sum-of-Beautiful-Subsequences) (H+) #### [Design](https://github.com/wisdompeak/LeetCode/tree/master/Design) [380.Insert-Delete-GetRandom-O(1)](https://github.com/wisdompeak/LeetCode/tree/master/Design/380.Insert-Delete-GetRandom-O-1/) (M+) @@ -355,32 +435,39 @@ [1586.Binary-Search-Tree-Iterator-II](https://github.com/wisdompeak/LeetCode/tree/master/Stack/1586.Binary-Search-Tree-Iterator-II) (H) [2197.Replace-Non-Coprime-Numbers-in-Array](https://github.com/wisdompeak/LeetCode/tree/master/Stack/2197.Replace-Non-Coprime-Numbers-in-Array) (H-) [2296.Design-a-Text-Editor](https://github.com/wisdompeak/LeetCode/tree/master/Design/2296.Design-a-Text-Editor) (M+) +[2751.Robot-Collisions](https://github.com/wisdompeak/LeetCode/tree/master/Stack/2751.Robot-Collisions) (M+) +[2764.is-Array-a-Preorder-of-Some-Binary-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Stack/2764.is-Array-a-Preorder-of-Some-Binary-Tree) (M+) * ``monotonic stack: next greater / smaller`` [042.Trapping-Rain-Water](https://github.com/wisdompeak/LeetCode/tree/master/Others/042.Trapping-Rain-Water) (H) -[084.Largest-Rectangle-in-Histogram](https://github.com/wisdompeak/LeetCode/tree/master/Stack/084.Largest-Rectangle-in-Histogram) (H) -[2334.Subarray-With-Elements-Greater-Than-Varying-Threshold](https://github.com/wisdompeak/LeetCode/tree/master/Stack/2334.Subarray-With-Elements-Greater-Than-Varying-Threshold) (M+) -[085.Maximal-Rectangle](https://github.com/wisdompeak/LeetCode/tree/master/Stack/085.Maximal-Rectangle) (H-) [255.Verify-Preorder-Sequence-in-Binary-Search-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Tree/255.Verify-Preorder-Sequence-in-Binary-Search-Tree) (H) [496.Next-Greater-Element-I](https://github.com/wisdompeak/LeetCode/tree/master/Stack/496.Next-Greater-Element-I) (H-) [503.Next-Greater-Element-II](https://github.com/wisdompeak/LeetCode/blob/master/Stack/503.Next-Greater-Element-II) (H-) -[221.Maximal-Square](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/221.Maximal-Square) (H-) [654.Maximum-Binary-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Stack/654.Maximum-Binary-Tree) (H) [739.Daily-Temperatures](https://github.com/wisdompeak/LeetCode/tree/master/Stack/739.Daily-Temperatures) (H-) [768.Max-Chunks-To-Make-Sorted-II](https://github.com/wisdompeak/LeetCode/tree/master/Stack/768.Max-Chunks-To-Make-Sorted-II) (H-) [901.Online-Stock-Span](https://github.com/wisdompeak/LeetCode/tree/master/Stack/901.Online-Stock-Span) (H-) -[907.Sum-of-Subarray-Minimums](https://github.com/wisdompeak/LeetCode/tree/master/Stack/907.Sum-of-Subarray-Minimums) (H-) +[907.Sum-of-Subarray-Minimums](https://github.com/wisdompeak/LeetCode/tree/master/Stack/907.Sum-of-Subarray-Minimums) (H-) [1856.Maximum-Subarray-Min-Product](https://github.com/wisdompeak/LeetCode/tree/master/Stack/1856.Maximum-Subarray-Min-Product) (M+) [2104.Sum-of-Subarray-Ranges](https://github.com/wisdompeak/LeetCode/tree/master/Stack/2104.Sum-of-Subarray-Ranges) (H-) [1019.Next-Greater-Node-In-Linked-List](https://github.com/wisdompeak/LeetCode/tree/master/Stack/1019.Next-Greater-Node-In-Linked-List) (M) [1063.Number-of-Valid-Subarrays](https://github.com/wisdompeak/LeetCode/tree/master/Stack/1063.Number-of-Valid-Subarrays) (M+) -[1124.Longest-Well-Performing-Interval](https://github.com/wisdompeak/LeetCode/tree/master/Stack/1124.Longest-Well-Performing-Interval) (H) +[1124.Longest-Well-Performing-Interval](https://github.com/wisdompeak/LeetCode/tree/master/Stack/1124.Longest-Well-Performing-Interval) (H) +[1130.Minimum-Cost-Tree-From-Leaf-Values](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1130.Minimum-Cost-Tree-From-Leaf-Values) (H) [1950.Maximum-of-Minimum-Values-in-All-Subarrays](https://github.com/wisdompeak/LeetCode/tree/master/Stack/1950.Maximum-of-Minimum-Values-in-All-Subarrays) (H-) [1966.Binary-Searchable-Numbers-in-an-Unsorted-Array](https://github.com/wisdompeak/LeetCode/tree/master/Stack/1966.Binary-Searchable-Numbers-in-an-Unsorted-Array) (M+) [2434.Using-a-Robot-to-Print-the-Lexicographically-Smallest-String](https://github.com/wisdompeak/LeetCode/tree/master/Stack/2434.Using-a-Robot-to-Print-the-Lexicographically-Smallest-String) (H-) -[2454.Next-Greater-Element-IV](https://github.com/wisdompeak/LeetCode/tree/master/Stack/2454.Next-Greater-Element-IV) (H-) -* ``monotonic stack: other usages`` -[962.Maximum-Width-Ramp](https://github.com/wisdompeak/LeetCode/tree/master/Stack/962.Maximum-Width-Ramp) (H) -[1130.Minimum-Cost-Tree-From-Leaf-Values](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1130.Minimum-Cost-Tree-From-Leaf-Values) (H) +[2454.Next-Greater-Element-IV](https://github.com/wisdompeak/LeetCode/tree/master/Stack/2454.Next-Greater-Element-IV) (H-) +[3113.Find-the-Number-of-Subarrays-Where-Boundary-Elements-Are-Maximum](https://github.com/wisdompeak/LeetCode/tree/master/Stack/3113.Find-the-Number-of-Subarrays-Where-Boundary-Elements-Are-Maximum) (M) +[3676.Count-Bowl-Subarrays](https://github.com/wisdompeak/LeetCode/tree/master/Stack/3676.Count-Bowl-Subarrays) (M+) +* ``monotonic stack: other usages`` +[084.Largest-Rectangle-in-Histogram](https://github.com/wisdompeak/LeetCode/tree/master/Stack/084.Largest-Rectangle-in-Histogram) (H) +[2334.Subarray-With-Elements-Greater-Than-Varying-Threshold](https://github.com/wisdompeak/LeetCode/tree/master/Stack/2334.Subarray-With-Elements-Greater-Than-Varying-Threshold) (M+) +[085.Maximal-Rectangle](https://github.com/wisdompeak/LeetCode/tree/master/Stack/085.Maximal-Rectangle) (H-) +[2866.Beautiful-Towers-II](https://github.com/wisdompeak/LeetCode/tree/master/Stack/2866.Beautiful-Towers-II) (H) +[1504.Count-Submatrices-With-All-Ones](https://github.com/wisdompeak/LeetCode/tree/master/Stack/1504.Count-Submatrices-With-All-Ones) (H) +[221.Maximal-Square](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/221.Maximal-Square) (H-) +[962.Maximum-Width-Ramp](https://github.com/wisdompeak/LeetCode/tree/master/Stack/962.Maximum-Width-Ramp) (H) +[2863.Maximum-Length-of-Semi-Decreasing-Subarrays](https://github.com/wisdompeak/LeetCode/tree/master/Stack/2863.Maximum-Length-of-Semi-Decreasing-Subarrays) (H) [1944.Number-of-Visible-People-in-a-Queue](https://github.com/wisdompeak/LeetCode/tree/master/Stack/1944.Number-of-Visible-People-in-a-Queue) (H) [2282.Number-of-People-That-Can-Be-Seen-in-a-Grid](https://github.com/wisdompeak/LeetCode/tree/master/Stack/2282.Number-of-People-That-Can-Be-Seen-in-a-Grid) (H) [2289.Steps-to-Make-Array-Non-decreasing](https://github.com/wisdompeak/LeetCode/tree/master/Design/2289.Steps-to-Make-Array-Non-decreasing) (H) @@ -416,11 +503,15 @@ [1696.Jump-Game-VI](https://github.com/wisdompeak/LeetCode/tree/master/Deque/1696.Jump-Game-VI) (M+) [1776.Car-Fleet-II](https://github.com/wisdompeak/LeetCode/tree/master/Deque/1776.Car-Fleet-II) (H) [2398.Maximum-Number-of-Robots-Within-Budget](https://github.com/wisdompeak/LeetCode/tree/master/Deque/2398.Maximum-Number-of-Robots-Within-Budget) (H-) +[2762.Continuous-Subarrays](https://github.com/wisdompeak/LeetCode/tree/master/Deque/2762.Continuous-Subarrays) (M+) +[2969.Minimum-Number-of-Coins-for-Fruits-II](https://github.com/wisdompeak/LeetCode/tree/master/Deque/2969.Minimum-Number-of-Coins-for-Fruits-II) (H-) +[3578.Count-Partitions-With-Max-Min-Difference-at-Most-K](https://github.com/wisdompeak/LeetCode/tree/master/Deque/3578.Count-Partitions-With-Max-Min-Difference-at-Most-K) (H-) +[3845.Maximum-Subarray-XOR-with-Bounded-Range](https://github.com/wisdompeak/LeetCode/tree/master/Deque/3845.Maximum-Subarray-XOR-with-Bounded-Range) (H) +[3956.Maximum-Sum-of-M-Non-Overlapping-Subarrays-I](https://github.com/wisdompeak/LeetCode/tree/master/Deque/3956.Maximum-Sum-of-M-Non-Overlapping-Subarrays-I) (H) #### [Priority Queue](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue) [004.Median-of-Two-Sorted-Arrays](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/004.Median-of-Two-Sorted-Arrays) (H) [373.Find-K-Pairs-with-Smallest-Sums](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/373.Find-K-Pairs-with-Smallest-Sums) (H-) -[774.Minimize-Max-Distance-to-Gas-Station](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/774.Minimize-Max-Distance-to-Gas-Station) (H) [871.Minimum-Number-of-Refueling-Stops](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/871.Minimum-Number-of-Refueling-Stops) (H-) [1057.Campus-Bikes](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/1057.Campus-Bikes) (H-) [1167.Minimum-Cost-to-Connect-Sticks](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/1167.Minimum-Cost-to-Connect-Sticks) (H-) @@ -430,16 +521,23 @@ [1792.Maximum-Average-Pass-Ratio](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/1792.Maximum-Average-Pass-Ratio) (M+) [2263.Make-Array-Non-decreasing-or-Non-increasing](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2263.Make-Array-Non-decreasing-or-Non-increasing) (H) [2386.Find-the-K-Sum-of-an-Array](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/2386.Find-the-K-Sum-of-an-Array) (H+) +[2931.Maximum-Spending-After-Buying-Items](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/2931.Maximum-Spending-After-Buying-Items) (M) +* ``反悔贪心`` +[630.Course-Schedule-III](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/630.Course-Schedule-III) (H) +[774.Minimize-Max-Distance-to-Gas-Station](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/774.Minimize-Max-Distance-to-Gas-Station) (H) +[2599.Make-the-Prefix-Sum-Non-negative](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/2599.Make-the-Prefix-Sum-Non-negative) (H-) +[3049.Earliest-Second-to-Mark-Indices-II](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/3049.Earliest-Second-to-Mark-Indices-II) (H) +[3645.Maximum-Total-from-Optimal-Activation-Order](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/3645.Maximum-Total-from-Optimal-Activation-Order) (H-) * ``Dual PQ`` [1801.Number-of-Orders-in-the-Backlog](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/1801.Number-of-Orders-in-the-Backlog) (M) [1882.Process-Tasks-Using-Servers](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/1882.Process-Tasks-Using-Servers) (H) [1942.The-Number-of-the-Smallest-Unoccupied-Chair](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/1942.The-Number-of-the-Smallest-Unoccupied-Chair) (M+) -[2102.Sequentially-Ordinal-Rank-Tracker](https://github.com/wisdompeak/LeetCode/tree/master/Heap/2102.Sequentially-Ordinal-Rank-Tracker) (H-) -[2402.Meeting-Rooms-III](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/2402.Meeting-Rooms-III) (M+) +[2102.Sequentially-Ordinal-Rank-Tracker](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/2102.Sequentially-Ordinal-Rank-Tracker) (H-) +[2402.Meeting-Rooms-III](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/2402.Meeting-Rooms-III) (M+) +[2653.Sliding-Subarray-Beauty](https://github.com/wisdompeak/LeetCode/tree/master/Sorted_Container/2653.Sliding-Subarray-Beauty) (M+) * ``Sort+PQ`` [253.Meeting-Rooms-II](https://github.com/wisdompeak/LeetCode/tree/master/Others/253.Meeting-Rooms-II) (M+) [502.IPO](https://github.com/wisdompeak/LeetCode/blob/master/Priority_Queue/502.IPO/) (M+) -[630.Course-Schedule-III](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/630.Course-Schedule-III) (H) [857.Minimum-Cost-to-Hire-K-Workers](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/857.Minimum-Cost-to-Hire-K-Workers) (H) [1353.Maximum-Number-of-Events-That-Can-Be-Attended](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/1353.Maximum-Number-of-Events-That-Can-Be-Attended) (H-) [1383.Maximum-Performance-of-a-Team](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/1383.Maximum-Performance-of-a-Team) (M+) @@ -455,7 +553,6 @@ [984.String-Without-AAA-or-BBB](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/984.String-Without-AAA-or-BBB) (M+) [1405.Longest-Happy-String](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1405.Longest-Happy-String) (H-) [1953.Maximum-Number-of-Weeks-for-Which-You-Can-Work](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/1953.Maximum-Number-of-Weeks-for-Which-You-Can-Work) (M+) -[2335.Minimum-Amount-of-Time-to-Fill-Cups](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/2335.Minimum-Amount-of-Time-to-Fill-Cups) (M+) #### [DFS](https://github.com/wisdompeak/LeetCode/tree/master/DFS) [037.Sudoku-Solver](https://github.com/wisdompeak/LeetCode/tree/master/DFS/037.Sudoku-Solver) (M+) @@ -478,6 +575,9 @@ [2014.Longest-Subsequence-Repeated-k-Times](https://github.com/wisdompeak/LeetCode/tree/master/DFS/2014.Longest-Subsequence-Repeated-k-Times) (H) [2056.Number-of-Valid-Move-Combinations-On-Chessboard](https://github.com/wisdompeak/LeetCode/tree/master/DFS/2056.Number-of-Valid-Move-Combinations-On-Chessboard) (H) [2065.Maximum-Path-Quality-of-a-Graph](https://github.com/wisdompeak/LeetCode/tree/master/DFS/2065.Maximum-Path-Quality-of-a-Graph) (M) +[2850.Minimum-Moves-to-Spread-Stones-Over-Grid](https://github.com/wisdompeak/LeetCode/tree/master/DFS/2850.Minimum-Moves-to-Spread-Stones-Over-Grid) (M) +[3459.Length-of-Longest-V-Shaped-Diagonal-Segment](https://github.com/wisdompeak/LeetCode/tree/master/DFS/3459.Length-of-Longest-V-Shaped-Diagonal-Segment) (M+) +[3593.Minimum-Increments-to-Equalize-Leaf-Paths](https://github.com/wisdompeak/LeetCode/tree/master/DFS/3593.Minimum-Increments-to-Equalize-Leaf-Paths) (M+) * ``search in an array`` [090.Subsets-II](https://github.com/wisdompeak/LeetCode/tree/master/DFS/090.Subsets-II) (M+) [301.Remove-Invalid-Parentheses](https://github.com/wisdompeak/LeetCode/tree/master/DFS/301.Remove-Invalid-Parentheses) (H) @@ -490,6 +590,9 @@ [1681.Minimum-Incompatibility](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1681.Minimum-Incompatibility) (H) [1723.Find-Minimum-Time-to-Finish-All-Jobs](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1723.Find-Minimum-Time-to-Finish-All-Jobs) (H-) [2305.Fair-Distribution-of-Cookies](https://github.com/wisdompeak/LeetCode/tree/master/DFS/2305.Fair-Distribution-of-Cookies) (H-) +[2597.The-Number-of-Beautiful-Subsets](https://github.com/wisdompeak/LeetCode/tree/master/DFS/2597.The-Number-of-Beautiful-Subsets) (M+) +[2842.Count-K-Subsequences-of-a-String-With-Maximum-Beauty](https://github.com/wisdompeak/LeetCode/tree/master/DFS/2842.Count-K-Subsequences-of-a-String-With-Maximum-Beauty) (M+) +[3669.Balanced-K-Factor-Decomposition](https://github.com/wisdompeak/LeetCode/tree/master/DFS/3669.Balanced-K-Factor-Decomposition) (M) * ``memorization`` [329.Longest-Increasing-Path-in-a-Matrix](https://github.com/wisdompeak/LeetCode/tree/master/DFS/329.Longest-Increasing-Path-in-a-Matrix) (M) [2328.Number-of-Increasing-Paths-in-a-Grid](https://github.com/wisdompeak/LeetCode/tree/master/DFS/2328.Number-of-Increasing-Paths-in-a-Grid) (M) @@ -497,7 +600,11 @@ [403.Frog-Jump](https://github.com/wisdompeak/LeetCode/tree/master/DFS/403.Frog-Jump) (M+) [546.Remove-Boxes](https://github.com/wisdompeak/LeetCode/tree/master/DFS/546.Remove-Boxes) (H+) [1340.Jump-Game-V](https://github.com/wisdompeak/LeetCode/tree/master/DFS/1340.Jump-Game-V) (M+) -[1815.Maximum-Number-of-Groups-Getting-Fresh-Donuts](https://github.com/wisdompeak/LeetCode/tree/master/DFS/1815.Maximum-Number-of-Groups-Getting-Fresh-Donuts) (H-) +[1815.Maximum-Number-of-Groups-Getting-Fresh-Donuts](https://github.com/wisdompeak/LeetCode/tree/master/DFS/1815.Maximum-Number-of-Groups-Getting-Fresh-Donuts) (H-) +[2741.Special-Permutations](https://github.com/wisdompeak/LeetCode/tree/master/DFS/2741.Special-Permutations) (M+) +[2746.Decremental-String-Concatenation](https://github.com/wisdompeak/LeetCode/tree/master/DFS/2746.Decremental-String-Concatenation) (H-) +[3213.Construct-String-with-Minimum-Cost](https://github.com/wisdompeak/LeetCode/tree/master/DFS/3213.Construct-String-with-Minimum-Cost) (H-) +[3615.Longest-Palindromic-Path-in-Graph](https://github.com/wisdompeak/LeetCode/tree/master/DFS/3615.Longest-Palindromic-Path-in-Graph) (H-) * ``hidden matrix`` [489.Robot-Room-Cleaner](https://github.com/wisdompeak/LeetCode/blob/master/DFS/489.Robot-Room-Cleaner) (H) [1778.Shortest-Path-in-a-Hidden-Grid](https://github.com/wisdompeak/LeetCode/tree/master/DFS/1778.Shortest-Path-in-a-Hidden-Grid) (H-) @@ -533,13 +640,18 @@ [2258.Escape-the-Spreading-Fire](https://github.com/wisdompeak/LeetCode/tree/master/BFS/2258.Escape-the-Spreading-Fire) (H+) [2290.Minimum-Obstacle-Removal-to-Reach-Corner](https://github.com/wisdompeak/LeetCode/tree/master/BFS/2290.Minimum-Obstacle-Removal-to-Reach-Corner) (M+) [2493.Divide-Nodes-Into-the-Maximum-Number-of-Groups](https://github.com/wisdompeak/LeetCode/tree/master/BFS/2493.Divide-Nodes-Into-the-Maximum-Number-of-Groups) (H-) +[2812.Find-the-Safest-Path-in-a-Grid](https://github.com/wisdompeak/LeetCode/tree/master/BFS/2812.Find-the-Safest-Path-in-a-Grid) (M+) +[3552.Grid-Teleportation-Traversal](https://github.com/wisdompeak/LeetCode/tree/master/BFS/3552.Grid-Teleportation-Traversal) (H-) +[3629.Minimum-Jumps-to-Reach-End-via-Prime-Teleportation](https://github.com/wisdompeak/LeetCode/tree/master/BFS/3629.Minimum-Jumps-to-Reach-End-via-Prime-Teleportation) (M+) +[3690.Split-and-Merge-Array-Transformation](https://github.com/wisdompeak/LeetCode/tree/master/BFS/3690.Split-and-Merge-Array-Transformation) (M) * ``Multi State`` [847.Shortest-Path-Visiting-All-Nodes](https://github.com/wisdompeak/LeetCode/tree/master/BFS/847.Shortest-Path-Visiting-All-Nodes) (H-) [864.Shortest-Path-to-Get-All-Keys](https://github.com/wisdompeak/LeetCode/tree/master/BFS/864.Shortest-Path-to-Get-All-Keys) (H-) [913.Cat-and-Mouse](https://github.com/wisdompeak/LeetCode/tree/master/BFS/913.Cat-and-Mouse) (H+) [1728.Cat-and-Mouse-II](https://github.com/wisdompeak/LeetCode/tree/master/BFS/1728.Cat-and-Mouse-II) (H+) [1293.Shortest-Path-in-a-Grid-with-Obstacles-Elimination](https://github.com/wisdompeak/LeetCode/tree/master/BFS/1293.Shortest-Path-in-a-Grid-with-Obstacles-Elimination) (H-) -[1928.Minimum-Cost-to-Reach-Destination-in-Time](https://github.com/wisdompeak/LeetCode/tree/master/BFS/1928.Minimum-Cost-to-Reach-Destination-in-Time) (H-) +[1928.Minimum-Cost-to-Reach-Destination-in-Time](https://github.com/wisdompeak/LeetCode/tree/master/BFS/1928.Minimum-Cost-to-Reach-Destination-in-Time) (H-) +[3568.Minimum-Moves-to-Clean-the-Classroom](https://github.com/wisdompeak/LeetCode/tree/master/BFS/3568.Minimum-Moves-to-Clean-the-Classroom) (H-) * ``拓扑排序`` [207.Course-Schedule](https://github.com/wisdompeak/LeetCode/tree/master/BFS/207.Course-Schedule) (H-) [210.Course-Schedule-II](https://github.com/wisdompeak/LeetCode/tree/master/BFS/210.Course-Schedule-II) (M) @@ -560,6 +672,7 @@ [2204.Distance-to-a-Cycle-in-Undirected-Graph](https://github.com/wisdompeak/LeetCode/tree/master/BFS/2204.Distance-to-a-Cycle-in-Undirected-Graph) (M) [2392.Build-a-Matrix-With-Conditions](https://github.com/wisdompeak/LeetCode/tree/master/BFS/2392.Build-a-Matrix-With-Conditions) (M+) [2440.Create-Components-With-Same-Value](https://github.com/wisdompeak/LeetCode/tree/master/BFS/2440.Create-Components-With-Same-Value) (H-) +[2603.Collect-Coins-in-a-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Graph/2603.Collect-Coins-in-a-Tree) (H-) * ``Dijkstra (BFS+PQ)`` [743.Network-Delay-Time](https://github.com/wisdompeak/LeetCode/tree/master/BFS/743.Network-Delay-Time) (H-) [407.Trapping-Rain-Water-II](https://github.com/wisdompeak/LeetCode/tree/master/BFS/407.Trapping-Rain-Water-II) (H) @@ -567,7 +680,6 @@ [2503.Maximum-Number-of-Points-From-Grid-Queries](https://github.com/wisdompeak/LeetCode/tree/master/BFS/2503.Maximum-Number-of-Points-From-Grid-Queries) (H-) [505.The-Maze-II](https://github.com/wisdompeak/LeetCode/tree/master/BFS/505.The-Maze-II) (H-) [499.The-Maze-III](https://github.com/wisdompeak/LeetCode/tree/master/BFS/499.The-Maze-III) (H) -[787.Cheapest-Flights-Within-K-Stops](https://github.com/wisdompeak/LeetCode/tree/master/Graph/787.Cheapest-Flights-Within-K-Stops) (H) [882.Reachable-Nodes-In-Subdivided-Graph](https://github.com/wisdompeak/LeetCode/tree/master/BFS/882.Reachable-Nodes-In-Subdivided-Graph ) (H) [1102.Path-With-Maximum-Minimum-Value](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/1102.Path-With-Maximum-Minimum-Value) (H-) [1368.Minimum-Cost-to-Make-at-Least-One-Valid-Path-in-a-Grid](https://github.com/wisdompeak/LeetCode/tree/master/BFS/1368.Minimum-Cost-to-Make-at-Least-One-Valid-Path-in-a-Grid) (H) @@ -576,8 +688,11 @@ [1810.Minimum-Path-Cost-in-a-Hidden-Grid](https://github.com/wisdompeak/LeetCode/tree/master/BFS/1810.Minimum-Path-Cost-in-a-Hidden-Grid) (M+) [1976.Number-of-Ways-to-Arrive-at-Destination](https://github.com/wisdompeak/LeetCode/tree/master/BFS/1976.Number-of-Ways-to-Arrive-at-Destination) (M+) [2093.Minimum-Cost-to-Reach-City-With-Discounts](https://github.com/wisdompeak/LeetCode/tree/master/BFS/2093.Minimum-Cost-to-Reach-City-With-Discounts) (H-) +[2714.Find-Shortest-Path-with-K-Hops](https://github.com/wisdompeak/LeetCode/tree/master/BFS/2714.Find-Shortest-Path-with-K-Hops) (M+) [2203.Minimum-Weighted-Subgraph-With-the-Required-Paths](https://github.com/wisdompeak/LeetCode/tree/master/BFS/2203.Minimum-Weighted-Subgraph-With-the-Required-Paths) (H-) [2473.Minimum-Cost-to-Buy-Apples](https://github.com/wisdompeak/LeetCode/tree/master/BFS/2473.Minimum-Cost-to-Buy-Apples) (M) +[3594.Minimum-Time-to-Transport-All-Individuals](https://github.com/wisdompeak/LeetCode/tree/master/BFS/3594.Minimum-Time-to-Transport-All-Individuals) (H) +[3604.Minimum-Time-to-Reach-Destination-in-Directed-Graph](https://github.com/wisdompeak/LeetCode/tree/master/BFS/3604.Minimum-Time-to-Reach-Destination-in-Directed-Graph) (M+) * ``Dijkstra (for Bipatite Graph)`` [1066.Campus-Bikes-II](https://github.com/wisdompeak/LeetCode/tree/master/BFS/1066.Campus-Bikes-II) (H+) [1879.Minimum-XOR-Sum-of-Two-Arrays](https://github.com/wisdompeak/LeetCode/tree/master/BFS/1879.Minimum-XOR-Sum-of-Two-Arrays) (H) @@ -601,12 +716,17 @@ 1032. Stream of Characters (TBD) [1858.Longest-Word-With-All-Prefixes](https://github.com/wisdompeak/LeetCode/tree/master/Trie/1858.Longest-Word-With-All-Prefixes) (M) [2416.Sum-of-Prefix-Scores-of-Strings](https://github.com/wisdompeak/LeetCode/tree/master/Trie/2416.Sum-of-Prefix-Scores-of-Strings) (M) +[2977.Minimum-Cost-to-Convert-String-II](https://github.com/wisdompeak/LeetCode/tree/master/Trie/2977.Minimum-Cost-to-Convert-String-II) (H) +[3093.Longest-Common-Suffix-Queries](https://github.com/wisdompeak/LeetCode/tree/master/Trie/3093.Longest-Common-Suffix-Queries) (H-) +[3670.Maximum-Product-of-Two-Integers-With-No-Common-Bits](https://github.com/wisdompeak/LeetCode/tree/master/Trie/3670.Maximum-Product-of-Two-Integers-With-No-Common-Bits) (H-) * ``Trie and XOR`` [421.Maximum-XOR-of-Two-Numbers-in-an-Array](https://github.com/wisdompeak/LeetCode/tree/master/Trie/421.Maximum-XOR-of-Two-Numbers-in-an-Array) (H-) [1707.Maximum-XOR-With-an-Element-From-Array](https://github.com/wisdompeak/LeetCode/tree/master/Trie/1707.Maximum-XOR-With-an-Element-From-Array) (H-) [1803.Count-Pairs-With-XOR-in-a-Range](https://github.com/wisdompeak/LeetCode/tree/master/Trie/1803.Count-Pairs-With-XOR-in-a-Range) (H) [1938.Maximum-Genetic-Difference-Query](https://github.com/wisdompeak/LeetCode/tree/master/Trie/1938.Maximum-Genetic-Difference-Query) (H) -[2479.Maximum-XOR-of-Two-Non-Overlapping-Subtrees](https://github.com/wisdompeak/LeetCode/tree/master/Trie/2479.Maximum-XOR-of-Two-Non-Overlapping-Subtrees) (H) +[2479.Maximum-XOR-of-Two-Non-Overlapping-Subtrees](https://github.com/wisdompeak/LeetCode/tree/master/Trie/2479.Maximum-XOR-of-Two-Non-Overlapping-Subtrees) (H) +[2935.Maximum-Strong-Pair-XOR-II](https://github.com/wisdompeak/LeetCode/tree/master/Trie/2935.Maximum-Strong-Pair-XOR-II) (H) +[3632.Subarrays-with-XOR-at-Least-K](https://github.com/wisdompeak/LeetCode/tree/master/Trie/3632.Subarrays-with-XOR-at-Least-K) (H) #### [Linked List](https://github.com/wisdompeak/LeetCode/tree/master/Linked_List) [061.Rotate-List](https://github.com/wisdompeak/LeetCode/tree/master/Linked_List/061.Rotate-List) (M) @@ -673,9 +793,25 @@ [2338.Count-the-Number-of-Ideal-Arrays](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2338.Count-the-Number-of-Ideal-Arrays) (H) [2431.Maximize-Total-Tastiness-of-Purchased-Fruits](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2431.Maximize-Total-Tastiness-of-Purchased-Fruits) (M+) [2484.Count-Palindromic-Subsequences](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2484.Count-Palindromic-Subsequences) (H-) +[2713.Maximum-Strictly-Inreasing-Cells-in-a-Matrix](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2713.Maximum-Strictly-Inreasing-Cells-in-a-Matrix) (H-) +[2787.Ways-to-Express-an-Integer-as-Sum-of-Powers](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2787.Ways-to-Express-an-Integer-as-Sum-of-Powers) (M+) +[2809.Minimum-Time-to-Make-Array-Sum-At-Most-x](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2809.Minimum-Time-to-Make-Array-Sum-At-Most-x) (H) +[2826.Sorting-Three-Groups](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2826.Sorting-Three-Groups) (M) +[2851.String-Transformation](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2851.String-Transformation) (H+) +[2896.Apply-Operations-to-Make-Two-Strings-Equal](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2896.Apply-Operations-to-Make-Two-Strings-Equal) (H) +[2979.Most-Expensive-Item-That-Can-Not-Be-Bought](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2979.Most-Expensive-Item-That-Can-Not-Be-Bought) (M+) +[3041.Maximize-Consecutive-Elements-in-an-Array-After-Modification](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3041.Maximize-Consecutive-Elements-in-an-Array-After-Modification) (H-) +[3082.Find-the-Sum-of-the-Power-of-All-Subsequences](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3082.Find-the-Sum-of-the-Power-of-All-Subsequences) (H-) +[3098.Find-the-Sum-of-Subsequence-Powers](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3098.Find-the-Sum-of-Subsequence-Powers) (H) +[3389.Minimum-Operations-to-Make-Character-Frequencies-Equal](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3389.Minimum-Operations-to-Make-Character-Frequencies-Equal) (H) +[3654.Minimum-Sum-After-Divisible-Sum-Deletions](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3654.Minimum-Sum-After-Divisible-Sum-Deletions) (H) +[3699.Number-of-ZigZag-Arrays-I](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3699.Number-of-ZigZag-Arrays-I) (M) * ``基本型 I`` [198.House-Robber](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/198.House-Robber) (E) [213.House-Robber-II](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/213.House-Robber-II) (M+) +[2597.The-Number-of-Beautiful-Subsets](https://github.com/wisdompeak/LeetCode/tree/master/DFS/2597.The-Number-of-Beautiful-Subsets) (H) +[2638.Count-the-Number-of-K-Free-Subsets](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2638.Count-the-Number-of-K-Free-Subsets) (M+) +[3186.Maximum-Total-Damage-With-Spell-Casting](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3186.Maximum-Total-Damage-With-Spell-Casting) (M+) [2320.Count-Number-of-Ways-to-Place-Houses](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2320.Count-Number-of-Ways-to-Place-Houses) (M+) [1388.Pizza-With-3n-Slices](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1388.Pizza-With-3n-Slices) (H-) [276.Paint-Fence](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/276.Paint-Fence) (H-) @@ -702,7 +838,10 @@ [2036.Maximum-Alternating-Subarray-Sum](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2036.Maximum-Alternating-Subarray-Sum) (M+) [2143.Choose-Numbers-From-Two-Arrays-in-Range](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2143.Choose-Numbers-From-Two-Arrays-in-Range) (H) [2318.Number-of-Distinct-Roll-Sequences](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2318.Number-of-Distinct-Roll-Sequences) (H-) -[2361.Minimum-Costs-Using-the-Train-Line](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2361.Minimum-Costs-Using-the-Train-Line) (M+) +[2361.Minimum-Costs-Using-the-Train-Line](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2361.Minimum-Costs-Using-the-Train-Line) (M+) +[2786.Visit-Array-Positions-to-Maximize-Score](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2786.Visit-Array-Positions-to-Maximize-Score) (M) +[3122.Minimum-Number-of-Operations-to-Satisfy-Conditions.cpp](https://github.com/wisdompeak/LeetCode/blob/master/Dynamic_Programming/3122.Minimum-Number-of-Operations-to-Satisfy-Conditions) (M+) +[3661.Maximum-Walls-Destroyed-by-Robots](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3661.Maximum-Walls-Destroyed-by-Robots) (H-) * ``基本型 II`` [368.Largest-Divisible-Subset](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/368.Largest-Divisible-Subset) (M+) [300.Longest-Increasing-Subsequence](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/300.Longest-Increasing-Subsequence) (M+) @@ -722,6 +861,12 @@ [2430.Maximum-Deletions-on-a-String](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2430.Maximum-Deletions-on-a-String) (M+) [2464.Minimum-Subarrays-in-a-Valid-Split](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2464.Minimum-Subarrays-in-a-Valid-Split) (M) [2522.Partition-String-Into-Substrings-With-Values-at-Most-K](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2522.Partition-String-Into-Substrings-With-Values-at-Most-K) (M+) +[3202.Find-the-Maximum-Length-of-Valid-Subsequence-II](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3202.Find-the-Maximum-Length-of-Valid-Subsequence-II) (M) + * `Interval` + [1235.Maximum-Profit-in-Job-Scheduling](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1235.Maximum-Profit-in-Job-Scheduling) (H-) + [1751.Maximum-Number-of-Events-That-Can-Be-Attended-II](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1751.Maximum-Number-of-Events-That-Can-Be-Attended-II) (H) + [2008.Maximum-Earnings-From-Taxi](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2008.Maximum-Earnings-From-Taxi) (M+) + [2830.Maximize-the-Profit-as-the-Salesman](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2830.Maximize-the-Profit-as-the-Salesman) (M) * ``走迷宫型`` [120.Triangle](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/120.Triangle) (E) [174.Dungeon-Game](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/174.Dungeon-Game) (H-) @@ -733,7 +878,8 @@ [1301.Number-of-Paths-with-Max-Score](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1301.Number-of-Paths-with-Max-Score) (M+) [1594.Maximum-Non-Negative-Product-in-a-Matrix](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1594.Maximum-Non-Negative-Product-in-a-Matrix) (M) [2267.Check-if-There-Is-a-Valid-Parentheses-String-Path](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2267.Check-if-There-Is-a-Valid-Parentheses-String-Path) (H-) -[2435.Paths-in-Matrix-Whose-Sum-Is-Divisible-by-K](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2435.Paths-in-Matrix-Whose-Sum-Is-Divisible-by-K) (M) +[2435.Paths-in-Matrix-Whose-Sum-Is-Divisible-by-K](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2435.Paths-in-Matrix-Whose-Sum-Is-Divisible-by-K) (M) +[3665.Twisted-Mirror-Path-Count](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3665.Twisted-Mirror-Path-Count) (M) * ``背包型`` [322.Coin-Change](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/322.Coin-Change) (M) [416.Partition-Equal-Subset-Sum](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/416.Partition-Equal-Subset-Sum) (M+) @@ -748,6 +894,12 @@ [1981.Minimize-the-Difference-Between-Target-and-Chosen-Elements](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1981.Minimize-the-Difference-Between-Target-and-Chosen-Elements) (M+) [2291.Maximum-Profit-From-Trading-Stocks](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2291.Maximum-Profit-From-Trading-Stocks) (M) [2518.Number-of-Great-Partitions](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2518.Number-of-Great-Partitions) (H-) +[2585.Number-of-Ways-to-Earn-Points](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2585.Number-of-Ways-to-Earn-Points) (M) +[2902.Count-of-Sub-Multisets-With-Bounded-Sum](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2902.Count-of-Sub-Multisets-With-Bounded-Sum) (H) +[3489.Zero-Array-Transformation-IV](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3489.Zero-Array-Transformation-IV) (H-) +[3592.Inverse-Coin-Change](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3592.Inverse-Coin-Change) (H) +[3685.Subsequence-Sum-After-Capping-Elements](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3685.Subsequence-Sum-After-Capping-Elements) (H) +[3946.Maximum-Number-of-Items-From-Sale-I](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3946.Maximum-Number-of-Items-From-Sale-I) (H-) * ``键盘型`` [650.2-Keys-Keyboard](https://github.com/wisdompeak/LeetCode/blob/master/Dynamic_Programming/650.2-Keys-Keyboard) (M+) [651.4-Keys-Keyboard](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/651.4-Keys-Keyboard) (M+) @@ -757,19 +909,23 @@ [487.Max-Consecutive-Ones-II](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/487.Max-Consecutive-Ones-II) (H-) [1186.Maximum-Subarray-Sum-with-One-Deletion](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1186.Maximum-Subarray-Sum-with-One-Deletion) (H-) [1187.Make-Array-Strictly-Increasing](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1187.Make-Array-Strictly-Increasing) (H-) -[1909.Remove-One-Element-to-Make-the-Array-Strictly-Increasing](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1909.Remove-One-Element-to-Make-the-Array-Strictly-Increasing) (H-) +[1909.Remove-One-Element-to-Make-the-Array-Strictly-Increasing](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1909.Remove-One-Element-to-Make-the-Array-Strictly-Increasing) (H-) +[3196.Maximize-Total-Cost-of-Alternating-Subarrays](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3196.Maximize-Total-Cost-of-Alternating-Subarrays) (M) * ``区间型 I`` [132.Palindrome-Partitioning-II](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/132.Palindrome-Partitioning-II) (H-) [410.Split-Array-Largest-Sum](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/410.Split-Array-Largest-Sum) (H) [813.Largest-Sum-of-Averages](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/813.Largest-Sum-of-Averages) (H-) [1278.Palindrome-Partitioning-III](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1278.Palindrome-Partitioning-III) (H) -[1335.Minimum-Difficulty-of-a-Job-Schedule](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1335.Minimum-Difficulty-of-a-Job-Schedule) (M+) +[1335.Minimum-Difficulty-of-a-Job-Schedule](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1335.Minimum-Difficulty-of-a-Job-Schedule) (M+) [1478.Allocate-Mailboxes](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1478.Allocate-Mailboxes) (H) [1977.Number-of-Ways-to-Separate-Numbers](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1977.Number-of-Ways-to-Separate-Numbers) (H) [2463.Minimum-Total-Distance-Traveled](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2463.Minimum-Total-Distance-Traveled) (M+) [2472.Maximum-Number-of-Non-overlapping-Palindrome-Substrings](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2472.Maximum-Number-of-Non-overlapping-Palindrome-Substrings) (M+) [2478.Number-of-Beautiful-Partitions](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2478.Number-of-Beautiful-Partitions) (H-) -[2547.Minimum-Cost-to-Split-an-Array](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2547.Minimum-Cost-to-Split-an-Array) (M) +[2547.Minimum-Cost-to-Split-an-Array](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2547.Minimum-Cost-to-Split-an-Array) (M) +[2911.Minimum-Changes-to-Make-K-Semi-palindromes](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2911.Minimum-Changes-to-Make-K-Semi-palindromes) (H-) +[3077.Maximum-Strength-of-K-Disjoint-Subarrays](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3077.Maximum-Strength-of-K-Disjoint-Subarrays) (M+) +[3579.Minimum-Steps-to-Convert-String-with-Operations](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3579.Minimum-Steps-to-Convert-String-with-Operations) (H-) * ``区间型 II`` [131.Palindrome-Partitioning](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/131.Palindrome-Partitioning) (M+) [312.Burst-Balloons](https://github.com/wisdompeak/LeetCode/tree/master/DFS/312.Burst-Balloons) (H-) @@ -787,7 +943,8 @@ [1682.Longest-Palindromic-Subsequence-II](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1682.Longest-Palindromic-Subsequence-II) (H) [1690.Stone-Game-VII](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1690.Stone-Game-VII) (H-) [1745.Palindrome-Partitioning-IV](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1745.Palindrome-Partitioning-IV) (M) -[1770.Maximum-Score-from-Performing-Multiplication-Operations](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1770.Maximum-Score-from-Performing-Multiplication-Operations) (H-) +[1770.Maximum-Score-from-Performing-Multiplication-Operations](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1770.Maximum-Score-from-Performing-Multiplication-Operations) (H-) +[3018.Maximum-Number-of-Removal-Queries-That-Can-Be-Processed-I](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3018.Maximum-Number-of-Removal-Queries-That-Can-Be-Processed-I) (H-) * ``双序列型`` [010.Regular-Expression-Matching](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/010.Regular-Expression-Matching) (H) [044.Wildcard-Matching](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/044.Wildcard-Matching) (H-) @@ -820,11 +977,14 @@ [1994.The-Number-of-Good-Subsets](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1994.The-Number-of-Good-Subsets) (H) [2184.Number-of-Ways-to-Build-Sturdy-Brick-Wall](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2184.Number-of-Ways-to-Build-Sturdy-Brick-Wall) (H-) [2403.Minimum-Time-to-Kill-All-Monsters](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2403.Minimum-Time-to-Kill-All-Monsters) (M+) +[2572.Count-the-Number-of-Square-Free-Subsets](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2572.Count-the-Number-of-Square-Free-Subsets) (H-) * ``枚举集合的子集`` [1494.Parallel-Courses-II](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1494.Parallel-Courses-II) (H) [1655.Distribute-Repeating-Integers](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1655.Distribute-Repeating-Integers) (H) [1986.Minimum-Number-of-Work-Sessions-to-Finish-the-Tasks](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1986.Minimum-Number-of-Work-Sessions-to-Finish-the-Tasks) (M+) - [2152.Minimum-Number-of-Lines-to-Cover-Points](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2152.Minimum-Number-of-Lines-to-Cover-Points) (H-) + [2152.Minimum-Number-of-Lines-to-Cover-Points](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2152.Minimum-Number-of-Lines-to-Cover-Points) (H-) + [3444.Minimum-Increments-for-Target-Multiples-in-an-Array](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3444.Minimum-Increments-for-Target-Multiples-in-an-Array) (H-) + [3801.Minimum-Cost-to-Merge-Sorted-Lists](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3801.Minimum-Cost-to-Merge-Sorted-Lists) (H-) * ``带权二分图`` [1066.Campus-Bikes-II](https://github.com/wisdompeak/LeetCode/tree/master/BFS/1066.Campus-Bikes-II) (H+) [1595.Minimum-Cost-to-Connect-Two-Groups-of-Points](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1595.Minimum-Cost-to-Connect-Two-Groups-of-Points) (H) @@ -840,14 +1000,23 @@ * ``Permutation`` [629.K-Inverse-Pairs-Array](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/629.K-Inverse-Pairs-Array) (H) [903.Valid-Permutations-for-DI-Sequence](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/903.Valid-Permutations-for-DI-Sequence) (H) -[1866.Number-of-Ways-to-Rearrange-Sticks-With-K-Sticks-Visible](https://github.com/wisdompeak/LeetCode/tree/master/Math/1866.Number-of-Ways-to-Rearrange-Sticks-With-K-Sticks-Visible) (H) +[1866.Number-of-Ways-to-Rearrange-Sticks-With-K-Sticks-Visible](https://github.com/wisdompeak/LeetCode/tree/master/Math/1866.Number-of-Ways-to-Rearrange-Sticks-With-K-Sticks-Visible) (H) +[3193.Count-the-Number-of-Inversions](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3193.Count-the-Number-of-Inversions) (H) * ``Infer future from current`` -[2044.Count-Number-of-Maximum-Bitwise-OR-Subsets](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2044.Count-Number-of-Maximum-Bitwise-OR-Subsets) (M) +[2044.Count-Number-of-Maximum-Bitwise-OR-Subsets](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2044.Count-Number-of-Maximum-Bitwise-OR-Subsets) (M) +[2742.Painting-the-Walls](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2742.Painting-the-Walls) (H) +[3538.Merge-Operations-for-Minimum-Travel-Time](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3538.Merge-Operations-for-Minimum-Travel-Time) (H) * ``maximum subarray`` [053.Maximum-Subarray](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/053.Maximum-Subarray) (E+) [152.Maximum-Product-Subarray](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/152.Maximum-Product-Subarray) (M+) [2272.Substring-With-Largest-Variance](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2272.Substring-With-Largest-Variance) (H-) [2321.Maximum-Score-Of-Spliced-Array](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2321.Maximum-Score-Of-Spliced-Array) (H-) +* ``前缀和辅助`` +[3130.Find-All-Possible-Stable-Binary-Arrays-II](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3130.Find-All-Possible-Stable-Binary-Arrays-II) (H) +* ``遍历优化`` +[3177.Find-the-Maximum-Length-of-a-Good-Subsequence-II)](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3177.Find-the-Maximum-Length-of-a-Good-Subsequence-II)) (H) +* ``循环型`` +[3892.Minimum-Operations-to-Achieve-At-Least-K-Peaks](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/3892.Minimum-Operations-to-Achieve-At-Least-K-Peaks) (H-) #### [Bit Manipulation](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation) [137.Single-Number-II](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/137.Single-Number-II) (H-) @@ -858,8 +1027,15 @@ [898.Bitwise-ORs-of-Subarrays](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/898.Bitwise-ORs-of-Subarrays) (H-) [957.Prison-Cells-After-N-Days](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/957.Prison-Cells-After-N-Days) (H) 1461.Check-If-a-String-Contains-All-Binary-Codes-of-Size-K (TBD) -[1521.Find-a-Value-of-a-Mysterious-Function-Closest-to-Target](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/1521.Find-a-Value-of-a-Mysterious-Function-Closest-to-Target) (H-) [2505.Bitwise-OR-of-All-Subsequence-Sums](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/2505.Bitwise-OR-of-All-Subsequence-Sums) (H) +[2680.Maximum-OR](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/2680.Maximum-OR) (M+) +[2802.Find-The-K-th-Lucky-Number](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/2802.Find-The-K-th-Lucky-Number) (M+) +[2992.Number-of-Self-Divisible-Permutations](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/2992.Number-of-Self-Divisible-Permutations) (M+) +[3133.Minimum-Array-End](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/3133.Minimum-Array-End) (M+) +* ``Prefix Hashing`` +[1521.Find-a-Value-of-a-Mysterious-Function-Closest-to-Target](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/1521.Find-a-Value-of-a-Mysterious-Function-Closest-to-Target) (H-) +[3171.Find-Subarray-With-Bitwise-OR-Closest-to-K](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/3171.Find-Subarray-With-Bitwise-AND-Closest-to-K) (H) +[3209.Number-of-Subarrays-With-AND-Value-of-K](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/3209.Number-of-Subarrays-With-AND-Value-of-K) (M+) * ``XOR`` [136.Single-Number](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/136.Single-Number) (M) [268.Missing-Number](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/268.Missing-Number) (H-) @@ -879,10 +1055,12 @@ [1774.Closest-Dessert-Cost](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/1774.Closest-Dessert-Cost) (M) [2002.Maximum-Product-of-the-Length-of-Two-Palindromic-Subsequences](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/2002.Maximum-Product-of-the-Length-of-Two-Palindromic-Subsequences) (M) [2151.Maximum-Good-People-Based-on-Statements](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/2151.Maximum-Good-People-Based-on-Statements) (M+) -[2397.Maximum-Rows-Covered-by-Columns](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/2397.Maximum-Rows-Covered-by-Columns) (M) +[2397.Maximum-Rows-Covered-by-Columns](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/2397.Maximum-Rows-Covered-by-Columns) (M) +[3116.Kth-Smallest-Amount-With-Single-Denomination-Combination](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/3116.Kth-Smallest-Amount-With-Single-Denomination-Combination) (H) * ``Meet in the Middle`` [1755.Closest-Subsequence-Sum](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/1755.Closest-Subsequence-Sum) (H) - [2035.Partition-Array-Into-Two-Arrays-to-Minimize-Sum-Difference](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/2035.Partition-Array-Into-Two-Arrays-to-Minimize-Sum-Difference) (H) + [2035.Partition-Array-Into-Two-Arrays-to-Minimize-Sum-Difference](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/2035.Partition-Array-Into-Two-Arrays-to-Minimize-Sum-Difference) (H) + [3877.Minimum-Removals-to-Achieve-Target-XOR](https://github.com/wisdompeak/LeetCode/tree/master/Bit_Manipulation/3877.Minimum-Removals-to-Achieve-Target-XOR) (M+) #### [Divide and Conquer](https://github.com/wisdompeak/LeetCode/tree/master/Divide_Conquer) [315.Count-of-Smaller-Numbers-After-Self](https://github.com/wisdompeak/LeetCode/tree/master/Divide_Conquer/315.Count-of-Smaller-Numbers-After-Self) (H-) @@ -898,7 +1076,6 @@ [388.Longest-Absolute-File-Path](https://github.com/wisdompeak/LeetCode/tree/master/String/388.Longest-Absolute-File-Path) (M+) [418.Sentence-Screen-Fitting](https://github.com/wisdompeak/LeetCode/tree/master/String/418.Sentence-Screen-Fitting) (M+) [423.Reconstruct-Original-Digits-from-English](https://github.com/wisdompeak/LeetCode/tree/master/Others/423.Reconstruct-Original-Digits-from-English) (H-) -[556.Next-Greater-Element-III](https://github.com/wisdompeak/LeetCode/tree/master/String/556.Next-Greater-Element-III) (H-) 616.Add-Bold-Tag-in-String (M) [467.Unique-Substrings-in-Wraparound-String](https://github.com/wisdompeak/LeetCode/tree/master/String/467.Unique-Substrings-in-Wraparound-String) (H-) [564.Find-the-Closest-Palindrome](https://github.com/wisdompeak/LeetCode/tree/master/String/564.Find-the-Closest-Palindrome) (H) @@ -924,7 +1101,9 @@ [2156.Find-Substring-With-Given-Hash-Value](https://github.com/wisdompeak/LeetCode/tree/master/String/2156.Find-Substring-With-Given-Hash-Value) (M) [2168.Unique-Substrings-With-Equal-Digit-Frequency](https://github.com/wisdompeak/LeetCode/tree/master/String/2168.Unique-Substrings-With-Equal-Digit-Frequency) (M+) [2223.Sum-of-Scores-of-Built-Strings](https://github.com/wisdompeak/LeetCode/tree/master/String/2223.Sum-of-Scores-of-Built-Strings) (H-) -[2261.K-Divisible-Elements-Subarrays](https://github.com/wisdompeak/LeetCode/tree/master/String/2261.K-Divisible-Elements-Subarrays) (H-) +[2261.K-Divisible-Elements-Subarrays](https://github.com/wisdompeak/LeetCode/tree/master/String/2261.K-Divisible-Elements-Subarrays) (H-) +[2781.Length-of-the-Longest-Valid-Substring](https://github.com/wisdompeak/LeetCode/tree/master/String/2781.Length-of-the-Longest-Valid-Substring) (H-) +[3388.Count-Beautiful-Splits-in-an-Array](https://github.com/wisdompeak/LeetCode/tree/master/String/3388.Count-Beautiful-Splits-in-an-Array) (H-) * ``KMP`` [1392.Longest-Happy-Prefix](https://github.com/wisdompeak/LeetCode/tree/master/String/1392.Longest-Happy-Prefix) (H) [028.Implement-strStr](https://github.com/wisdompeak/LeetCode/tree/master/String/028.Implement-strStr) (H) @@ -935,6 +1114,10 @@ 1397.Find All Good Strings (TBD) [1764.Form-Array-by-Concatenating-Subarrays-of-Another-Array](https://github.com/wisdompeak/LeetCode/tree/master/String/1764.Form-Array-by-Concatenating-Subarrays-of-Another-Array) (H) [2301.Match-Substring-After-Replacement](https://github.com/wisdompeak/LeetCode/tree/master/String/2301.Match-Substring-After-Replacement) (H-) +[2851.String-Transformation](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2851.String-Transformation) (H+) +[3008.Find-Beautiful-Indices-in-the-Given-Array-II](https://github.com/wisdompeak/LeetCode/tree/master/String/3008.Find-Beautiful-Indices-in-the-Given-Array-II) (H-) +[3031.Minimum-Time-to-Revert-Word-to-Initial-State-II](https://github.com/wisdompeak/LeetCode/tree/master/String/3031.Minimum-Time-to-Revert-Word-to-Initial-State-II) (H) +[3045.Count-Prefix-and-Suffix-Pairs-II](https://github.com/wisdompeak/LeetCode/tree/master/String/3045.Count-Prefix-and-Suffix-Pairs-II) (H) * ``Manacher`` [005.Longest-Palindromic-Substring](https://github.com/wisdompeak/LeetCode/tree/master/String/005.Longest-Palindromic-Substring) (H) [214.Shortest-Palindrome](https://github.com/wisdompeak/LeetCode/blob/master/String/214.Shortest-Palindrome) (H) @@ -967,6 +1150,10 @@ [2092.Find-All-People-With-Secret](https://github.com/wisdompeak/LeetCode/tree/master/Union_Find/2092.Find-All-People-With-Secret) (H-) [2157.Groups-of-Strings](https://github.com/wisdompeak/LeetCode/tree/master/Union_Find/2157.Groups-of-Strings) (H) [2492.Minimum-Score-of-a-Path-Between-Two-Cities](https://github.com/wisdompeak/LeetCode/tree/master/Union_Find/2492.Minimum-Score-of-a-Path-Between-Two-Cities) (M) +[2867.Count-Valid-Paths-in-a-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Union_Find/2867.Count-Valid-Paths-in-a-Tree) (M+) +[3873.Maximum-Points-Activated-with-One-Addition](https://github.com/wisdompeak/LeetCode/tree/master/Union_Find/3873.Maximum-Points-Activated-with-One-Addition) (M+) +* ``Union with Weight`` +[3887.Incremental-Even-Weighted-Cycle-Queries](https://github.com/wisdompeak/LeetCode/tree/master/Union_Find/3887.Incremental-Even-Weighted-Cycle-Queries) (H) * ``Union in an order`` [803.Bricks-Falling-When-Hit](https://github.com/wisdompeak/LeetCode/tree/master/DFS/803.Bricks-Falling-When-Hit) (H) [1970.Last-Day-Where-You-Can-Still-Cross](https://github.com/wisdompeak/LeetCode/tree/master/Union_Find/1970.Last-Day-Where-You-Can-Still-Cross) (H-) @@ -977,12 +1164,14 @@ [952.Largest-Component-Size-by-Common-Factor](https://github.com/wisdompeak/LeetCode/tree/master/Union_Find/952.Largest-Component-Size-by-Common-Factor) (H) [1627.Graph-Connectivity-With-Threshold](https://github.com/wisdompeak/LeetCode/tree/master/Union_Find/1627.Graph-Connectivity-With-Threshold) (M+) [1998.GCD-Sort-of-an-Array](https://github.com/wisdompeak/LeetCode/tree/master/Union_Find/1998.GCD-Sort-of-an-Array) (H-) +[2709.Greatest-Common-Divisor-Traversal](https://github.com/wisdompeak/LeetCode/tree/master/Union_Find/2709.Greatest-Common-Divisor-Traversal) (H-) * ``MST`` [1135.Connecting-Cities-With-Minimum-Cost](https://github.com/wisdompeak/LeetCode/tree/master/Union_Find/1135.Connecting-Cities-With-Minimum-Cost) (M+) [1168.Optimize-Water-Distribution-in-a-Village](https://github.com/wisdompeak/LeetCode/tree/master/Union_Find/1168.Optimize-Water-Distribution-in-a-Village) (H-) [1489.Find-Critical-and-Pseudo-Critical-Edges-in-Minimum-Spanning-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Union_Find/1489.Find-Critical-and-Pseudo-Critical-Edges-in-Minimum-Spanning-Tree) (H) [1579.Remove-Max-Number-of-Edges-to-Keep-Graph-Fully-Traversable](https://github.com/wisdompeak/LeetCode/tree/master/Union_Find/1579.Remove-Max-Number-of-Edges-to-Keep-Graph-Fully-Traversable) (H-) -[1584.Min-Cost-to-Connect-All-Points](https://github.com/wisdompeak/LeetCode/tree/master/Union_Find/1584.Min-Cost-to-Connect-All-Points) (H-) +[1584.Min-Cost-to-Connect-All-Points](https://github.com/wisdompeak/LeetCode/tree/master/Union_Find/1584.Min-Cost-to-Connect-All-Points) (H-) +[3600.Maximize-Spanning-Tree-Stability-with-Upgrades](https://github.com/wisdompeak/LeetCode/tree/master/Union_Find/3600.Maximize-Spanning-Tree-Stability-with-Upgrades) (H) #### [Recursion](https://github.com/wisdompeak/LeetCode/tree/master/Recursion) [087.Scramble-String](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/087.Scramble-String) (H-) @@ -993,7 +1182,6 @@ [390.Elimination-Game](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/390.Elimination-Game) (H) [395.Longest-Substring-with-At-Least-K-Repeating-Characters](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/395.Longest-Substring-with-At-Least-K-Repeating-Characters) (H) [397.Integer-Replacement](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/397.Integer-Replacement) (M+) -440.K-th-Smallest-in-Lexicographical-Order (H) [761.Special-Binary-String](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/761.Special-Binary-String) (H) 779.K-th-Symbol-in-Grammar (M) [780.Reaching-Points](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/780.Reaching-Points) (H-) @@ -1005,10 +1193,10 @@ [1088.Confusing-Number-II](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/1088.Confusing-Number-II) (H) [1199.Minimum-Time-to-Build-Blocks](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/1199.Minimum-Time-to-Build-Blocks) (H+) [1274.Number-of-Ships-in-a-Rectangle](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/1274.Number-of-Ships-in-a-Rectangle) (M) -[1415.The-k-th-Lexicographical-String-of-All-Happy-Strings-of-Length-n](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/1415.The-k-th-Lexicographical-String-of-All-Happy-Strings-of-Length-n) (H-) -1545. Find Kth Bit in Nth Binary String (TBD) [1553.Minimum-Number-of-Days-to-Eat-N-Oranges](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/1553.Minimum-Number-of-Days-to-Eat-N-Oranges) (H) [1611.Minimum-One-Bit-Operations-to-Make-Integers-Zero](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/1611.Minimum-One-Bit-Operations-to-Make-Integers-Zero) (H) +[2998.Minimum-Number-of-Operations-to-Make-X-and-Y-Equal](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/2998.Minimum-Number-of-Operations-to-Make-X-and-Y-Equal) (M+) +[3864.Minimum-Cost-to-Partition-a-Binary-String](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/3864.Minimum-Cost-to-Partition-a-Binary-String) (M+) * ``Evaluate Expressions`` [241.Different-Ways-to-Add-Parentheses](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/241.Different-Ways-to-Add-Parentheses) (M+) [2019.The-Score-of-Students-Solving-Math-Expression](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/2019.The-Score-of-Students-Solving-Math-Expression) (H-) @@ -1021,6 +1209,20 @@ [1510.Stone-Game-IV](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/1510.Stone-Game-IV) (M) [1563.Stone-Game-V](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1563.Stone-Game-V) (H-) [2029.Stone-Game-IX](https://github.com/wisdompeak/LeetCode/tree/master/Others/2029.Stone-Game-IX) (H) +* ``Digit counting & finding`` +[440.K-th-Smallest-in-Lexicographical-Order](https://github.com/wisdompeak/LeetCode/tree/master/Others/440.K-th-Smallest-in-Lexicographical-Order) (H-) +[1012.Numbers-With-Repeated-Digits](https://github.com/wisdompeak/LeetCode/tree/master/Math/1012.Numbers-With-Repeated-Digits) (H-) +[1415.The-k-th-Lexicographical-String-of-All-Happy-Strings-of-Length-n](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/1415.The-k-th-Lexicographical-String-of-All-Happy-Strings-of-Length-n) (H-) +[1545.Find-Kth-Bit-in-Nth-Binary-String](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/1545.Find-Kth-Bit-in-Nth-Binary-String) (M+) +[2376.Count-Special-Integers](https://github.com/wisdompeak/LeetCode/tree/master/Others/2376.Count-Special-Integers) (M+) +[2719.Count-of-Integers](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/2719.Count-of-Integers) (H) +[2801.Count-Stepping-Numbers-in-Range](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/2801.Count-Stepping-Numbers-in-Range) (H) +[2827.Number-of-Beautiful-Integers-in-the-Range](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/2827.Number-of-Beautiful-Integers-in-the-Range) (H) +[2999.Count-the-Number-of-Powerful-Integers](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/2999.Count-the-Number-of-Powerful-Integers) (H-) +[3307.Find-the-K-th-Character-in-String-Game-II](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/3307.Find-the-K-th-Character-in-String-Game-II) (M) +[3490.Count-Beautiful-Numbers](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/3490.Count-Beautiful-Numbers) (M+) +[3614.Process-String-with-Special-Operations-II](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/3614.Process-String-with-Special-Operations-II) (H-) +[3704.Count-No-Zero-Pairs-That-Sum-to-N](https://github.com/wisdompeak/LeetCode/tree/master/Recursion/3704.Count-No-Zero-Pairs-That-Sum-to-N) (M+) #### [Graph](https://github.com/wisdompeak/LeetCode/tree/master/Graph/) [332.Reconstruct-Itinerary](https://github.com/wisdompeak/LeetCode/tree/master/DFS/332.Reconstruct-Itinerary) (H) @@ -1028,7 +1230,6 @@ [753.Cracking-the-Safe](https://github.com/wisdompeak/LeetCode/tree/master/Hash/753.Cracking-the-Safe) (H) [1059.All-Paths-from-Source-Lead-to-Destination](https://github.com/wisdompeak/LeetCode/tree/master/Graph/1059.All-Paths-from-Source-Lead-to-Destination) (H) [1192.Critical-Connections-in-a-Network](https://github.com/wisdompeak/LeetCode/tree/master/DFS/1192.Critical-Connections-in-a-Network) (H) -1334.Find-the-City-With-the-Smallest-Number-of-Neighbors-at-a-Threshold-Distance (TBD) 1361.Validate-Binary-Tree-Nodes (TBD) [1719.Number-Of-Ways-To-Reconstruct-A-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Graph/1719.Number-Of-Ways-To-Reconstruct-A-Tree) (H+) [1761.Minimum-Degree-of-a-Connected-Trio-in-a-Graph](https://github.com/wisdompeak/LeetCode/tree/master/Graph/1761.Minimum-Degree-of-a-Connected-Trio-in-a-Graph) (M+) @@ -1036,6 +1237,24 @@ [2360.Longest-Cycle-in-a-Graph](https://github.com/wisdompeak/LeetCode/tree/master/Graph/2360.Longest-Cycle-in-a-Graph) (M+) [2508.Add-Edges-to-Make-Degrees-of-All-Nodes-Even](https://github.com/wisdompeak/LeetCode/tree/master/Graph/2508.Add-Edges-to-Make-Degrees-of-All-Nodes-Even) (H-) [2556.Disconnect-Path-in-a-Binary-Matrix-by-at-Most-One-Flip](https://github.com/wisdompeak/LeetCode/tree/master/Graph/2556.Disconnect-Path-in-a-Binary-Matrix-by-at-Most-One-Flip) (H) +[2603.Collect-Coins-in-a-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Graph/2603.Collect-Coins-in-a-Tree) (H-) +[2608.Shortest-Cycle-in-a-Graph](https://github.com/wisdompeak/LeetCode/tree/master/Graph/2608.Shortest-Cycle-in-a-Graph) (M+) +[2791.Count-Paths-That-Can-Form-a-Palindrome-in-a-Tree](https://github.com/wisdompeak/LeetCode/tree/master/Graph/2791.Count-Paths-That-Can-Form-a-Palindrome-in-a-Tree) (H) +[2876.Count-Visited-Nodes-in-a-Directed-Graph](https://github.com/wisdompeak/LeetCode/tree/master/Graph/2876.Count-Visited-Nodes-in-a-Directed-Graph) (M+) +[3017.Count-the-Number-of-Houses-at-a-Certain-Distance-II](https://github.com/wisdompeak/LeetCode/tree/master/Graph/3017.Count-the-Number-of-Houses-at-a-Certain-Distance-II) (H) +* ``Dijkstra`` +[787.Cheapest-Flights-Within-K-Stops](https://github.com/wisdompeak/LeetCode/tree/master/Graph/787.Cheapest-Flights-Within-K-Stops) (H) +[2577.Minimum-Time-to-Visit-a-Cell-In-a-Grid](https://github.com/wisdompeak/LeetCode/tree/master/BFS/2577.Minimum-Time-to-Visit-a-Cell-In-a-Grid) (H-) +[2662.Minimum-Cost-of-a-Path-With-Special-Roads](https://github.com/wisdompeak/LeetCode/tree/master/BFS/2662.Minimum-Cost-of-a-Path-With-Special-Roads) (H-) +[2699.Modify-Graph-Edge-Weights](https://github.com/wisdompeak/LeetCode/tree/master/Graph/2699.Modify-Graph-Edge-Weights) (H) +[3112.Minimum-Time-to-Visit-Disappearing-Nodes](https://github.com/wisdompeak/LeetCode/tree/master/Graph/3112.Minimum-Time-to-Visit-Disappearing-Nodes) (M) +[3123.Find-Edges-in-Shortest-Paths](https://github.com/wisdompeak/LeetCode/tree/master/Graph/3123.Find-Edges-in-Shortest-Paths) (H-) +* ``Floyd`` +[1334.Find-the-City-With-the-Smallest-Number-of-Neighbors-at-a-Threshold-Distance](https://github.com/wisdompeak/LeetCode/tree/master/Graph/1334.Find-the-City-With-the-Smallest-Number-of-Neighbors-at-a-Threshold-Distance) (M) +[2642.Design-Graph-With-Shortest-Path-Calculator](https://github.com/wisdompeak/LeetCode/tree/master/Graph/2642.Design-Graph-With-Shortest-Path-Calculator) (M+) +[2959.Number-of-Possible-Sets-of-Closing-Branches](https://github.com/wisdompeak/LeetCode/tree/master/Graph/2959.Number-of-Possible-Sets-of-Closing-Branches) (M+) +[2976.Minimum-Cost-to-Convert-String-I](https://github.com/wisdompeak/LeetCode/tree/master/Graph/2976.Minimum-Cost-to-Convert-String-I) (M+) +[3387.Maximize-Amount-After-Two-Days-of-Conversions](https://github.com/wisdompeak/LeetCode/tree/master/Graph/3387.Maximize-Amount-After-Two-Days-of-Conversions) (H-) * ``Hungarian Algorithm`` [1820.Maximum-Number-of-Accepted-Invitations](https://github.com/wisdompeak/LeetCode/tree/master/Graph/1820.Maximum-Number-of-Accepted-Invitations) (H) [2123.Minimum-Operations-to-Remove-Adjacent-Ones-in-Matrix](https://github.com/wisdompeak/LeetCode/tree/master/Graph/2123.Minimum-Operations-to-Remove-Adjacent-Ones-in-Matrix) (H) @@ -1067,19 +1286,27 @@ [1680.Concatenation-of-Consecutive-Binary-Numbers](https://github.com/wisdompeak/LeetCode/tree/master/Math/1680.Concatenation-of-Consecutive-Binary-Numbers) (M) [1739.Building-Boxes](https://github.com/wisdompeak/LeetCode/tree/master/Math/1739.Building-Boxes) (H-) [1806.Minimum-Number-of-Operations-to-Reinitialize-a-Permutation](https://github.com/wisdompeak/LeetCode/tree/master/Math/1806.Minimum-Number-of-Operations-to-Reinitialize-a-Permutation) (H) +[1922.Count-Good-Numbers](https://github.com/wisdompeak/LeetCode/tree/master/Binary_Search/1922.Count-Good-Numbers) (M) [1969.Minimum-Non-Zero-Product-of-the-Array-Elements](https://github.com/wisdompeak/LeetCode/tree/master/Math/1969.Minimum-Non-Zero-Product-of-the-Array-Elements) (M+) [2128.Remove-All-Ones-With-Row-and-Column-Flips](https://github.com/wisdompeak/LeetCode/tree/master/Math/2128.Remove-All-Ones-With-Row-and-Column-Flips) (M+) [2217.Find-Palindrome-With-Fixed-Length](https://github.com/wisdompeak/LeetCode/tree/master/Math/2217.Find-Palindrome-With-Fixed-Length) (M+) * ``Distances`` -[296.Best-Meeting-Point](https://github.com/wisdompeak/LeetCode/tree/master/Math/296.Best-Meeting-Point) (M+) -[462.Minimum-Moves-to-Equal-Array-Elements-II](https://github.com/wisdompeak/LeetCode/tree/master/Math/462.Minimum-Moves-to-Equal-Array-Elements-II) (M-) -[2033.Minimum-Operations-to-Make-a-Uni-Value-Grid](https://github.com/wisdompeak/LeetCode/tree/master/Math/2033.Minimum-Operations-to-Make-a-Uni-Value-Grid) (M+) -[1703.Minimum-Adjacent-Swaps-for-K-Consecutive-Ones](https://github.com/wisdompeak/LeetCode/tree/master/Math/1703.Minimum-Adjacent-Swaps-for-K-Consecutive-Ones) (H) [1478.Allocate-Mailboxes](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1478.Allocate-Mailboxes) (H) -[2448.Minimum-Cost-to-Make-Array-Equal](https://github.com/wisdompeak/LeetCode/tree/master/Math/2448.Minimum-Cost-to-Make-Array-Equal) (H-) -[1131.Maximum-of-Absolute-Value-Expression](https://github.com/wisdompeak/LeetCode/tree/master/Math/1131.Maximum-of-Absolute-Value-Expression) (H) +[1131.Maximum-of-Absolute-Value-Expression](https://github.com/wisdompeak/LeetCode/tree/master/Math/1131.Maximum-of-Absolute-Value-Expression) (H) +[3102.Minimize-Manhattan-Distances](https://github.com/wisdompeak/LeetCode/tree/master/Math/3102.Minimize-Manhattan-Distances) (H) 1515.Best Position for a Service Centre (TBD) [1956.Minimum-Time-For-K-Virus-Variants-to-Spread](https://github.com/wisdompeak/LeetCode/tree/master/Math/1956.Minimum-Time-For-K-Virus-Variants-to-Spread) (H+) +* ``Median Theorem`` +[296.Best-Meeting-Point](https://github.com/wisdompeak/LeetCode/tree/master/Math/296.Best-Meeting-Point) (M+) +[462.Minimum-Moves-to-Equal-Array-Elements-II](https://github.com/wisdompeak/LeetCode/tree/master/Math/462.Minimum-Moves-to-Equal-Array-Elements-II) (M-) +[1703.Minimum-Adjacent-Swaps-for-K-Consecutive-Ones](https://github.com/wisdompeak/LeetCode/tree/master/Math/1703.Minimum-Adjacent-Swaps-for-K-Consecutive-Ones) (H) +[2033.Minimum-Operations-to-Make-a-Uni-Value-Grid](https://github.com/wisdompeak/LeetCode/tree/master/Math/2033.Minimum-Operations-to-Make-a-Uni-Value-Grid) (M+) +[2448.Minimum-Cost-to-Make-Array-Equal](https://github.com/wisdompeak/LeetCode/tree/master/Math/2448.Minimum-Cost-to-Make-Array-Equal) (H-) +[2607.Make-K-Subarray-Sums-Equal](https://github.com/wisdompeak/LeetCode/tree/master/Math/2607.Make-K-Subarray-Sums-Equal) (M+) +[1838.Frequency-of-the-Most-Frequent-Element](https://github.com/wisdompeak/LeetCode/tree/master/Two_Pointers/1838.Frequency-of-the-Most-Frequent-Element) (H-) +[2967.Minimum-Cost-to-Make-Array-Equalindromic](https://github.com/wisdompeak/LeetCode/tree/master/Math/2967.Minimum-Cost-to-Make-Array-Equalindromic) (H-) +[2968.Apply-Operations-to-Maximize-Frequency-Score](https://github.com/wisdompeak/LeetCode/tree/master/Math/2968.Apply-Operations-to-Maximize-Frequency-Score) (H-) +[3086.Minimum-Moves-to-Pick-K-Ones](https://github.com/wisdompeak/LeetCode/tree/master/Math/3086.Minimum-Moves-to-Pick-K-Ones) (H) * ``Geometry`` [223.Rectangle-Area](https://github.com/wisdompeak/LeetCode/tree/master/Math/223.Rectangle-Area) (M+) [335.Self-Crossing](https://github.com/wisdompeak/LeetCode/tree/master/Math/335.Self-Crossing) (H) @@ -1090,7 +1317,8 @@ [1401.Circle-and-Rectangle-Overlapping](https://github.com/wisdompeak/LeetCode/tree/master/Math/1401.Circle-and-Rectangle-Overlapping) (H) [1453.Maximum-Number-of-Darts-Inside-of-a-Circular-Dartboard](https://github.com/wisdompeak/LeetCode/tree/master/Math/1453.Maximum-Number-of-Darts-Inside-of-a-Circular-Dartboard) (H) [1610.Maximum-Number-of-Visible-Points](https://github.com/wisdompeak/LeetCode/tree/master/Math/1610.Maximum-Number-of-Visible-Points) (H) -[2280.Minimum-Lines-to-Represent-a-Line-Chart](https://github.com/wisdompeak/LeetCode/tree/master/Math/2280.Minimum-Lines-to-Represent-a-Line-Chart) (M) +[2280.Minimum-Lines-to-Represent-a-Line-Chart](https://github.com/wisdompeak/LeetCode/tree/master/Math/2280.Minimum-Lines-to-Represent-a-Line-Chart) (M) +[3197.Find-the-Minimum-Area-to-Cover-All-Ones-II](https://github.com/wisdompeak/LeetCode/tree/master/Math/3197.Find-the-Minimum-Area-to-Cover-All-Ones-II) (H-) * ``Random Pick`` [382.Linked-List-Random-Node](https://github.com/wisdompeak/LeetCode/tree/master/Math/382.Linked-List-Random-Node) (H) [470.Implement-Rand10()-Using-Rand7()](https://github.com/wisdompeak/LeetCode/tree/master/Math/470.Implement-Rand10--Using-Rand7) (M+) @@ -1116,7 +1344,15 @@ [1916.Count-Ways-to-Build-Rooms-in-an-Ant-Colony](https://github.com/wisdompeak/LeetCode/tree/master/Math/1916.Count-Ways-to-Build-Rooms-in-an-Ant-Colony) (H) [2221.Find-Triangular-Sum-of-an-Array](https://github.com/wisdompeak/LeetCode/tree/master/Math/2221.Find-Triangular-Sum-of-an-Array) (M) [2400.Number-of-Ways-to-Reach-a-Position-After-Exactly-k-Steps](https://github.com/wisdompeak/LeetCode/tree/master/Math/2400.Number-of-Ways-to-Reach-a-Position-After-Exactly-k-Steps) (M+) -[2514.Count-Anagrams](https://github.com/wisdompeak/LeetCode/tree/master/Math/2514.Count-Anagrams) (H-) +[2514.Count-Anagrams](https://github.com/wisdompeak/LeetCode/tree/master/Math/2514.Count-Anagrams) (H-) +[2539.Count-the-Number-of-Good-Subsequences](https://github.com/wisdompeak/LeetCode/tree/master/Math/2539.Count-the-Number-of-Good-Subsequences) (H-) +[2930.Number-of-Strings-Which-Can-Be-Rearranged-to-Contain-Substring](https://github.com/wisdompeak/LeetCode/tree/master/Math/2930.Number-of-Strings-Which-Can-Be-Rearranged-to-Contain-Substring) (H-) +[2954.Count-the-Number-of-Infection-Sequences](https://github.com/wisdompeak/LeetCode/tree/master/Math/2954.Count-the-Number-of-Infection-Sequences) (H) +[3395.Subsequences-with-a-Unique-Middle-Mode-I](https://github.com/wisdompeak/LeetCode/tree/master/Math/3395.Subsequences-with-a-Unique-Middle-Mode-I) (H) +[3405.Count-the-Number-of-Arrays-with-K-Matching-Adjacent-Elements](https://github.com/wisdompeak/LeetCode/tree/master/Math/3405.Count-the-Number-of-Arrays-with-K-Matching-Adjacent-Elements) (H-) +[3428.Maximum-and-Minimum-Sums-of-at-Most-Size-K-Subsequences](https://github.com/wisdompeak/LeetCode/tree/master/Math/3428.Maximum-and-Minimum-Sums-of-at-Most-Size-K-Subsequences) (M+) +[3463.Check-If-Digits-Are-Equal-in-String-After-Operations-II](https://github.com/wisdompeak/LeetCode/tree/master/Math/3463.Check-If-Digits-Are-Equal-in-String-After-Operations-II) (H+) +[3518.Smallest-Palindromic-Rearrangement-II](https://github.com/wisdompeak/LeetCode/tree/master/Math/3518.Smallest-Palindromic-Rearrangement-II) (H) * ``Numerical Theory`` [204.Count-Primes](https://github.com/wisdompeak/LeetCode/tree/master/Math/204.Count-Primes) (M) [343.Integer-Break](https://github.com/wisdompeak/LeetCode/tree/master/Math/343.Integer-Break) (H-) @@ -1126,19 +1362,24 @@ [2183.Count-Array-Pairs-Divisible-by-K](https://github.com/wisdompeak/LeetCode/tree/master/Math/2183.Count-Array-Pairs-Divisible-by-K) (M+) [2344.Minimum-Deletions-to-Make-Array-Divisible](https://github.com/wisdompeak/LeetCode/tree/master/Math/2344.Minimum-Deletions-to-Make-Array-Divisible) (E) [2543.Check-if-Point-Is-Reachable](https://github.com/wisdompeak/LeetCode/tree/master/Math/2543.Check-if-Point-Is-Reachable) (H) +[2654.Minimum-Number-of-Operations-to-Make-All-Array-Elements-Equal-to-1](https://github.com/wisdompeak/LeetCode/tree/master/Math/2654.Minimum-Number-of-Operations-to-Make-All-Array-Elements-Equal-to-1) (M) +[3164.Find-the-Number-of-Good-Pairs-II](https://github.com/wisdompeak/LeetCode/tree/master/Math/3164.Find-the-Number-of-Good-Pairs-II) (M+) +* ``Linear Basis`` +[3681.Maximum-XOR-of-Subsequences](https://github.com/wisdompeak/LeetCode/tree/master/Math/3681.Maximum-XOR-of-Subsequences) (H) +* ``Matrix Power`` +[2851.String-Transformation](https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/2851.String-Transformation) (H+) +[3700.Number-of-ZigZag-Arrays-II](https://github.com/wisdompeak/LeetCode/tree/master/Math/3700.Number-of-ZigZag-Arrays-II) (H) #### [Greedy](https://github.com/wisdompeak/LeetCode/tree/master/Greedy) [055.Jump-Game](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/055.Jump-Game) (E+) [045.Jump-Game-II](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/045.Jump-Game-II) (M) [134.Gas-Station](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/134.Gas-Station) (H) -[229.Majority-Element-II](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/229.Majority-Element-II) (H) [659.Split-Array-into-Consecutive-Subsequences](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/659.Split-Array-into-Consecutive-Subsequences) (H) [386.Lexicographical-Numbers](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/386.Lexicographical-Numbers) (H) 624.Maximum-Distance-in-Arrays (M) [665.Non-decreasing-Array](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/665.Non-decreasing-Array) (H) 670.Maximum-Swap (M+) 649.Dota2-Senate (H) -[330.Patching-Array](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/330.Patching-Array) (H) [683.K-Empty-Slots](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/683.K-Empty-Slots) (H) [517.Super-Washing-Machines](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/517.Super-Washing-Machines) (H) 870.Advantage-Shuffle (M) @@ -1156,9 +1397,7 @@ [1253.Reconstruct-a-2-Row-Binary-Matrix](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1253.Reconstruct-a-2-Row-Binary-Matrix) (M) [1354.Construct-Target-Array-With-Multiple-Sums](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1354.Construct-Target-Array-With-Multiple-Sums) (H-) [1414.Find-the-Minimum-Number-of-Fibonacci-Numbers-Whose-Sum-Is-K](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1414.Find-the-Minimum-Number-of-Fibonacci-Numbers-Whose-Sum-Is-K) (M+) -[1504.Count-Submatrices-With-All-Ones](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1504.Count-Submatrices-With-All-Ones) (M) [1505.Minimum-Possible-Integer-After-at-Most-K-Adjacent-Swaps-On-Digits](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1505.Minimum-Possible-Integer-After-at-Most-K-Adjacent-Swaps-On-Digits) (H) -[1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array) (H-) [1535.Find-the-Winner-of-an-Array-Game](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1535.Find-the-Winner-of-an-Array-Game) (M+) [1536.Minimum-Swaps-to-Arrange-a-Binary-Grid](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1536.Minimum-Swaps-to-Arrange-a-Binary-Grid) (H-) [1540.Can-Convert-String-in-K-Moves](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1540.Can-Convert-String-in-K-Moves) (M+) @@ -1171,7 +1410,6 @@ [1727.Largest-Submatrix-With-Rearrangements](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1727.Largest-Submatrix-With-Rearrangements) (M) [1744.Can-You-Eat-Your-Favorite-Candy-on-Your-Favorite-Day](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1744.Can-You-Eat-Your-Favorite-Candy-on-Your-Favorite-Day) (M) [1788.Maximize-the-Beauty-of-the-Garden](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1788.Maximize-the-Beauty-of-the-Garden) (M+) -[1798.Maximum-Number-of-Consecutive-Values-You-Can-Make](https://github.com/wisdompeak/LeetCode/blob/master/Greedy/1798.Maximum-Number-of-Consecutive-Values-You-Can-Make) (H-) [1818.Minimum-Absolute-Sum-Difference](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1818.Minimum-Absolute-Sum-Difference) (M+) [1850.Minimum-Adjacent-Swaps-to-Reach-the-Kth-Smallest-Number](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1850.Minimum-Adjacent-Swaps-to-Reach-the-Kth-Smallest-Number) (M+) [1911.Maximum-Alternating-Subsequence-Sum](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1911.Maximum-Alternating-Subsequence-Sum) (M+) @@ -1184,7 +1422,6 @@ [2216.Minimum-Deletions-to-Make-Array-Beautiful](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2216.Minimum-Deletions-to-Make-Array-Beautiful) (M+) [2242.Maximum-Score-of-a-Node-Sequence](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2242.Maximum-Score-of-a-Node-Sequence) (M+) [2257.Count-Unguarded-Cells-in-the-Grid](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2257.Count-Unguarded-Cells-in-the-Grid) (M+) -[2271.Maximum-White-Tiles-Covered-by-a-Carpet](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2271.Maximum-White-Tiles-Covered-by-a-Carpet) (M+) [2275.Largest-Combination-With-Bitwise-AND-Greater-Than-Zero](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2275.Largest-Combination-With-Bitwise-AND-Greater-Than-Zero) (M+) [2306.Naming-a-Company](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2306.Naming-a-Company) (H-) [2311.Longest-Binary-Subsequence-Less-Than-or-Equal-to-K](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2311.Longest-Binary-Subsequence-Less-Than-or-Equal-to-K) (H-) @@ -1198,6 +1435,32 @@ [2546.Apply-Bitwise-Operations-to-Make-Strings-Equal](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2546.Apply-Bitwise-Operations-to-Make-Strings-Equal) (M+) [2551.Put-Marbles-in-Bags](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2551.Put-Marbles-in-Bags) (M+) [2561.Rearranging-Fruits](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2561.Rearranging-Fruits) (H-) +[2598.Smallest-Missing-Non-negative-Integer-After-Operations](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2598.Smallest-Missing-Non-negative-Integer-After-Operations) (M) +[2813.Maximum-Elegance-of-a-K-Length-Subsequence](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2813.Maximum-Elegance-of-a-K-Length-Subsequence) (H-) +[2835.Minimum-Operations-to-Form-Subsequence-With-Target-Sum](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2835.Minimum-Operations-to-Form-Subsequence-With-Target-Sum) (M+) +[2871.Split-Array-Into-Maximum-Number-of-Subarrays](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2871.Split-Array-Into-Maximum-Number-of-Subarrays) (M+) +[2868.The-Wording-Game](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2868.The-Wording-Game) (M) +[2897.Apply-Operations-on-Array-to-Maximize-Sum-of-Squares](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2897.Apply-Operations-on-Array-to-Maximize-Sum-of-Squares) (M+) +[3022.Minimize-OR-of-Remaining-Elements-Using-Operations](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/3022.Minimize-OR-of-Remaining-Elements-Using-Operations) (H) +[3219.Minimum-Cost-for-Cutting-Cake-II](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/3219.Minimum-Cost-for-Cutting-Cake-II) (H) +[3635.Earliest-Finish-Time-for-Land-and-Water-Rides-II](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/3635.Earliest-Finish-Time-for-Land-and-Water-Rides-II) (H-) +[3696.Maximum-Distance-Between-Unequal-Words-in-Array-I](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/3696.Maximum-Distance-Between-Unequal-Words-in-Array-I) (M) +[3800.Minimum-Cost-to-Make-Two-Binary-Strings-Equal](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/3800.Minimum-Cost-to-Make-Two-Binary-Strings-Equal) (M+) +[3806.Maximum-Bitwise-AND-After-Increment-Operations](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/3806.Maximum-Bitwise-AND-After-Increment-Operations) (H-) +[3863.Minimum-Operations-to-Sort-a-String](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/3863.Minimum-Operations-to-Sort-a-String) (M+) +[3961.Maximize-Sum-of-Device-Ratings](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/3961.Maximize-Sum-of-Device-Ratings) (M+) +* ``Boyer-Moore Majority Voting`` +[229.Majority-Element-II](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/229.Majority-Element-II) (H) +[2335.Minimum-Amount-of-Time-to-Fill-Cups](https://github.com/wisdompeak/LeetCode/tree/master/Priority_Queue/2335.Minimum-Amount-of-Time-to-Fill-Cups) (M+) +[2856.Minimum-Array-Length-After-Pair-Removals](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2856.Minimum-Array-Length-After-Pair-Removals) (M) +[3139.Minimum-Cost-to-Equalize-Array](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/3139.Minimum-Cost-to-Equalize-Array) (H) +[3495.Minimum-Operations-to-Make-Array-Elements-Zero](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/3495.Minimum-Operations-to-Make-Array-Elements-Zero) (H) +[3664.Two-Letter-Card-Game](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/3664.Two-Letter-Card-Game) (H) +[3785.Minimum-Swaps-to-Avoid-Forbidden-Values](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/3785.Minimum-Swaps-to-Avoid-Forbidden-Values) (H) +* ``Lexicographical Sequence`` +[031.Next-Permutation](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/031.Next-Permutation) (M) +[556.Next-Greater-Element-III](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/556.Next-Greater-Element-III) (M) +[2663.Lexicographically-Smallest-Beautiful-String](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2663.Lexicographically-Smallest-Beautiful-String) (H-) * ``DI Sequence`` [942.DI-String-Match](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/942.DI-String-Match) (M) [484.Find-Permutation](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/484.Find-Permutation) (M) @@ -1211,7 +1474,8 @@ [354.Russian-Doll-Envelopes](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/354.Russian-Doll-Envelopes) (H-) [1713.Minimum-Operations-to-Make-a-Subsequence](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1713.Minimum-Operations-to-Make-a-Subsequence) (H-) [1964.Find-the-Longest-Valid-Obstacle-Course-at-Each-Position](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1964.Find-the-Longest-Valid-Obstacle-Course-at-Each-Position) (M+) -[2111.Minimum-Operations-to-Make-the-Array-K-Increasing](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2111.Minimum-Operations-to-Make-the-Array-K-Increasing) (M+) +[2111.Minimum-Operations-to-Make-the-Array-K-Increasing](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2111.Minimum-Operations-to-Make-the-Array-K-Increasing) (M+) +[3920.Maximize-Fixed-Points-After-Deletions](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/3920.Maximize-Fixed-Points-After-Deletions) (H-) * ``Two-pass distribution`` [135.Candy](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/135.Candy) (M+) [1840.Maximum-Building-Height](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1840.Maximum-Building-Height) (H) @@ -1231,7 +1495,8 @@ [2163.Minimum-Difference-in-Sums-After-Removal-of-Elements](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2163.Minimum-Difference-in-Sums-After-Removal-of-Elements) (M+) [2167.Minimum-Time-to-Remove-All-Cars-Containing-Illegal-Goods](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2167.Minimum-Time-to-Remove-All-Cars-Containing-Illegal-Goods) (H-) [2555.Maximize-Win-From-Two-Segments](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2555.Maximize-Win-From-Two-Segments) (M+) -[2565.Subsequence-With-the-Minimum-Score](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2565.Subsequence-With-the-Minimum-Score) (H-) +[2565.Subsequence-With-the-Minimum-Score](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2565.Subsequence-With-the-Minimum-Score) (H-) +[3872.Longest-Arithmetic-Sequence-After-Changing-At-Most-One-Element](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/3872.Longest-Arithmetic-Sequence-After-Changing-At-Most-One-Element) (M+) * ``State Machine`` [524.Longest-Word-in-Dictionary-through-Deleting](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/524.Longest-Word-in-Dictionary-through-Deleting) (M+) [727.Minimum-Window-Subsequence](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/727.Minimum-Window-Subsequence) (H-) @@ -1249,15 +1514,15 @@ 826.Most-Profit-Assigning-Work (M) [1268.Search-Suggestions-System](https://github.com/wisdompeak/LeetCode/tree/master/Trie/1268.Search-Suggestions-System) (H-) [1402.Reducing-Dishes](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1402.Reducing-Dishes) (M) -[1520.Maximum-Number-of-Non-Overlapping-Substrings](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1520.Maximum-Number-of-Non-Overlapping-Substrings) (H-) [1564.Put-Boxes-Into-the-Warehouse-I](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1564.Put-Boxes-Into-the-Warehouse-I) (M+) [1665.Minimum-Initial-Energy-to-Finish-Tasks](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1665.Minimum-Initial-Energy-to-Finish-Tasks) (H-) [1686.Stone-Game-VI](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1686.Stone-Game-VI) (H-) [1996.The-Number-of-Weak-Characters-in-the-Game](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1996.The-Number-of-Weak-Characters-in-the-Game) (M+) [2250.Count-Number-of-Rectangles-Containing-Each-Point](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2250.Count-Number-of-Rectangles-Containing-Each-Point) (H-) [2343.Query-Kth-Smallest-Trimmed-Number](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2343.Query-Kth-Smallest-Trimmed-Number) (H-) -[2412.Minimum-Money-Required-Before-Transactions](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2412.Minimum-Money-Required-Before-Transactions) (H-) -[2345.Finding-the-Number-of-Visible-Mountains](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2345.Finding-the-Number-of-Visible-Mountains) (H-) +[2412.Minimum-Money-Required-Before-Transactions](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2412.Minimum-Money-Required-Before-Transactions) (H-) +[2345.Finding-the-Number-of-Visible-Mountains](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2345.Finding-the-Number-of-Visible-Mountains) (H-) +[3027.Find-the-Number-of-Ways-to-Place-People-II](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/3027.Find-the-Number-of-Ways-to-Place-People-II) (M) * ``Indexing Sort`` [041.First-Missing-Positive](https://github.com/wisdompeak/LeetCode/blob/master/Greedy/041.First-Missing-Positive/Readme.md) (H) [268.Missing-Number](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/268.Missing-Number) (H-) @@ -1266,7 +1531,8 @@ [448.Find-All-Numbers-Disappeared-in-an-Array](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/448.Find-All-Numbers-Disappeared-in-an-Array) (M) [645.Set-Mismatch](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/645.Set-Mismatch) (M) [2471.Minimum-Number-of-Operations-to-Sort-a-Binary-Tree-by-Level](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2471.Minimum-Number-of-Operations-to-Sort-a-Binary-Tree-by-Level) (M+) -[2459.Sort-Array-by-Moving-Items-to-Empty-Space](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2459.Sort-Array-by-Moving-Items-to-Empty-Space) (H) +[2459.Sort-Array-by-Moving-Items-to-Empty-Space](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2459.Sort-Array-by-Moving-Items-to-Empty-Space) (H) +[3551.Minimum-Swaps-to-Sort-by-Digit-Sum](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/3551.Minimum-Swaps-to-Sort-by-Digit-Sum) (M) * ``Parenthesis`` [032.Longest-Valid-Parentheses](https://github.com/wisdompeak/LeetCode/tree/master/Stack/032.Longest-Valid-Parentheses) (H) [921.Minimum-Add-to-Make-Parentheses-Valid](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/921.Minimum-Add-to-Make-Parentheses-Valid) (M+) @@ -1283,10 +1549,17 @@ [1272.Remove-Interval](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1272.Remove-Interval) (M+) [1288.Remove-Covered-Intervals](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1288.Remove-Covered-Intervals) (M+) [1326.Minimum-Number-of-Taps-to-Open-to-Water-a-Garden](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1326.Minimum-Number-of-Taps-to-Open-to-Water-a-Garden) (M+) -[1235.Maximum-Profit-in-Job-Scheduling](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1235.Maximum-Profit-in-Job-Scheduling) (H-) -[1751.Maximum-Number-of-Events-That-Can-Be-Attended-II](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1751.Maximum-Number-of-Events-That-Can-Be-Attended-II) (H) -[2008.Maximum-Earnings-From-Taxi](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2008.Maximum-Earnings-From-Taxi) (M+) [2054.Two-Best-Non-Overlapping-Events](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2054.Two-Best-Non-Overlapping-Events) (H-) +[2580.Count-Ways-to-Group-Overlapping-Ranges](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2580.Count-Ways-to-Group-Overlapping-Ranges) (M) +[2589.Minimum-Time-to-Complete-All-Tasks](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2589.Minimum-Time-to-Complete-All-Tasks) (H) +[2983.Palindrome-Rearrangement-Queries](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2983.Palindrome-Rearrangement-Queries) (H+) +[2781.Length-of-the-Longest-Valid-Substring](https://github.com/wisdompeak/LeetCode/tree/master/String/2781.Length-of-the-Longest-Valid-Substring) (H-) +[3394.Check-if-Grid-can-be-Cut-into-Sections](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/3394.Check-if-Grid-can-be-Cut-into-Sections) (M) +[2271.Maximum-White-Tiles-Covered-by-a-Carpet](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2271.Maximum-White-Tiles-Covered-by-a-Carpet) (M+) +[3413.Maximum-Coins-From-K-Consecutive-Bags](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/3413.Maximum-Coins-From-K-Consecutive-Bags) (H-) +3104.Find Longest Self-Contained Substring (TBD) +[1520.Maximum-Number-of-Non-Overlapping-Substrings](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/1520.Maximum-Number-of-Non-Overlapping-Substrings) (H-) +[3458.Select-K-Disjoint-Special-Substrings](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/3458.Select-K-Disjoint-Special-Substrings) (H-) * ``Constructive Problems`` [324.Wiggle-Sort-II](https://github.com/wisdompeak/LeetCode/tree/master/Others/324.Wiggle-Sort-II) (H) [667.Beautiful-Arrangement-II](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/667.Beautiful-Arrangement-II) (M) @@ -1298,6 +1571,19 @@ [2202.Maximize-the-Topmost-Element-After-K-Moves](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2202.Maximize-the-Topmost-Element-After-K-Moves) (H) [2498.Frog-Jump-II](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2498.Frog-Jump-II) (H) [2499.minimum-total-cost-to-make-arrays-unequal](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2499.minimum-total-cost-to-make-arrays-unequal) (H) +[2567.Minimum-Score-by-Changing-Two-Elements](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2567.Minimum-Score-by-Changing-Two-Elements) (M) +[2568.Minimum-Impossible-OR](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2568.Minimum-Impossible-OR) (H-) +[2571.Minimum-Operations-to-Reduce-an-Integer-to-0](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2571.Minimum-Operations-to-Reduce-an-Integer-to-0) (H-) +[2573.Find-the-String-with-LCP](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2573.Find-the-String-with-LCP) (H-) +[2576.Find-the-Maximum-Number-of-Marked-Indices](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2576.Find-the-Maximum-Number-of-Marked-Indices) (H-) +[2712.Minimum-Cost-to-Make-All-Characters-Equal](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2712.Minimum-Cost-to-Make-All-Characters-Equal) (H-) +[2732.Find-a-Good-Subset-of-the-Matrix](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2732.Find-a-Good-Subset-of-the-Matrix) (H) +[2749.Minimum-Operations-to-Make-the-Integer-Zero](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2749.Minimum-Operations-to-Make-the-Integer-Zero) (H) +[2745.Construct-the-Longest-New-String](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2745.Construct-the-Longest-New-String) (H-) +[2753.Count-Houses-in-a-Circular-Street-II](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/2753.Count-Houses-in-a-Circular-Street-II) (H-) +[3012.Minimize-Length-of-Array-Using-Operations](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/3012.Minimize-Length-of-Array-Using-Operations) (H-) +3301.Maximize-the-Total-Height-of-Unique-Towers (M) +3397.Maximum Number-of-Distinct-Elements-After-Operations (M) #### [Simulation](https://github.com/wisdompeak/LeetCode/tree/master/Simulation) [2061.Number-of-Spaces-Cleaning-Robot-Cleaned](https://github.com/wisdompeak/LeetCode/tree/master/Simulation/2061.Number-of-Spaces-Cleaning-Robot-Cleaned) (M) @@ -1345,9 +1631,25 @@ [2359.Find-Closest-Node-to-Given-Two-Nodes](https://github.com/wisdompeak/LeetCode/tree/master/Others/2359.Find-Closest-Node-to-Given-Two-Nodes) (M) [2380.Time-Needed-to-Rearrange-a-Binary-String](https://github.com/wisdompeak/LeetCode/tree/master/Others/2380.Time-Needed-to-Rearrange-a-Binary-String) (H) [2453.Destroy-Sequential-Targets](https://github.com/wisdompeak/LeetCode/tree/master/Others/2453.Destroy-Sequential-Targets) (M) +[2591.Distribute-Money-to-Maximum-Children](https://github.com/wisdompeak/LeetCode/tree/master/Others/2591.Distribute-Money-to-Maximum-Children) (M+) +[2647.Color-the-Triangle-Red](https://github.com/wisdompeak/LeetCode/tree/master/Others/2647.Color-the-Triangle-Red) (H) +[2718.Sum-of-Matrix-After-Queries](https://github.com/wisdompeak/LeetCode/tree/master/Others/2718.Sum-of-Matrix-After-Queries) (M+) +[2808.Minimum-Seconds-to-Equalize-a-Circular-Array](https://github.com/wisdompeak/LeetCode/tree/master/Others/2808.Minimum-Seconds-to-Equalize-a-Circular-Array) (M+) +[2811.Check-if-it-is-Possible-to-Split-Array](https://github.com/wisdompeak/LeetCode/tree/master/Others/2811.Check-if-it-is-Possible-to-Split-Array) (M+) +[3068.Find-the-Maximum-Sum-of-Node-Values](https://github.com/wisdompeak/LeetCode/tree/master/Others/3068.Find-the-Maximum-Sum-of-Node-Values) (M+) +[3400.Maximum-Number-of-Matching-Indices-After-Right-Shifts](https://github.com/wisdompeak/LeetCode/tree/master/Others/3400.Maximum-Number-of-Matching-Indices-After-Right-Shifts) (M+) +* ``公式变形`` +[2898.Maximum-Linear-Stock-Score](https://github.com/wisdompeak/LeetCode/tree/master/Others/2898.Maximum-Linear-Stock-Score) (M) +* ``Collision`` +[853.Car-Fleet](https://github.com/wisdompeak/LeetCode/tree/master/Others/853.Car-Fleet) (M) +[1503.Last-Moment-Before-All-Ants-Fall-Out-of-a-Plank](https://github.com/wisdompeak/LeetCode/tree/master/Others/1503.Last-Moment-Before-All-Ants-Fall-Out-of-a-Plank) (M) +[2211.Count-Collisions-on-a-Road](https://github.com/wisdompeak/LeetCode/tree/master/Others/2211.Count-Collisions-on-a-Road) (M) +[2731.Movement-of-Robots](https://github.com/wisdompeak/LeetCode/tree/master/Others/2731.Movement-of-Robots) (M+) * ``结论转移`` [1685.Sum-of-Absolute-Differences-in-a-Sorted-Array](https://github.com/wisdompeak/LeetCode/tree/master/Others/1685.Sum-of-Absolute-Differences-in-a-Sorted-Array) (M) [2121.Intervals-Between-Identical-Elements](https://github.com/wisdompeak/LeetCode/tree/master/Others/2121.Intervals-Between-Identical-Elements) (M) +[2615.Sum-of-Distances](https://github.com/wisdompeak/LeetCode/tree/master/Others/2615.Sum-of-Distances) (M+) +[3086.Minimum-Moves-to-Pick-K-Ones](https://github.com/wisdompeak/LeetCode/tree/master/Math/3086.Minimum-Moves-to-Pick-K-Ones) (H) * ``Count Subarray by Element`` [828.Count-Unique-Characters-of-All-Substrings-of-a-Given-String](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/828.Count-Unique-Characters-of-All-Substrings-of-a-Given-String) (H-) [907.Sum-of-Subarray-Minimums](https://github.com/wisdompeak/LeetCode/tree/master/Stack/907.Sum-of-Subarray-Minimums) (H-) @@ -1358,6 +1660,10 @@ [2281.Sum-of-Total-Strength-of-Wizards](https://github.com/wisdompeak/LeetCode/tree/master/Others/2281.Sum-of-Total-Strength-of-Wizards) (H) [2302.Count-Subarrays-With-Score-Less-Than-K](https://github.com/wisdompeak/LeetCode/tree/master/Others/2302.Count-Subarrays-With-Score-Less-Than-K) (H-) [2444.Count-Subarrays-With-Fixed-Bounds](https://github.com/wisdompeak/LeetCode/tree/master/Others/2444.Count-Subarrays-With-Fixed-Bounds) (M+) +[2681.Power-of-Heroes](https://github.com/wisdompeak/LeetCode/tree/master/Others/2681.Power-of-Heroes) (H-) +[2763.Sum-of-Imbalance-Numbers-of-All-Subarrays](https://github.com/wisdompeak/LeetCode/tree/master/Others/2763.Sum-of-Imbalance-Numbers-of-All-Subarrays) (H-) +[2818.Apply-Operations-to-Maximize-Score](https://github.com/wisdompeak/LeetCode/tree/master/Others/2818.Apply-Operations-to-Maximize-Score) (H-) +[3428.Maximum-and-Minimum-Sums-of-at-Most-Size-K-Subsequences](https://github.com/wisdompeak/LeetCode/tree/master/Math/3428.Maximum-and-Minimum-Sums-of-at-Most-Size-K-Subsequences) (M+) * ``扫描线 / 差分数组`` [252.Meeting-Rooms](https://github.com/wisdompeak/LeetCode/tree/master/Others/252.Meeting-Rooms) (M) [253.Meeting-Rooms-II](https://github.com/wisdompeak/LeetCode/tree/master/Others/253.Meeting-Rooms-II) (M+) @@ -1369,7 +1675,8 @@ [798.Smallest-Rotation-with-Highest-Score](https://github.com/wisdompeak/LeetCode/tree/master/Others/798.Smallest-Rotation-with-Highest-Score) (H) [995.Minimum-Number-of-K-Consecutive-Bit-Flips](https://github.com/wisdompeak/LeetCode/tree/master/Others/995.Minimum-Number-of-K-Consecutive-Bit-Flips) (H-) [1094.Car-Pooling](https://github.com/wisdompeak/LeetCode/tree/master/Others/1094.Car-Pooling) (E) -[1109.Corporate-Flight-Bookings](https://github.com/wisdompeak/LeetCode/tree/master/Others/1109.Corporate-Flight-Bookings) (M) +[1109.Corporate-Flight-Bookings](https://github.com/wisdompeak/LeetCode/tree/master/Others/1109.Corporate-Flight-Bookings) (M) +[1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array](https://github.com/wisdompeak/LeetCode/tree/master/Others/1526.Minimum-Number-of-Increments-on-Subarrays-to-Form-a-Target-Array) (H-) [1589.Maximum-Sum-Obtained-of-Any-Permutation](https://github.com/wisdompeak/LeetCode/tree/master/Others/1589.Maximum-Sum-Obtained-of-Any-Permutation) (M) [1674.Minimum-Moves-to-Make-Array-Complementary](https://github.com/wisdompeak/LeetCode/tree/master/Others/1674.Minimum-Moves-to-Make-Array-Complementary) (H) [1871.Jump-Game-VII](https://github.com/wisdompeak/LeetCode/tree/master/Others/1871.Jump-Game-VII) (M+) @@ -1382,6 +1689,13 @@ [2251.Number-of-Flowers-in-Full-Bloom](https://github.com/wisdompeak/LeetCode/tree/master/Others/2251.Number-of-Flowers-in-Full-Bloom) (M) [2327.Number-of-People-Aware-of-a-Secret](https://github.com/wisdompeak/LeetCode/tree/master/Others/2327.Number-of-People-Aware-of-a-Secret) (H-) [2381.Shifting-Letters-II](https://github.com/wisdompeak/LeetCode/tree/master/Others/2381.Shifting-Letters-II) (M) +[2584.Split-the-Array-to-Make-Coprime-Products](https://github.com/wisdompeak/LeetCode/tree/master/Others/2584.Split-the-Array-to-Make-Coprime-Products) (H) +[2617.Minimum-Number-of-Visited-Cells-in-a-Grid](https://github.com/wisdompeak/LeetCode/tree/master/Others/2617.Minimum-Number-of-Visited-Cells-in-a-Grid) (H) +[2772.Apply-Operations-to-Make-All-Array-Elements-Equal-to-Zero](https://github.com/wisdompeak/LeetCode/tree/master/Others/2772.Apply-Operations-to-Make-All-Array-Elements-Equal-to-Zero) (H-) +[2963.Count-the-Number-of-Good-Partitions](https://github.com/wisdompeak/LeetCode/tree/master/Others/2963.Count-the-Number-of-Good-Partitions) (H-) +[3009.Maximum-Number-of-Intersections-on-the-Chart](https://github.com/wisdompeak/LeetCode/tree/master/Others/3009.Maximum-Number-of-Intersections-on-the-Chart) (H) +[3169.Count-Days-Without-Meetings](https://github.com/wisdompeak/LeetCode/tree/master/Others/3169.Count-Days-Without-Meetings) (M) +[3655.XOR-After-Range-Multiplication-Queries-II](https://github.com/wisdompeak/LeetCode/tree/master/Others/3655.XOR-After-Range-Multiplication-Queries-II) (H+) * ``二维差分`` [850.Rectangle-Area-II](https://github.com/wisdompeak/LeetCode/tree/master/Others/850.Rectangle-Area-II) (H) [2132.Stamping-the-Grid](https://github.com/wisdompeak/LeetCode/tree/master/Others/2132.Stamping-the-Grid) (H) @@ -1396,7 +1710,14 @@ [1714.Sum-Of-Special-Evenly-Spaced-Elements-In-Array](https://github.com/wisdompeak/LeetCode/tree/master/Others/1714.Sum-Of-Special-Evenly-Spaced-Elements-In-Array) (H) [1737.Change-Minimum-Characters-to-Satisfy-One-of-Three-Conditions](https://github.com/wisdompeak/LeetCode/tree/master/Others/1737.Change-Minimum-Characters-to-Satisfy-One-of-Three-Conditions) (M+) [2013.Detect-Squares](https://github.com/wisdompeak/LeetCode/tree/master/Others/2013.Detect-Squares) (M+) -[2552.Count-Increasing-Quadruplets](https://github.com/wisdompeak/LeetCode/tree/master/Others/2552.Count-Increasing-Quadruplets) (H-) +[2552.Count-Increasing-Quadruplets](https://github.com/wisdompeak/LeetCode/tree/master/Others/2552.Count-Increasing-Quadruplets) (H-) +[2768.Number-of-Black-Blocks](https://github.com/wisdompeak/LeetCode/tree/master/Others/2768.Number-of-Black-Blocks) (M+) +[2857.Count-Pairs-of-Points-With-Distance-k](https://github.com/wisdompeak/LeetCode/tree/master/Others/2857.Count-Pairs-of-Points-With-Distance-k) (M+) +[3404.Count-Special-Subsequences](https://github.com/wisdompeak/LeetCode/tree/master/Others/3404.Count-Special-Subsequences) (H) +[3447.Assign-Elements-to-Groups-with-Constraints](https://github.com/wisdompeak/LeetCode/tree/master/Others/3447.Assign-Elements-to-Groups-with-Constraints) (M+) +[3628.Maximum-Number-of-Subsequences-After-One-Inserting](https://github.com/wisdompeak/LeetCode/tree/master/Others/3628.Maximum-Number-of-Subsequences-After-One-Inserting) (H-) +[3640.Trionic-Array-II](https://github.com/wisdompeak/LeetCode/tree/master/Others/3640.Trionic-Array-II) (M+) +[3886.Sum-of-Sortable-Integers](https://github.com/wisdompeak/LeetCode/blob/master/Others/3886.Sum-of-Sortable-Integers/3886.Sum-of-Sortable-Integers.cpp) (M+) * ``Presum`` [1878.Get-Biggest-Three-Rhombus-Sums-in-a-Grid](https://github.com/wisdompeak/LeetCode/tree/master/Others/1878.Get-Biggest-Three-Rhombus-Sums-in-a-Grid) (M+) [1906.Minimum-Absolute-Difference-Queries](https://github.com/wisdompeak/LeetCode/tree/master/Others/1906.Minimum-Absolute-Difference-Queries) (M+) @@ -1412,14 +1733,26 @@ [347.Top-K-Frequent-Elements](https://github.com/wisdompeak/LeetCode/tree/master/Others/347.Top-K-Frequent-Elements) (M+) [973.K-Closest-Points-to-Origin](https://github.com/wisdompeak/LeetCode/tree/master/Others/973.K-Closest-Points-to-Origin) (M) [324.Wiggle-Sort-II](https://github.com/wisdompeak/LeetCode/tree/master/Others/324.Wiggle-Sort-II) (H) -* ``数位计算`` -[233.Number-of-Digit-One](https://github.com/wisdompeak/LeetCode/tree/master/Math/233.Number-of-Digit-One) (H-) +* ``Digit counting`` +[233.Number-of-Digit-One](https://github.com/wisdompeak/LeetCode/tree/master/Math/233.Number-of-Digit-One) (H-) +[3007.Maximum-Number-That-Sum-of-the-Prices-Is-Less-Than-or-Equal-to-K](https://github.com/wisdompeak/LeetCode/tree/master/Others/3007.Maximum-Number-That-Sum-of-the-Prices-Is-Less-Than-or-Equal-to-K) (H) [1067.Digit-Count-in-Range](https://github.com/wisdompeak/LeetCode/tree/master/Others/1067.Digit-Count-in-Range) (H) [357.Count-Numbers-with-Unique-Digits](https://github.com/wisdompeak/LeetCode/tree/master/Others/357.Count-Numbers-with-Unique-Digits) (M) -[2376.Count-Special-Integers](https://github.com/wisdompeak/LeetCode/tree/master/Others/2376.Count-Special-Integers) (M+) -[1012.Numbers-With-Repeated-Digits](https://github.com/wisdompeak/LeetCode/tree/master/Math/1012.Numbers-With-Repeated-Digits) (H-) [2417.Closest-Fair-Integer](https://github.com/wisdompeak/LeetCode/tree/master/Others/2417.Closest-Fair-Integer) (H-) +#### [Thinking](https://github.com/wisdompeak/LeetCode/tree/master/Thinking)   +[2860.Happy-Students](https://github.com/wisdompeak/LeetCode/tree/master/Thinking/2860.Happy-Students) (M+) +[2862.Maximum-Element-Sum-of-a-Complete-Subset-of-Indices](https://github.com/wisdompeak/LeetCode/tree/master/Thinking/2862.Maximum-Element-Sum-of-a-Complete-Subset-of-Indices) (H-) +[2910.Minimum-Number-of-Groups-to-Create-a-Valid-Assignment](https://github.com/wisdompeak/LeetCode/tree/master/Thinking/2910.Minimum-Number-of-Groups-to-Create-a-Valid-Assignment) (H-) +[2939.Maximum-Xor-Product](https://github.com/wisdompeak/LeetCode/tree/master/Thinking/2939.Maximum-Xor-Product) (H-) +[2957.Remove-Adjacent-Almost-Equal-Characters](https://github.com/wisdompeak/LeetCode/tree/master/Thinking/2957.Remove-Adjacent-Almost-Equal-Characters) (M) +[330.Patching-Array](https://github.com/wisdompeak/LeetCode/tree/master/Greedy/330.Patching-Array) (H) +[1798.Maximum-Number-of-Consecutive-Values-You-Can-Make](https://github.com/wisdompeak/LeetCode/blob/master/Greedy/1798.Maximum-Number-of-Consecutive-Values-You-Can-Make) (H-) +[2952.Minimum-Number-of-Coins-to-be-Added](https://github.com/wisdompeak/LeetCode/tree/master/Thinking/2952.Minimum-Number-of-Coins-to-be-Added) (H-) +[3609.Minimum-Moves-to-Reach-Target-in-Grid](https://github.com/wisdompeak/LeetCode/tree/master/Thinking/3609.Minimum-Moves-to-Reach-Target-in-Grid) (H) +[3644.Maximum-K-to-Sort-a-Permutation](https://github.com/wisdompeak/LeetCode/tree/master/Thinking/3644.Maximum-K-to-Sort-a-Permutation) (H) +[3660.Jump-Game-IX](https://github.com/wisdompeak/LeetCode/tree/master/Thinking/3660.Jump-Game-IX) (H) + #### [LeetCode Cup](https://github.com/wisdompeak/LeetCode/tree/master/LCCUP) [LCP23.魔术排列](https://github.com/wisdompeak/LeetCode/tree/master/LCCUP/2020Fall/LCP23.%E9%AD%94%E6%9C%AF%E6%8E%92%E5%88%97) [LCP24.数字游戏](https://github.com/wisdompeak/LeetCode/tree/master/LCCUP/2020Fall/LCP24.%E6%95%B0%E5%AD%97%E6%B8%B8%E6%88%8F) @@ -1436,8 +1769,10 @@ [Inverse_Element](https://github.com/wisdompeak/LeetCode/tree/master/Template/Inverse_Element) [Graph](https://github.com/wisdompeak/LeetCode/tree/master/Template/Graph) [Bit_Manipulation](https://github.com/wisdompeak/LeetCode/tree/master/Template/Bit_manipulation) -[RB_Tree](https://github.com/wisdompeak/LeetCode/tree/master/Template/RB_Tree) -[二维子矩阵求和](https://github.com/wisdompeak/LeetCode/tree/master/Template/Sub_Rect_Sum_2D) +[RB_Tree](https://github.com/wisdompeak/LeetCode/tree/master/Template/RB_Tree) +[Binary_Lift](https://github.com/wisdompeak/LeetCode/tree/master/Template/Binary_Lift) +[Union_Find](https://github.com/wisdompeak/LeetCode/tree/master/Template/Union_Find) +[二维子矩阵求和](https://github.com/wisdompeak/LeetCode/tree/master/Template/Sub_Rect_Sum_2D) [二维差分数组](https://github.com/wisdompeak/LeetCode/tree/master/Template/Diff_Array_2D) [CPP_LANG](https://github.com/wisdompeak/LeetCode/tree/master/Template/CPP_LANG) diff --git a/Recursion/1415.The-k-th-Lexicographical-String-of-All-Happy-Strings-of-Length-n/Readme.md b/Recursion/1415.The-k-th-Lexicographical-String-of-All-Happy-Strings-of-Length-n/Readme.md index 4bd11e3cc..2757aebc5 100644 --- a/Recursion/1415.The-k-th-Lexicographical-String-of-All-Happy-Strings-of-Length-n/Readme.md +++ b/Recursion/1415.The-k-th-Lexicographical-String-of-All-Happy-Strings-of-Length-n/Readme.md @@ -6,6 +6,7 @@ #### 解法2 更聪明点的递归。 -当我们尝试填写长度为n的字符串的首字母时,无论首字母是什么,之后的n-1位都有pow(2,n-1)种填写方法。所以我们用k/pow(2,n-1)就可以确定此时的首字母ch应该是字母表的第几个。注意这里的k应该用0-index更为方便。比如k=0,那么ch应该就是'a',如果k=1,那么ch应该就是'b'. +当我们尝试填写长度为n的字符串的首字母时,无论首字母是什么,之后的n-1位都有pow(2,n-1)种填写方法。所以我们用`t = k/pow(2,n-1)`就可以确定此时的首字母ch应该是字母表的第几个。注意这里的k和t都用0-index更为方便。比如t=0,那么ch应该就是'a',如果t=1,那么ch应该就是'b'. + +但是我们还需要考虑到之前一位的制约。如果发现计算得到的ch比上一位字母要大,那么意味着实际填写的ch还需要再加1。比如,上一个位置是'a',本轮计算得到`t=1`,意味着我们需要跳过`2^(n-1)`种排列。但注意这`2^(n-1)`种排列并不是对应的`axx..xx`,因为它与上一个位置'a'冲突。所以我们只能认为这`2^(n-1)`种排列对应的是`bxx..xx`。故跳过他们之后,我们认为本位置必须是填写`c`. -但是我们还需要考虑到之前一位的制约。如果发现计算得到的ch比上一位字母要大,那么意味着当前字母基数应该加1。因为此位我们不能尝试和前面一样的字母,所以会少pow(2,n-1)的可能性。 diff --git a/Recursion/1545.Find-Kth-Bit-in-Nth-Binary-String/1545.Find-Kth-Bit-in-Nth-Binary-String.cpp b/Recursion/1545.Find-Kth-Bit-in-Nth-Binary-String/1545.Find-Kth-Bit-in-Nth-Binary-String.cpp new file mode 100644 index 000000000..7cf549c8a --- /dev/null +++ b/Recursion/1545.Find-Kth-Bit-in-Nth-Binary-String/1545.Find-Kth-Bit-in-Nth-Binary-String.cpp @@ -0,0 +1,28 @@ +class Solution { + vectorlen; +public: + char findKthBit(int n, int k) + { + len.resize(n+1); + len[1] = 1; + for (int i=2; i<=n; i++) + len[i] = len[i-1]*2+1; + + return dfs(n, k); + } + + char dfs(int n, int k) + { + if (n==1) return '0'; + if (k==len[n]/2+1) return '1'; + if (knext[55]; + int n; + int count[55]; + int plan0[55]; + int plan1[55]; + int val[55]; +public: + int minimumTotalPrice(int n, vector>& edges, vector& price, vector>& trips) + { + for (auto& edge: edges) + { + int a = edge[0], b = edge[1]; + next[a].push_back(b); + next[b].push_back(a); + } + for (int i=0; i=min_sum && digitSum<=max_sum) ret = (ret+1)%M; + return ret; + } + + int calculate(string& s) + { + int ret = 0; + for (auto ch:s) ret += ch-'0'; + return ret; + } + + LL CountNoGreater(string num, int max_sum) + { + vector>>memo(2, vector>(25, vector(405, -1))); + return dfs(num, max_sum, 0, 0, true, memo); + } + + LL dfs(string num, int max_sum, int i, int sum, bool isSame, vector>>&memo) + { + if (sum > max_sum) return 0; + if (memo[isSame][i][sum]!=-1) return memo[isSame][i][sum]; + if (i==num.size()) return 1; + + LL ret = 0; + if (!isSame) + { + for (int k=0; k<=9; k++) + { + ret += dfs(num, max_sum, i+1, sum+k, false, memo); + ret %= M; + } + } + else + { + for (int k=0; k<(num[i]-'0'); k++) + { + ret += dfs(num, max_sum, i+1, sum+k, false, memo); + ret %= M; + } + ret += dfs(num, max_sum, i+1, sum+(num[i]-'0'), true, memo); + ret %= M; + } + + memo[isSame][i][sum] = ret; + return ret; + } +}; + + diff --git a/Recursion/2719.Count-of-Integers/Readme.md b/Recursion/2719.Count-of-Integers/Readme.md new file mode 100644 index 000000000..e578450c2 --- /dev/null +++ b/Recursion/2719.Count-of-Integers/Readme.md @@ -0,0 +1,15 @@ +### 2719.Count-of-Integers + +求介于两个范围[low, high]之间的、符合条件的元素个数,一个非常常见的套路,就是只写一个求不高于某上界、符合条件的元素个数`NoGreaterThan`.这样答案就是`NoGreaterThan(high)-NoGreaterThan(low-1)`. + +本题有两个不同类型的范围限制,数值大小的范围和digitsum的范围。我们用同样的套路,写函数`NoGreaterThan(string num, int max_sum)`,求数值上不超过num,digitSum不超过max_sum的元素个数,这样最终答案就是 +``` +return (NoGreaterThan(num2, max_sum)-NoGreaterThan(num2, min_sum-1)) + - (NoGreaterThan(num1-1, max_sum)-NoGreaterThan(num1-1, min_sum-1)); +``` + +在编写`NoGreaterThan`的时候,我们递归考察num的每个位置,尝试可以填写哪些digits。用记忆化来避免重复的函数调用。 + +其中一个比较重要的逻辑就是,如果我们给前i位设置的digits比num对应前缀要小,那么第i位上我们可以任意设置0~9都可以满足要求(即不超过num)。反之,如果给前i位设置的digits与num的对应前缀完全吻合,那么在第i位上的设置就不能超过num[i](否则就超过了num)。所以递归的时候我们需要有一个bool量的标记,表示在处理当前位i之前,我们是否设置了完全与num前缀相同的digits。 + +此外,对于cpp而言,我们比较难直接得到num-1的字符串形式。技巧是我们将num1单独处理即可。 diff --git a/Recursion/2801.Count-Stepping-Numbers-in-Range/2801.Count-Stepping-Numbers-in-Range.cpp b/Recursion/2801.Count-Stepping-Numbers-in-Range/2801.Count-Stepping-Numbers-in-Range.cpp new file mode 100644 index 000000000..1aa77f62c --- /dev/null +++ b/Recursion/2801.Count-Stepping-Numbers-in-Range/2801.Count-Stepping-Numbers-in-Range.cpp @@ -0,0 +1,78 @@ +using LL = long long; +LL M = 1e9+7; +class Solution { +public: + int countSteppingNumbers(string low, string high) + { + LL ret = helper(high) - helper(low); + ret = (ret + M) % M; + ret = (ret + check(low) + M) % M; + + return ret; + } + + bool check(string s) + { + for (int i=1; i= 0) + ret = (ret + dfs(len-1, prev-1, false, num, memo)) % M; + } + else + { + int D = num[n-len] - '0'; + if (prev+1 < D) + ret += dfs(len-1, prev+1, false, num, memo); + else if (prev+1 == D) + ret += dfs(len-1, prev+1, true, num, memo); + ret %= M; + + if (prev-1 >= 0 && prev-1 < D) + ret += dfs(len-1, prev-1, false, num, memo); + else if (prev-1 >= 0 && prev-1 == D) + ret += dfs(len-1, prev-1, true, num, memo); + ret %= M; + } + + memo[len][prev][isSame] = ret; + return ret; + } +}; diff --git a/Recursion/2801.Count-Stepping-Numbers-in-Range/Readme.md b/Recursion/2801.Count-Stepping-Numbers-in-Range/Readme.md new file mode 100644 index 000000000..ce7f3d2d0 --- /dev/null +++ b/Recursion/2801.Count-Stepping-Numbers-in-Range/Readme.md @@ -0,0 +1,13 @@ +### 2801.Count-Stepping-Numbers-in-Range + +首先依据套路转化为前缀之差的形式:`return helper(high) - helper(low) + check(low)`. 其中helper(num)表示求[1,num]区间内符合要求的数的个数。 + +我们用dfs的方法来这个填充每一位。设计`dfs(len, prev, isSame)`表示在当前“状态”最终会有多少个合法的数字,其中len表示还有多少位需要填充,prev表示上一位填充的数字是什么,isSame表示之前填充的所有数字是否与num的前缀贴合。 + +1. 如果isSame==false,那么只要`prev+1<=9`,那么就可以在当前位填充prev+1;只要`prev-1>=0`,那么就可以在当前位填充prev-1. 递归函数里的isSame都是false。 + +2. 如果isSame==true,令当前位置上num的数字是D,那么只要`prev+1=0 && prev-1k = k; + return helper(high, k) - helper(low-1, k); + } + + int helper(LL num, int k) + { + int memo[11][2][22][22]; + memset(memo, -1, sizeof(memo)); + + string Num = to_string(num); + int n = Num.size(); + + int ret = 0; + for (int len = 2; len < n; len+=2) + { + for (int d=1; d<=9; d++) + ret += dfs(len-1, false, (d%2==0)*2-1, d%k, Num, memo); + } + + if (n%2==0) + { + int D = Num[0]-'0'; + for (int d=1; d=x) return y-x; + + if (memo[x]!=0) return memo[x]; + + int ret = INT_MAX/2; + ret = min(ret, minimumOperationsToMakeEqual( (x-(x%11))/11, y) + x%11+1); + ret = min(ret, minimumOperationsToMakeEqual( (x+ (11-x%11))/11, y) + (11-x%11) + 1); + + ret = min(ret, minimumOperationsToMakeEqual( (x-(x%5))/5, y) + x%5+1); + ret = min(ret, minimumOperationsToMakeEqual( (x+(5-x%5))/5, y) + (5-x%5)+1); + + ret = min(ret, x-y); + + memo[x] = ret; + + return ret; + } +}; diff --git a/Recursion/2998.Minimum-Number-of-Operations-to-Make-X-and-Y-Equal/Readme.md b/Recursion/2998.Minimum-Number-of-Operations-to-Make-X-and-Y-Equal/Readme.md new file mode 100644 index 000000000..7116e1b72 --- /dev/null +++ b/Recursion/2998.Minimum-Number-of-Operations-to-Make-X-and-Y-Equal/Readme.md @@ -0,0 +1,10 @@ +### 2998.Minimum-Number-of-Operations-to-Make-X-and-Y-Equal + +因为除法操作最高效,所有的增减操作都是为了能够凑出除法操作。所以当x>y时,我们想要将x拉低至y,只需要考虑以下五种操作: +1. 增加x,使得x能被11整除 +2. 减小x,使得x能被11整除 +3. 增加x,使得x能被5整除 +4. 减小x,使得x能被5整除 +5. 直接将x与y拉平。 + +此外,本题需要记忆化来提升效率。 diff --git a/Recursion/2999.Count-the-Number-of-Powerful-Integers/2999.Count-the-Number-of-Powerful-Integers.cpp b/Recursion/2999.Count-the-Number-of-Powerful-Integers/2999.Count-the-Number-of-Powerful-Integers.cpp new file mode 100644 index 000000000..c345b0ae5 --- /dev/null +++ b/Recursion/2999.Count-the-Number-of-Powerful-Integers/2999.Count-the-Number-of-Powerful-Integers.cpp @@ -0,0 +1,43 @@ +using LL = long long; +class Solution { +public: + long long numberOfPowerfulInt(long long start, long long finish, int limit, string s) + { + return helper(to_string(finish), limit, s) - helper(to_string(start-1), limit, s); + } + + LL helper(string a, int limit, string s) + { + if (a.size() < s.size()) return 0; + return dfs(a, s, limit, 0, true); + } + + LL dfs(string a, string s, int limit, int k, bool same) + { + if (a.size() - k == s.size()) + { + int len = s.size(); + if (!same || a.substr(a.size()-len, len) >= s) return 1; + else return 0; + } + + LL ret = 0; + if (!same) + { + int d = a.size()-s.size()-k; + ret = pow(1+limit, d); + } + else + { + for (int i=0; i<=limit; i++) + { + if (i > a[k]-'0') break; + else if (i == a[k]-'0') + ret += dfs(a, s, limit, k+1, true); + else + ret += dfs(a, s, limit, k+1, false); + } + } + return ret; + } +}; diff --git a/Recursion/2999.Count-the-Number-of-Powerful-Integers/Readme.md b/Recursion/2999.Count-the-Number-of-Powerful-Integers/Readme.md new file mode 100644 index 000000000..fc0cc3ebf --- /dev/null +++ b/Recursion/2999.Count-the-Number-of-Powerful-Integers/Readme.md @@ -0,0 +1,7 @@ +### 2999.Count-the-Number-of-Powerful-Integers + +首先,对于区间内的计数,常用的技巧就是转化为`helper(to_string(finish), limit, s) - helper(to_string(start-1), limit, s)`,其中`helper(string a, int limit, string s)`表示在[1:a]区间内有多少符合条件的数(即每个digit不超过limit且后缀为s)。 + +接下来写helper函数。令上限a的长度为d,那么我们计数的时候只需要逐位填充、循环d次即可。对于第k位而言,分两种情况: +1. 如果填充的前k-1位小于a同样长度的前缀,那么第k位可以任意填充0 ~ limit都不会超过上限a。甚至从第k+1位起,直至固定的后缀s之前,总共有`d = a.size()-s.size()-k`位待填充的数字,都可以任意填充为0~limit。故直接返回计数结果:`pow(1+limit, d)`. +2. 如果填充的前k-1位等于a同样长度的前缀,那么第k位可以填充为0 ~ min(limit, a[k])。确定之后,接下来递归处理下一位即可。注意,如果填充为a[k]的话,需要告知递归函数“已构造的前缀继续与a相同”,否则告知递归函数“已构造的前缀小于a”。这样下一轮递归函数知道选择哪一个分支。 diff --git a/Recursion/3307.Find-the-K-th-Character-in-String-Game-II/3307.Find-the-K-th-Character-in-String-Game-II.cpp b/Recursion/3307.Find-the-K-th-Character-in-String-Game-II/3307.Find-the-K-th-Character-in-String-Game-II.cpp new file mode 100644 index 000000000..5bce51cfe --- /dev/null +++ b/Recursion/3307.Find-the-K-th-Character-in-String-Game-II/3307.Find-the-K-th-Character-in-String-Game-II.cpp @@ -0,0 +1,31 @@ +using LL = long long; +class Solution { +public: + char kthCharacter(long long k, vector& operations) + { + LL n = 1; + int t = 0; + while (n=0; i--) + { + if (k>n/2) + { + if (operations[i]==0) + k = k-n/2; + else + { + k = k-n/2; + count++; + } + } + n/=2; + } + return 'a' + (count) % 26; + } +}; diff --git a/Recursion/3307.Find-the-K-th-Character-in-String-Game-II/Readme.md b/Recursion/3307.Find-the-K-th-Character-in-String-Game-II/Readme.md new file mode 100644 index 000000000..03d131228 --- /dev/null +++ b/Recursion/3307.Find-the-K-th-Character-in-String-Game-II/Readme.md @@ -0,0 +1,5 @@ +### 3307.Find-the K-th-Character-in-String-Game-II + +假设当前总共有n个字符,求其中的第k个。显然,如果k是在前n/2里,那么等效于求`dfs(n/2,k)`。如果是在后半部分,那么它其实是前半部分里第`k-n/2`个字符shift零次或一次(取决于operation类型)之后的结果,即等效于`dfs(n/2,k-n/2)+1`. 以此递归处理即可。时间就是logN. + +此题类似 1545.Find-Kth-Bit-in-Nth-Binary-String diff --git a/Recursion/3490.Count-Beautiful-Numbers/3490.Count-Beautiful-Numbers.cpp b/Recursion/3490.Count-Beautiful-Numbers/3490.Count-Beautiful-Numbers.cpp new file mode 100644 index 000000000..0891090f8 --- /dev/null +++ b/Recursion/3490.Count-Beautiful-Numbers/3490.Count-Beautiful-Numbers.cpp @@ -0,0 +1,47 @@ +using State = tuple; + +class Solution { +public: + map memo; + vector digits; + + int dfs(int pos, int sum, int product, bool tight, bool leading_zero) + { + if (pos == digits.size()) { + return (sum > 0) && (product % sum == 0); + } + + State key = {pos, sum, product, tight, leading_zero}; + if (memo.find(key) != memo.end()) return memo[key]; + + int limit = (tight ? digits[pos] : 9); + int res = 0; + + for (int d = 0; d <= limit; d++) + { + res += dfs(pos + 1, sum + d, (leading_zero && d == 0) ? 1 : product * d, tight && (d == limit), leading_zero && (d == 0)); + } + + return memo[key] = res; + } + + int count(int T) + { + if (T <= 0) return 0; + digits.clear(); + memo.clear(); + + while (T > 0) + { + digits.push_back(T % 10); + T /= 10; + } + reverse(digits.begin(), digits.end()); + return dfs(0, 0, 1, true, true); + } + + int beautifulNumbers(int l, int r) + { + return count(r) - count(l-1); + } +}; diff --git a/Recursion/3490.Count-Beautiful-Numbers/Readme.md b/Recursion/3490.Count-Beautiful-Numbers/Readme.md new file mode 100644 index 000000000..542d37d94 --- /dev/null +++ b/Recursion/3490.Count-Beautiful-Numbers/Readme.md @@ -0,0 +1,20 @@ +### 3490.Count-Beautiful-Numbers + +常见的数位DP或者递归搜索。本质我们需要设计一个函数count(T)来记录0-T之间所有符合条件的数。 + +因为beautiful number最多只有10位,每个位置最多只有0-9共十种填法,我们可以逐位搜索。搜索过程中,第pos个位置上的可选决策受到两个先前状态的制约: +1. 该位置是否贴近上限T。如果pos之前的选择都是贴着上限T,那么在第pos位上,我们的选择上限也只能是T[pos],否则上限可以是9. +2. 该位置是否是先导零。如果pos之前的选择全部都是0,那么在pos位置之前记录的乘积应该强制认作是1。这么做是为了处理这样一种情况:pos之前都是0,并且pos位也想取零。如果没有这条规则,那么递归到后面的乘积就永远是零了。 + +由此,我们一旦做出了pos位置上的决策,在往后递归的时候,也需要相应更新isTight和isLeadingZero这两个状态。 + +递归需要记忆化的支持。本题记忆化的状态就是递归函数的参数:pos, sum, product, isTight, isLeadingZero。我们可以用tuple作为key,加上有序map来存储访问过的状态。 + +有人会问product的个数会不会很大?事实上9个digit想乘,可以得到的不同的乘积并不大。 +``` +st = {1} # 空集的乘积(乘法单位元) +for _ in range(9): # 9 个数相乘 + st = set(x * d for x in st for d in range(10)) # 每个数从 0 到 9 +print(len(st)) # 3026 +``` +总的记忆化状态数目最多`9*81*3000*2*2=8748000`,恰好可以接受。 diff --git a/Recursion/3614.Process-String-with-Special-Operations-II/3614.Process-String-with-Special-Operations-II.cpp b/Recursion/3614.Process-String-with-Special-Operations-II/3614.Process-String-with-Special-Operations-II.cpp new file mode 100644 index 000000000..c3ff18731 --- /dev/null +++ b/Recursion/3614.Process-String-with-Special-Operations-II/3614.Process-String-with-Special-Operations-II.cpp @@ -0,0 +1,44 @@ +using ll = long long; +class Solution { +public: + char processStr(string s, long long k) { + k++; + int n = s.size(); + s = "#"+s; + + vectorlen(n+1); + for (int i=1; i<=n; i++) { + char c = s[i]; + if ('a'<=c && c<='z') { + len[i] = len[i-1]+1; + } else if (c=='*') { + len[i] = len[i-1]==0? 0: len[i-1]-1; + } else if (c=='#') { + len[i] = len[i-1] * 2; + } else if (c=='%') { + len[i] = len[i-1]; + } + } + if (k>len[n] || k==0) return '.'; + + for (int t=n; t>=1; t--) { + char c = s[t]; + ll before = len[t-1]; + ll after = len[t]; + + if ('a'<=c && c<='z') { + if (k==after) + return c; + } else if (c=='*') { + k = k; + } else if (c=='#') { + if (k> before) + k = k-before; + } else if (c=='%') { + k = before+1-k; + } + } + + return '.'; + } +}; diff --git a/Recursion/3614.Process-String-with-Special-Operations-II/Readme.md b/Recursion/3614.Process-String-with-Special-Operations-II/Readme.md new file mode 100644 index 000000000..37f9e8776 --- /dev/null +++ b/Recursion/3614.Process-String-with-Special-Operations-II/Readme.md @@ -0,0 +1,13 @@ +### 3614.Process-String-with-Special-Operations-II + +很显然,构造完全之后的字符串非常长,我们不可能在此基础上定位第k个元素。此题极大概率是递归反推。 + +我们分类思考每种操作下的第k个字符(注意是1-index)是什么情况: +1. 添加一个字符,使得长度从a变成了a+1. 如果k=a+1,那么答案就是最新添加的字符。否则,递归求原字符串里的第k个。 +2. 删减最后一个字符,使得长度从a变成了a-1. 这种情况下k不可能是a(否则无解),因此等效于递归求原字符串里的第k个。 +3. 将字符串copy+append,使得长度从a变成了2a. 显然如果k<=a,递归求原字符串里的第k个。如果k>a,递归求原字符串里的第k-a个。 +4. 将字符串反转,长度依然是k。显然,递归求原字符串里的第a+1-k个。 + +综上,只要知道每个回合的操作和长度变化,我们就可以把“求当前字符串的第k个元素”,逆推为“求前一个回合字符串的第k'个元素”,其中k'的计算如上。 + +注意,这个题可能无解。逆推的前提是正向的变化合法存在。所以我们必须先正向走一遍,记录下每个回合的字符串长度,最后检查k是否在最终长度的范围内。 diff --git a/Recursion/3704.Count-No-Zero-Pairs-That-Sum-to-N/3704.Count-No-Zero-Pairs-That-Sum-to-N.cpp b/Recursion/3704.Count-No-Zero-Pairs-That-Sum-to-N/3704.Count-No-Zero-Pairs-That-Sum-to-N.cpp new file mode 100644 index 000000000..98d8fccbe --- /dev/null +++ b/Recursion/3704.Count-No-Zero-Pairs-That-Sum-to-N/3704.Count-No-Zero-Pairs-That-Sum-to-N.cpp @@ -0,0 +1,62 @@ +using LL = long long; +class Solution { + LL memo[18][2][2][2]; + int L; + vectordigits; +public: + LL dfs(int pos, int carry, int endA, int endB) { + if (pos==L) { + if (carry==0 && endA==1 && endB==1) + return 1; + else + return 0; + } + + if (memo[pos][carry][endA][endB]!=-1) + return memo[pos][carry][endA][endB]; + + LL ret = 0; + int target = digits[pos]; + for (int i=0; i<=9; i++) { + if(endA==1 && i!=0) continue; + if(endA==0 && i==0) continue; + vectorA; + if (endA) A = {1}; + else A = {0,1}; + + for (int j=0; j<=9; j++) { + if (endB==1 && j!=0) continue; + if (endB==0 && j==0) continue; + int sum = i+j+carry; + if (sum%10!=target) continue; + int ncarry = (sum>=10)?1:0; + + vectorB; + if (endB) B = {1}; + else B = {0,1}; + + for (int nxt_enda: A) + for (int nxt_endb: B) + ret += dfs(pos+1, ncarry, nxt_enda, nxt_endb); + } + } + + memo[pos][carry][endA][endB] = ret; + return ret; + + }; + + long long countNoZeroPairs(long long n) { + + fill(&memo[0][0][0][0], &memo[0][0][0][0]+18*2*2*2, -1); + + LL temp = n; + while (temp>0) { + digits.push_back(temp%10); + temp/=10; + } + L = digits.size(); + + return dfs(0,0,0,0); + } +}; diff --git a/Recursion/3704.Count-No-Zero-Pairs-That-Sum-to-N/Readme.md b/Recursion/3704.Count-No-Zero-Pairs-That-Sum-to-N/Readme.md new file mode 100644 index 000000000..0ea02e942 --- /dev/null +++ b/Recursion/3704.Count-No-Zero-Pairs-That-Sum-to-N/Readme.md @@ -0,0 +1,9 @@ +### 3704.Count-No-Zero-Pairs-That-Sum-to-N + +比较常规的数位DP或者递归。 + +我们从低位往高位在每个位置尝试符合条件的一对digit组合(最多10*10种)。对于第k位而言,一对digit组合的符合条件有:1. 加起来(再加上上一位的进位)之和等于target[k],2. digit不能为零,除非该数已经标记为构造结束。 + +所以状态需要四个参量memo[pos][carry][endA][endB],pos表示当前处理第k位,carry表示之前一位带来的进位,endA表示A已经结束构造,endB表示B已经结束构造。 + +总的状态有`15*2*2*2=120`,加上每种状态有100种fanout组合,所以总的时间复杂度可以结束。 diff --git a/Recursion/3864.Minimum-Cost-to-Partition-a-Binary-String/3864.Minimum-Cost-to-Partition-a-Binary-String.cpp b/Recursion/3864.Minimum-Cost-to-Partition-a-Binary-String/3864.Minimum-Cost-to-Partition-a-Binary-String.cpp new file mode 100644 index 000000000..27ce6bf0e --- /dev/null +++ b/Recursion/3864.Minimum-Cost-to-Partition-a-Binary-String/3864.Minimum-Cost-to-Partition-a-Binary-String.cpp @@ -0,0 +1,27 @@ +using ll = long long; +class Solution { +public: + long long minCost(string s, int encCost, int flatCost) { + int n = s.size(); + vectorpref(n+1,0); + for (int i=0; i ll { + int L = r-l+1; + int X=pref[r+1]-pref[l]; + if (X==0) return (long long)flatCost; + else return 1LL*L*X*encCost; + }; + + std::function dfs = [&](int l, int r) -> ll { + int len = r-l+1; + if (len<=0) return 0; + if (len%2==1) return cost(l,r); + + return min(cost(l,r), dfs(l, l+len/2-1) + dfs(l+len/2, r)); + }; + + return dfs(0, n-1); + } +}; diff --git a/Recursion/3864.Minimum-Cost-to-Partition-a-Binary-String/Readme.md b/Recursion/3864.Minimum-Cost-to-Partition-a-Binary-String/Readme.md new file mode 100644 index 000000000..ed176f948 --- /dev/null +++ b/Recursion/3864.Minimum-Cost-to-Partition-a-Binary-String/Readme.md @@ -0,0 +1,13 @@ +### 3864.Minimum-Cost-to-Partition-a-Binary-String + +根据题意的规则,非常容易想到递归。当长度为奇数时,无法分割,只能直接算direct cost。当长度为偶数时,只有两种策略:不分割直接算direct cost,或者对半分割递归处理。 + +对于这样的递归函数,参数必然有两个,就是左右端点的位置。但是本题的数据量n=1e5,理论上可能需要访问的状态有n^2种。即使记忆化,空间上也是不可能的。那么怎么办呢? + +其实关键点在于本题的规则有着特殊性,使得需要遍历的状态并不多。每个状态看似有两个分支,但是其中一个分支(也就是不split)并不需要继续往下递归。所以极端情况下,假设长度为n的数组且n是二次幂,那么我们需要访问: +* 第一层:[0,n-1] +* 第二层:[0,n/2),[n/2,n-1], +* 第三层[0,n/4),[n/4,n/2),[n/2,3/4n),[3/4n,n)... +* 我们最多递归到最底层每个区间只有一个元素。 + +我们发现每层的区间并不重叠,且不同分支的探索也不会有任何重复的机会,所以最多访问的区间也就是`2*n`个。因此本题完全不需要借助记忆化,暴力递归即可。 diff --git a/Recursion/440.K-th-Smallest-in-Lexicographical-Order/440.K-th-Smallest-in-Lexicographical-Order.cpp b/Recursion/440.K-th-Smallest-in-Lexicographical-Order/440.K-th-Smallest-in-Lexicographical-Order.cpp deleted file mode 100644 index c0d853bbf..000000000 --- a/Recursion/440.K-th-Smallest-in-Lexicographical-Order/440.K-th-Smallest-in-Lexicographical-Order.cpp +++ /dev/null @@ -1,49 +0,0 @@ -class Solution { - -public: - int findKthNumber(int n, int k) - { - return FindKthNumberBeginWith(0,n,k); - } - - // return the Lexicographically Kth element that begin with the prefix - // excluding the prefix itself - int FindKthNumberBeginWith(int prefix, int n, int k) - { - if (k==0) return prefix; - - for (int i=(prefix==0?1:0); i<=9; i++) - { - int count = TotalNumbersBeginWith(prefix*10+i,n); - if (countk的话,我们就确定了首元素必须是1,进而考虑第二个数字,也是从1的可能性考虑起--我们发现,这就是在递归重复之前的步骤. - -代码的流程大致如下: -```cpp -int FindKthNumberBeginWith(prefix,k) -{ - if (k==0) return prefix; - - for i=0 to 9 - { - count = TotalNumbersBeginWith(prefix+[i]); - if (countbitArr; // Note: all arrays are 1-index + vectornums; long long M = 1e9+7; - // increase nums[i] by delta (1-index) + void init(int N) + { + this->N = N; + bitArr.resize(N+1); + nums.resize(N+1); + } + + // increase nums[i] by delta void updateDelta(int i, long long delta) { int idx = i; - while (idx <= MAX_N) + while (idx <= N) { bitArr[idx]+=delta; bitArr[idx] %= M; @@ -18,7 +23,7 @@ class Solution { } } - // sum of a range nums[1:j] inclusively, 1-index + // sum of a range nums[1:j] inclusively long long queryPreSum(int idx){ long long result = 0; while (idx){ @@ -32,23 +37,29 @@ class Solution { // sum of a range nums[i:j] inclusively long long sumRange(int i, int j) { return queryPreSum(j)-queryPreSum(i-1); - } - + } +}; + +using LL = long long; +LL OFFSET = 1e5+10; +LL M = 1e9+7; +class Solution { public: int subarraysWithMoreZerosThanOnes(vector& nums) { - cout<info + right->info; // check with your own logic + } + } + + SegTreeNode(int a, int b, vector& val) // init for range [a,b] with the same-size array val + { + lazy_tag = 0; + lazy_val = 0; + start = a, end = b; + if (a==b) + { + info = val[a]; + return; + } + int mid = (a+b)/2; + if (left==NULL) + { + left = new SegTreeNode(a, mid, val); + right = new SegTreeNode(mid+1, b, val); + info = left->info + right->info; // check with your own logic + } + } + + void pushDown() + { + if (lazy_tag==1 && left) + { + if (lazy_val % 2 == 1) + { + left->info = (left->end - left->start + 1) - left->info; + right->info = (right->end - right->start + 1) - right->info; + left->lazy_tag = 1; left->lazy_val += lazy_val; + right->lazy_tag = 1; right->lazy_val += lazy_val; + } + + lazy_tag = 0; lazy_val = 0; + } + } + + void updateRange(int a, int b) // set range [a,b] with flips + { + if (b < start || a > end ) // not covered by [a,b] at all + return; + if (a <= start && end <=b) // completely covered within [a,b] + { + info = (end-start+1) - info; + lazy_tag = 1; + lazy_val += 1; + return; + } + + if (left) + { + pushDown(); + left->updateRange(a, b); + right->updateRange(a, b); + info = left->info + right->info; // write your own logic + } + } + + LL queryRange(int a, int b) // query the sum over range [a,b] + { + if (b < start || a > end ) + { + return 0; // check with your own logic + } + if (a <= start && end <=b) + { + return info; // check with your own logic + } + + if (left) + { + pushDown(); + LL ret = left->queryRange(a, b) + right->queryRange(a, b); + info = left->info + right->info; // check with your own logic + return ret; + } + + return info; // should not reach here + } +}; + +class Solution { +public: + vector handleQuery(vector& nums1, vector& nums2, vector>& queries) + { + int n = nums1.size(); + SegTreeNode* root = new SegTreeNode(0, n-1, nums1); + LL sum = accumulate(nums2.begin(), nums2.end(), 0LL); + vectorrets; + for (auto & query: queries) + { + if (query[0]==1) + root->updateRange(query[1], query[2]); + else if (query[0]==2) + sum += root->queryRange(0, n-1) * query[1]; + else + rets.push_back(sum); + } + + return rets; + } +}; diff --git a/Segment_Tree/2569.Handling-Sum-Queries-After-Update/Readme.md b/Segment_Tree/2569.Handling-Sum-Queries-After-Update/Readme.md new file mode 100644 index 000000000..9d5c69bb6 --- /dev/null +++ b/Segment_Tree/2569.Handling-Sum-Queries-After-Update/Readme.md @@ -0,0 +1,9 @@ +### 2569.Handling-Sum-Queries-After-Update + +本题的本质就是需要高效的区间更新函数来维护nums1,来实现第一类query。对于第二类query,只需要操作`sum += total(nums1)*p`即可,其中total就是对nums1全部元素取和。对于第三类query,就是输出当前的sum。 + +显然我们会用线段树来实现这样的数据结构。 + +在现有的模板中,我们肯定会选择带有“区间求和”功能的模板,即`queryRange(a,b)`可以求得nums1里指定区间[a,b]元素的和。但是“区间更新”的功能需要重写:原本的功能是将区间的数值替换为val,这里的区间更新是将里面的元素全部做0/1翻转,这对区间和会产生什么影响呢?假设原本一段区间[a,b]内记录的元素和是info,那么`updateRange(a,b)`造成的影响其实就是`info = (b-a+1)-info`,改动非常简单。 + +另外,我们还需要对懒标记进行重新的定义。在模板里,`lazy_tag=1`表示该区间的子区间有待更新(即01翻转)。那么`lazy_val`我们需要重新定义为“它的子区间我们还需要翻转多少次”。当我们需要`push_down`的时候,就需要对左右子区间分别进行`lazy_val`次的翻转。 diff --git a/Segment_Tree/2659.Make-Array-Empty/2659.Make-Array-Empty.cpp b/Segment_Tree/2659.Make-Array-Empty/2659.Make-Array-Empty.cpp new file mode 100644 index 000000000..8fea5b15d --- /dev/null +++ b/Segment_Tree/2659.Make-Array-Empty/2659.Make-Array-Empty.cpp @@ -0,0 +1,91 @@ +class BIT{ + public: + int N; + vectorbitArr; // Note: all arrays are 1-index + vectornums; + long long M = 1e9+7; + + void init(int N) + { + this->N = N; + bitArr.resize(N+1); + nums.resize(N+1); + } + + // increase nums[i] by delta + void updateDelta(int i, long long delta) { + int idx = i; + while (idx <= N) + { + bitArr[idx]+=delta; + // bitArr[idx] %= M; + idx+=idx&(-idx); + } + } + + // sum of a range nums[1:j] inclusively + long long queryPreSum(int idx){ + long long result = 0; + while (idx){ + result += bitArr[idx]; + // result %= M; + idx-=idx&(-idx); + } + return result; + } + + // sum of a range nums[i:j] inclusively + long long sumRange(int i, int j) + { + if (i>j) return 0; + return queryPreSum(j)-queryPreSum(i-1); + } +}; + + +class Solution { +public: + long long countOperationsToEmptyArray(vector& nums) + { + int n = nums.size(); + nums.insert(nums.begin(), 0); + BIT bit; + bit.init(n+10); + + for (int i=1; i<=n; i++) + { + bit.updateDelta(i, 1); + } + + mapMap; + for (int i=1; i<=n; i++) + Map[nums[i]] = i; + + long long ret = 0; + int last_p = -1; + for (auto& [v, p]: Map) + { + if (last_p==-1) + { + ret += bit.sumRange(1, p-1); + last_p = p; + bit.updateDelta(p, -1); + continue; + } + + if (last_p <= p) + { + ret += bit.sumRange(last_p, p-1); + } + else + { + ret += bit.sumRange(1, p-1); + ret += bit.sumRange(last_p+1, n); + } + last_p = p; + bit.updateDelta(p, -1); + } + + return ret + n; + } +}; diff --git a/Segment_Tree/2659.Make-Array-Empty/Readme.md b/Segment_Tree/2659.Make-Array-Empty/Readme.md new file mode 100644 index 000000000..deab212a3 --- /dev/null +++ b/Segment_Tree/2659.Make-Array-Empty/Readme.md @@ -0,0 +1,11 @@ +2659.Make-Array-Empty + +假设一个序列里面前三个最小的元素是x,y,z,他们在序列中的位置如下:`***x****z****y*****` + +首先我们必然会x,它是第一个会被消除的元素,那么在x之前的元素我们都会挪动到最后。所以操作的次数是x之前的元素的个数。 + +其次我们需要消除y,那么所有在x与y之间的元素都会被挪动到最后。所需要的操作次数也就是x与y之间的元素的个数。 + +接着我们需要消除z。注意在上一步之后,所有在y之前的元素都已经被挪到最后去了。想要消除z,必须先挪动从y+1到z-1之间的元素,其实是一个wrap around的过程。从原始序列上看,因为z的位置在y的前面,那么我们需要挪动的元素其实包含了[y+1,n-1]以及[0:z-1]两部分。特别注意,我们要扣除掉x,因为它已经被消除了。 + +所以这就提示我们可以用线段树或者BIT,支持任意单个元素的删减操作,并可以高效求出任意一段区间内的剩余元素个数。 diff --git a/Segment_Tree/2916.Subarrays-Distinct-Element-Sum-of-Squares-II/2916.Subarrays-Distinct-Element-Sum-of-Squares-II.cpp b/Segment_Tree/2916.Subarrays-Distinct-Element-Sum-of-Squares-II/2916.Subarrays-Distinct-Element-Sum-of-Squares-II.cpp new file mode 100644 index 000000000..fe25d0520 --- /dev/null +++ b/Segment_Tree/2916.Subarrays-Distinct-Element-Sum-of-Squares-II/2916.Subarrays-Distinct-Element-Sum-of-Squares-II.cpp @@ -0,0 +1,145 @@ +using LL = long long; +LL M = 1e9+7; +class SegTreeNode +{ + public: + SegTreeNode* left = NULL; + SegTreeNode* right = NULL; + int start, end; + LL info; // the sum value over the range + LL delta; + bool tag; + + SegTreeNode(int a, int b, int val) // init for range [a,b] with val + { + tag = 0; + delta = 0; + start = a, end = b; + if (a==b) + { + info = val; + return; + } + int mid = (a+b)/2; + if (left==NULL) + { + left = new SegTreeNode(a, mid, val); + right = new SegTreeNode(mid+1, b, val); + info = left->info + right->info; // check with your own logic + } + } + + SegTreeNode(int a, int b, vector& val) // init for range [a,b] with the same-size array val + { + tag = 0; + delta = 0; + start = a, end = b; + if (a==b) + { + info = val[a]; + return; + } + int mid = (a+b)/2; + if (left==NULL) + { + left = new SegTreeNode(a, mid, val); + right = new SegTreeNode(mid+1, b, val); + info = left->info + right->info; // check with your own logic + } + } + + void pushDown() + { + if (tag==1 && left) + { + left->info += delta * (left->end - left->start + 1); + left->delta += delta; + right->info += delta * (right->end - right->start + 1); + right->delta += delta; + left->tag = 1; + right->tag = 1; + tag = 0; + delta = 0; + } + } + + void updateRangeBy(int a, int b, int val) // increase range [a,b] by val + { + if (b < start || a > end ) // not covered by [a,b] at all + return; + if (a <= start && end <=b) // completely covered within [a,b] + { + info += val * (end-start+1); + delta += val; + tag = 1; + return; + } + + if (left) + { + pushDown(); + left->updateRangeBy(a, b, val+delta); + right->updateRangeBy(a, b, val+delta); + delta = 0; + tag = 0; + info = left->info + right->info; // write your own logic + } + } + + LL queryRange(int a, int b) // query the sum within range [a,b] + { + if (b < start || a > end ) + { + return 0; // check with your own logic + } + if (a <= start && end <=b) + { + return info; // check with your own logic + } + + if (left) + { + pushDown(); + LL ret = left->queryRange(a, b) + right->queryRange(a, b); + info = left->info + right->info; // check with your own logic + return ret; + } + + return info; // should not reach here + } +}; + +class Solution { +public: + int sumCounts(vector& nums) + { + unordered_mapMap; + int n = nums.size(); + vectorprev(n, -1); + for (int i=0; idp(n); + for (int i=0; iqueryRange(j+1, i-1) + i-1-j; + dp[i] += 1; + dp[i] %= M; + root->updateRangeBy(j+1, i, 1); + } + + LL ret = 0; + for (int i=0; ii时,区间实际不存在,故标记0. + +我们再定义`count[a:b]`表示某个区间内的distinct number的数目,`square[a:b]`表示该区间内distinct number的数目的平方。 + +对于位置i,我们在数组里找到相同nums[i]出现的前一个位置k。于是 +1. 对于`j=0,1,...,k`而言,`square[j:i] = square[j:i-1]`. +2. 对于`j=k+1,k+2,...,i-1`而言,`count[j:i] = count[j:i-1]+1,两边平方一下就得到`square[j:i] = square[j:i-1] + 2*count[j:i-1] + 1`. +将两部分相加得到 +``` +sum{square[j:i]} = sum{square[j:i-1]} (for j=0,1,2,...i-1) + 2 * sum{count[j:i-1]} + (i-1-k) (for j=k+1,...i-1) +``` +可见`sum{square[j:i]}`与`sum{square[j:i-1]}`之间存在递推关系。其中相差的部分`sum{count[j:i-1]}`就是之前定义的线段树中在t=i-1时刻,叶子节点区间[k+1, i-1]的元素之和。 + +当我们求出`sum{square[j:i]}`之后,该如何更新这棵线段树呢?显然,只有以k+1,k+2,...i-1开头的这些区间,随着i的加入,distinct number会增1. 所以我们只需要将叶子节点区间[k+1, i-1]的元素统一都增加1即可。 + +最终的答案就是将每个位置i的`sum{square[j:i]}`再加起来。 + diff --git a/Segment_Tree/307.Range-Sum-Query-Mutable/307.Range-Sum-Query-Mutable_BIT.cpp b/Segment_Tree/307.Range-Sum-Query-Mutable/307.Range-Sum-Query-Mutable_BIT.cpp index e738ea721..db7a5ddb3 100644 --- a/Segment_Tree/307.Range-Sum-Query-Mutable/307.Range-Sum-Query-Mutable_BIT.cpp +++ b/Segment_Tree/307.Range-Sum-Query-Mutable/307.Range-Sum-Query-Mutable_BIT.cpp @@ -1,41 +1,76 @@ -class NumArray { -public: - vectorbitArr; - vectornums; +class BIT{ + public: + int N; + vectorbitArr; // Note: all arrays are 1-index + vectornums; + long long M = 1e9+7; - NumArray(vector& nums) { - this->nums = nums; - bitArr.resize(nums.size()+1); - for (int i=0; iN = N; + bitArr.resize(N+1); + nums.resize(N+1); } - void update(int i, int val){ - my_update(i, val-nums[i]); - nums[i] = val; - } - - void my_update(int i, int delta) { - int idx = i+1; - while (idxnums; +public: + NumArray(vector& nums) + { + this->nums = nums; + int n = nums.size(); + bit.init(n+10); + + for (int i=0; iupdate(index,val); + * int param_2 = obj->sumRange(left,right); + */ diff --git a/Segment_Tree/3072.Distribute-Elements-Into-Two-Arrays-II/3072.Distribute-Elements-Into-Two-Arrays-II.cpp b/Segment_Tree/3072.Distribute-Elements-Into-Two-Arrays-II/3072.Distribute-Elements-Into-Two-Arrays-II.cpp new file mode 100644 index 000000000..645189786 --- /dev/null +++ b/Segment_Tree/3072.Distribute-Elements-Into-Two-Arrays-II/3072.Distribute-Elements-Into-Two-Arrays-II.cpp @@ -0,0 +1,171 @@ +using LL = long long; +LL M = 1e9+7; +class SegTreeNode +{ + public: + SegTreeNode* left = NULL; + SegTreeNode* right = NULL; + int start, end; + LL info; // the sum value over the range + LL delta; + bool tag; + + SegTreeNode(int a, int b, int val) // init for range [a,b] with val + { + tag = 0; + delta = 0; + start = a, end = b; + if (a==b) + { + info = val; + return; + } + int mid = (a+b)/2; + if (left==NULL) + { + left = new SegTreeNode(a, mid, val); + right = new SegTreeNode(mid+1, b, val); + info = left->info + right->info; // check with your own logic + } + } + + SegTreeNode(int a, int b, vector& val) // init for range [a,b] with the same-size array val + { + tag = 0; + delta = 0; + start = a, end = b; + if (a==b) + { + info = val[a]; + return; + } + int mid = (a+b)/2; + if (left==NULL) + { + left = new SegTreeNode(a, mid, val); + right = new SegTreeNode(mid+1, b, val); + info = left->info + right->info; // check with your own logic + } + } + + void pushDown() + { + if (tag==1 && left) + { + left->info += delta * (left->end - left->start + 1); + left->delta += delta; + right->info += delta * (right->end - right->start + 1); + right->delta += delta; + left->tag = 1; + right->tag = 1; + tag = 0; + delta = 0; + } + } + + void updateRangeBy(int a, int b, int val) // increase range [a,b] by val + { + if (b < start || a > end ) // not covered by [a,b] at all + return; + if (a <= start && end <=b) // completely covered within [a,b] + { + info += val * (end-start+1); + delta += val; + tag = 1; + return; + } + + if (left) + { + pushDown(); + left->updateRangeBy(a, b, val+delta); + right->updateRangeBy(a, b, val+delta); + delta = 0; + tag = 0; + info = left->info + right->info; // write your own logic + } + } + + LL queryRange(int a, int b) // query the sum within range [a,b] + { + if (b < start || a > end ) + { + return 0; // check with your own logic + } + if (a <= start && end <=b) + { + return info; // check with your own logic + } + + if (left) + { + pushDown(); + LL ret = left->queryRange(a, b) + right->queryRange(a, b); + info = left->info + right->info; // check with your own logic + return ret; + } + + return info; // should not reach here + } +}; + +class Solution { +public: + vector resultArray(vector& nums) + { + setSet(nums.begin(), nums.end()); + int idx = 0; + unordered_mapMap; + for (int x: Set) + { + Map[x] = idx; + idx++; + } + int n = Set.size(); + + SegTreeNode* root1 = new SegTreeNode(0, n-1, 0); + int k = Map[nums[0]]; + root1->updateRangeBy(k, k , 1); + + SegTreeNode* root2 = new SegTreeNode(0, n-1, 0); + k = Map[nums[1]]; + root2->updateRangeBy(k, k , 1); + + vectorarr1({nums[0]}); + vectorarr2({nums[1]}); + for (int i=2; iqueryRange(k+1, n-1); + int y = root2->queryRange(k+1, n-1); + if (x>y) + { + arr1.push_back(nums[i]); + root1->updateRangeBy(k, k, 1); + } + else if (xupdateRangeBy(k, k, 1); + } + else + { + if (arr1.size() <= arr2.size()) + { + arr1.push_back(nums[i]); + root1->updateRangeBy(k, k, 1); + } + else + { + arr2.push_back(nums[i]); + root2->updateRangeBy(k, k, 1); + } + } + } + + vectorrets; + for (int x: arr1) rets.push_back(x); + for (int x: arr2) rets.push_back(x); + return rets; + } +}; diff --git a/Segment_Tree/3072.Distribute-Elements-Into-Two-Arrays-II/3072.Distribute-Elements-Into-Two-Arrays-II.py b/Segment_Tree/3072.Distribute-Elements-Into-Two-Arrays-II/3072.Distribute-Elements-Into-Two-Arrays-II.py new file mode 100644 index 000000000..b41d13c5d --- /dev/null +++ b/Segment_Tree/3072.Distribute-Elements-Into-Two-Arrays-II/3072.Distribute-Elements-Into-Two-Arrays-II.py @@ -0,0 +1,21 @@ +from sortedcontainers import SortedList +class Solution: + def resultArray(self, nums: List[int]) -> List[int]: + s1 = SortedList([nums[0]]) + s2 = SortedList([nums[1]]) + arr1 = [nums[0]] + arr2 = [nums[1]] + + for i in range(2, len(nums)): + x = len(s1)-s1.bisect_right(nums[i]) + y = len(s2)-s2.bisect_right(nums[i]) + if (x>y) or (x==y and len(arr1)<=len(arr2)): + arr1.append(nums[i]) + s1.add(nums[i]) + else: + arr2.append(nums[i]) + s2.add(nums[i]) + + return arr1+arr2 + + diff --git a/Segment_Tree/3072.Distribute-Elements-Into-Two-Arrays-II/Readme.md b/Segment_Tree/3072.Distribute-Elements-Into-Two-Arrays-II/Readme.md new file mode 100644 index 000000000..ced5d5bb5 --- /dev/null +++ b/Segment_Tree/3072.Distribute-Elements-Into-Two-Arrays-II/Readme.md @@ -0,0 +1,5 @@ +### 3072.Distribute-Elements-Into-Two-Arrays-II + +此题如果用python的SortedList来做的话,秒杀。 + +用C++的话,得用线段树或者树状数组。将nums里面的元素按照从小到大离散化,按照数值从小到大映射成编号(即第x号元素)。建立两棵线段树,叶子节点的容量与编号的上限相同(记做M),叶子节点的初始数值都是零。然后就模拟题意,对于nums[i],我们得到它对应的编号k,那么我们就分别查询两棵线段树里[k+1,M-1]范围内叶子节点之和,即为`greaterCount(arr, nums[i])`. 然后对应需要插入的那棵线段树,将第i个叶子节点增1. 不断模拟,由此得到最终的分配方案。 diff --git a/Segment_Tree/3161.Block-Placement-Queries/3161.Block-Placement-Queries.cpp b/Segment_Tree/3161.Block-Placement-Queries/3161.Block-Placement-Queries.cpp new file mode 100644 index 000000000..888d5c421 --- /dev/null +++ b/Segment_Tree/3161.Block-Placement-Queries/3161.Block-Placement-Queries.cpp @@ -0,0 +1,149 @@ +class SegTreeNode +{ + public: + SegTreeNode* left = NULL; + SegTreeNode* right = NULL; + int start, end; + int info; // the maximum value of the range + bool tag; + + SegTreeNode(int a, int b, int val) // init for range [a,b] with val + { + tag = 0; + start = a, end = b; + if (a==b) + { + info = val; + return; + } + int mid = (a+b)/2; + if (left==NULL) + { + left = new SegTreeNode(a, mid, val); + right = new SegTreeNode(mid+1, b, val); + info = max(left->info, right->info); // check with your own logic + } + } + + SegTreeNode(int a, int b, vector& val) // init for range [a,b] with the same-size array val + { + tag = 0; + info = 0; + start = a, end = b; + if (a==b) + { + info = val[a]; + return; + } + int mid = (a+b)/2; + if (left==NULL) + { + left = new SegTreeNode(a, mid, val); + right = new SegTreeNode(mid+1, b, val); + info = max(left->info, right->info); // check with your own logic + } + } + + void pushDown() + { + if (tag==1 && left) + { + left->info = info; + right->info = info; + left->tag = 1; + right->tag = 1; + tag = 0; + } + } + + void updateRange(int a, int b, int val) // set range [a,b] with val + { + if (b < start || a > end ) // not covered by [a,b] at all + return; + if (a <= start && end <=b) // completely covered within [a,b] + { + info = val; + tag = 1; + return; + } + + if (left) + { + pushDown(); + left->updateRange(a, b, val); + right->updateRange(a, b, val); + info = max(left->info, right->info); // write your own logic + } + } + + int queryRange(int a, int b) // query the maximum value within range [a,b] + { + if (b < start || a > end ) + { + return INT_MIN/2; // check with your own logic + } + if (a <= start && end <=b) + { + return info; // check with your own logic + } + + if (left) + { + pushDown(); + int ret = max(left->queryRange(a, b), right->queryRange(a, b)); + info = max(left->info, right->info); // check with your own logic + return ret; + } + + return info; // should not reach here + } + +}; + + +class Solution { +public: + vector getResults(vector>& queries) + { + int n = min(50000, (int)queries.size()*3) + 5; + SegTreeNode* root = new SegTreeNode(0, n, 0); + + setSet; + Set.insert(0); + + vectorrets; + + for (auto q:queries) + { + if (q[0]==1) + { + int x = q[1]; + Set.insert(x); + auto iter = Set.find(x); + int a = *prev(iter); + root->updateRange(x,x,x-a); + + if (next(iter)!=Set.end()) + { + int b = *next(iter); + root->updateRange(b,b,b-x); + } + } + else + { + int x = q[1], sz = q[2]; + int len = root->queryRange(0, x); + + if (Set.find(x)==Set.end()) + { + auto iter = Set.lower_bound(x); + int a = *prev(iter); + len = max(len, x-a); + } + rets.push_back(len >= sz); + } + } + + return rets; + } +}; diff --git a/Segment_Tree/3161.Block-Placement-Queries/Readme.md b/Segment_Tree/3161.Block-Placement-Queries/Readme.md new file mode 100644 index 000000000..3f9e3eb41 --- /dev/null +++ b/Segment_Tree/3161.Block-Placement-Queries/Readme.md @@ -0,0 +1,9 @@ +### 3161.Block-Placement-Queries + +本题的第一类query会在数轴上不断插入block,通常情况下每插入一个block,就会隔出两个区间。当遇到第二类query时,我们只需要在[0,x]范围内查看这些区间,找出最大的区间长度,再与sz比较即可。此时我们只需要把每个区间的长度,作为该区间右端点的一个属性,就可以发现就是在[0,x]里求最大值。所以我们容易想到,只需要构造一棵线段树,其中queryRange是求任意一段区间内的最大值。初始状态每个点的值是0. + +更具体的,对于第一类操作,我们在x处插入一个block时,找到它之前已经存在的block位置记做a,之后已经存在的block位置记做b,那么我们只需要对线段树进行单点更新:在x处更新属性x-a,在b处更新数值b-x(前提是b存在)。 + +对于第二类操作,我们只需要在线段树的[0,x]范围里找最大值。特别注意,如果x处本身并没有block,我们还需要考察x之前的那个block(记做a)到x这段空间长度。 + +最后,对于一个x,如果找到它之前和之后的block呢?只需要维护一个有序容器set即可,不断将x插入其中,并且用lower_bound和upper_bound找到其在Set里的前后元素。 diff --git a/Segment_Tree/3165.Maximum-Sum-of-Subsequence-With-Non-adjacent-Elements/3165.Maximum-Sum-of-Subsequence-With-Non-adjacent-Elements.cpp b/Segment_Tree/3165.Maximum-Sum-of-Subsequence-With-Non-adjacent-Elements/3165.Maximum-Sum-of-Subsequence-With-Non-adjacent-Elements.cpp new file mode 100644 index 000000000..d0d504a54 --- /dev/null +++ b/Segment_Tree/3165.Maximum-Sum-of-Subsequence-With-Non-adjacent-Elements/3165.Maximum-Sum-of-Subsequence-With-Non-adjacent-Elements.cpp @@ -0,0 +1,66 @@ +using LL = long long; +LL M = 1e9+7; +class SegTreeNode +{ + public: + SegTreeNode* left = NULL; + SegTreeNode* right = NULL; + int start, end; + LL info00, info11, info10, info01; + + SegTreeNode(int a, int b, vector& vals) // init for range [a,b] with val + { + start = a, end = b; + if (a==b) + { + info11 = vals[start], info01 = -1e18, info10 = -1e18, info00 = 0; + return; + } + int mid = (a+b)/2; + + left = new SegTreeNode(a, mid, vals); + right = new SegTreeNode(mid+1, b, vals); + info11 = max({left->info10 + right->info01, left->info11 + right->info01, left->info10 + right->info11}); + info00 = max({left->info00 + right->info00, left->info01 + right->info00, left->info00 + right->info10}); + info10 = max({left->info10 + right->info00, left->info10 + right->info10, left->info11 + right->info00}); + info01 = max({left->info00 + right->info01, left->info01 + right->info01, left->info00 + right->info11}); + } + + void updateRange(int a, int val) // set range [a,b] with val + { + if (a < start || a > end ) // not covered by [a,b] at all + return; + if (start==end) // completely covered within [a,b] + { + info00 = 0; + info11 = val; + return; + } + + left->updateRange(a, val); + right->updateRange(a, val); + info11 = max({left->info10 + right->info01, left->info11 + right->info01, left->info10 + right->info11}); + info00 = max({left->info00 + right->info00, left->info01 + right->info00, left->info00 + right->info10}); + info10 = max({left->info10 + right->info00, left->info10 + right->info10, left->info11 + right->info00}); + info01 = max({left->info00 + right->info01, left->info01 + right->info01, left->info00 + right->info11}); + } +}; + + +class Solution { +public: + int maximumSumSubsequence(vector& nums, vector>& queries) + { + int n = nums.size(); + SegTreeNode* root = new SegTreeNode(0, n-1, nums); + + LL ret = 0; + for (auto q: queries) + { + root->updateRange(q[0], q[1]); + ret += max({root->info00,root->info01,root->info10,root->info11}); + ret%=M; + } + return ret; + } +}; diff --git a/Segment_Tree/3165.Maximum-Sum-of-Subsequence-With-Non-adjacent-Elements/Readme.md b/Segment_Tree/3165.Maximum-Sum-of-Subsequence-With-Non-adjacent-Elements/Readme.md new file mode 100644 index 000000000..1f35afc4b --- /dev/null +++ b/Segment_Tree/3165.Maximum-Sum-of-Subsequence-With-Non-adjacent-Elements/Readme.md @@ -0,0 +1,70 @@ +### 3165.Maximum-Sum-of-Subsequence-With-Non-adjacent-Elements + +本题的基础是经典的house robber,但是如果从house robber的常规解法去思考,那么本题是做不下去的。事实上,house robber有另一种适合拓展的做法,即递归分治。 + +我们令dp00[i][j]表示[i:j]区间内的最大收益,并且要求左右端点都不取到。同理,定义dp11[i][j]为区间[i:j]两个端点都取到时的最大收益,定义dp10[i][j]为区间[i:j]仅左端点都取到时的最大收益,定义dp01[i][j]为区间[i:j]仅右端点都取到时的最大收益。 + +我们不难发现,对于[i:j]内的任意一个点k,我们可以将[i:j]的收益分为[i:k]和[k+1:j]两端区间的收益之和。更具体地,要满足“不同时取相邻节点”的原则,针对分割处取或不取的决策,我们可以有对dp00[i][j]的三种分解 +``` +dp00[i][j] = max{dp00[i][k]+dp00[k+1][j], dp01[i][k]+dp00[k+1][j], dp00[i][k]+dp10[k+1][j],} +``` +同理,我们可以写出dp01,dp10,dp11的分解。 + +至此我们可以发现这是一个可以自上而下分治解决的问题。边界条件就是`dp00[i][i]=0, dp11[i][i]=1, dp01[i][i]=dp10[i][i]=-inf`. + +写成这样分治的结构,我们明显可以搞成线段树,它有两个好处: +1. 线段树可以用log(n)的时间求任意一段区间内的最大收益。 +2. 对于任何单点的变动后,我们依然可以用log(n)的时间更新整棵线段树。 + +注意,这样的线段树,懒标记是不能用的。每次单点的变动,必须将更新传播到最底层,再把区间最大收益反向传播上去。 + +本题的线段树不需要queryRange方法,最终只需要返回`root->info00,root->info10,root->info01,root->info11`的最大值即可。 + +数据结构如下: +```cpp +class SegTreeNode +{ + public: + SegTreeNode* left = NULL; + SegTreeNode* right = NULL; + int start, end; + LL info00, info11, info10, info01; + + SegTreeNode(int a, int b, vector& vals) // init for range [a,b] with val + { + start = a, end = b; + if (a==b) + { + info11 = vals[start], info01 = -1e18, info10 = -1e18, info00 = 0; + return; + } + int mid = (a+b)/2; + + left = new SegTreeNode(a, mid, vals); + right = new SegTreeNode(mid+1, b, vals); + info11 = max({left->info10 + right->info01, left->info11 + right->info01, left->info10 + right->info11}); + info00 = max({left->info00 + right->info00, left->info01 + right->info00, left->info00 + right->info10}); + info10 = max({left->info10 + right->info00, left->info10 + right->info10, left->info11 + right->info00}); + info01 = max({left->info00 + right->info01, left->info01 + right->info01, left->info00 + right->info11}); + } + + void updateRange(int a, int val) // set range [a,b] with val + { + if (a < start || a > end ) // not covered by [a,b] at all + return; + if (start==end) // completely covered within [a,b] + { + info00 = 0; + info11 = val; + return; + } + + left->updateRange(a, val); + right->updateRange(a, val); + info11 = max({left->info10 + right->info01, left->info11 + right->info01, left->info10 + right->info11}); + info00 = max({left->info00 + right->info00, left->info01 + right->info00, left->info00 + right->info10}); + info10 = max({left->info10 + right->info00, left->info10 + right->info10, left->info11 + right->info00}); + info01 = max({left->info00 + right->info01, left->info01 + right->info01, left->info00 + right->info11}); + } +}; +``` diff --git a/Segment_Tree/3187.Peaks-in-Array/3187.Peaks-in-Array.cpp b/Segment_Tree/3187.Peaks-in-Array/3187.Peaks-in-Array.cpp new file mode 100644 index 000000000..a3125ea3a --- /dev/null +++ b/Segment_Tree/3187.Peaks-in-Array/3187.Peaks-in-Array.cpp @@ -0,0 +1,148 @@ +using LL = long long; +class SegTreeNode +{ + public: + SegTreeNode* left = NULL; + SegTreeNode* right = NULL; + int start, end; + LL info; // the sum value over the range + bool lazy_tag; + LL lazy_val; + + SegTreeNode(int a, int b, int val) // init for range [a,b] with val + { + lazy_tag = 0; + lazy_val = 0; + start = a, end = b; + if (a==b) + { + info = val; + return; + } + int mid = (a+b)/2; + if (left==NULL) + { + left = new SegTreeNode(a, mid, val); + right = new SegTreeNode(mid+1, b, val); + info = left->info + right->info; // check with your own logic + } + } + + SegTreeNode(int a, int b, vector& val) // init for range [a,b] with the same-size array val + { + lazy_tag = 0; + lazy_val = 0; + start = a, end = b; + if (a==b) + { + info = val[a]; + return; + } + int mid = (a+b)/2; + if (left==NULL) + { + left = new SegTreeNode(a, mid, val); + right = new SegTreeNode(mid+1, b, val); + info = left->info + right->info; // check with your own logic + } + } + + void pushDown() + { + if (lazy_tag==1 && left) + { + left->info = lazy_val * (left->end - left->start + 1); + right->info = lazy_val * (right->end - right->start + 1); + left->lazy_tag = 1; left->lazy_val = lazy_val; + right->lazy_tag = 1; right->lazy_val = lazy_val; + lazy_tag = 0; lazy_val = 0; + } + } + + void updateRange(int a, int b, int val) // set range [a,b] with val + { + if (b < start || a > end ) // not covered by [a,b] at all + return; + if (a <= start && end <=b) // completely covered within [a,b] + { + info = val * (end-start+1); + lazy_tag = 1; + lazy_val = val; + return; + } + + if (left) + { + pushDown(); + left->updateRange(a, b, val); + right->updateRange(a, b, val); + info = left->info + right->info; // write your own logic + } + } + + LL queryRange(int a, int b) // query the sum over range [a,b] + { + if (b < start || a > end ) + { + return 0; // check with your own logic + } + if (a <= start && end <=b) + { + return info; // check with your own logic + } + + if (left) + { + pushDown(); + LL ret = left->queryRange(a, b) + right->queryRange(a, b); + info = left->info + right->info; // check with your own logic + return ret; + } + + return info; // should not reach here + } +}; + +class Solution { +public: + vector countOfPeaks(vector& nums, vector>& queries) + { + int n = nums.size(); + vectorpeaks(n, 0); + for (int i=1; inums[i-1] && nums[i]>nums[i+1]) + peaks[i] = 1; + } + + SegTreeNode* root = new SegTreeNode(0, n-1, peaks); + + vectorrets; + for (auto query: queries) + { + if (query[0]==1) + { + int a = query[1], b = query[2]; + rets.push_back(root->queryRange(a+1, b-1)); + } + else + { + int i = query[1]; + nums[i] = query[2]; + if (i>=1 && i=1 && i-1=1 && i+1&nums, vector&peaks) + { + int v = nums[i]>nums[i-1] && nums[i]>nums[i+1]; + if (v==peaks[i]) return; + peaks[i] = v; + root->updateRange(i,i,v); + } +}; diff --git a/Segment_Tree/3187.Peaks-in-Array/Readme.md b/Segment_Tree/3187.Peaks-in-Array/Readme.md new file mode 100644 index 000000000..78d93ad47 --- /dev/null +++ b/Segment_Tree/3187.Peaks-in-Array/Readme.md @@ -0,0 +1,5 @@ +### 3187.Peaks-in-Array + +高效地求任意一段区间内的peak的数目,显然是用线段树实现。我们构造一棵线段树,叶子节点是一个binary value,表示该元素是否是peak。 + +当我们修改nums[i]时,可能会对i-1,i,i+1三处位置的peak状态产生影响。所以我们需要分别进行考察,相应地修改线段树节点的值。 diff --git a/Segment_Tree/3261.Count-Substrings-That-Satisfy-K-Constraint-II/3261.Count-Substrings-That-Satisfy-K-Constraint-II.cpp b/Segment_Tree/3261.Count-Substrings-That-Satisfy-K-Constraint-II/3261.Count-Substrings-That-Satisfy-K-Constraint-II.cpp new file mode 100644 index 000000000..6a6521eeb --- /dev/null +++ b/Segment_Tree/3261.Count-Substrings-That-Satisfy-K-Constraint-II/3261.Count-Substrings-That-Satisfy-K-Constraint-II.cpp @@ -0,0 +1,156 @@ +using LL = long long; +LL M = 1e9+7; +class SegTreeNode +{ + public: + SegTreeNode* left = NULL; + SegTreeNode* right = NULL; + int start, end; + LL info; // the sum value over the range + LL delta; + bool tag; + + SegTreeNode(int a, int b, int val) // init for range [a,b] with val + { + tag = 0; + delta = 0; + start = a, end = b; + if (a==b) + { + info = val; + return; + } + int mid = (a+b)/2; + if (left==NULL) + { + left = new SegTreeNode(a, mid, val); + right = new SegTreeNode(mid+1, b, val); + info = left->info + right->info; // check with your own logic + } + } + + SegTreeNode(int a, int b, vector& val) // init for range [a,b] with the same-size array val + { + tag = 0; + delta = 0; + start = a, end = b; + if (a==b) + { + info = val[a]; + return; + } + int mid = (a+b)/2; + if (left==NULL) + { + left = new SegTreeNode(a, mid, val); + right = new SegTreeNode(mid+1, b, val); + info = left->info + right->info; // check with your own logic + } + } + + void pushDown() + { + if (tag==1 && left) + { + left->info += delta * (left->end - left->start + 1); + left->delta += delta; + right->info += delta * (right->end - right->start + 1); + right->delta += delta; + left->tag = 1; + right->tag = 1; + tag = 0; + delta = 0; + } + } + + void updateRangeBy(int a, int b, int val) // increase range [a,b] by val + { + if (b < start || a > end ) // not covered by [a,b] at all + return; + if (a <= start && end <=b) // completely covered within [a,b] + { + info += val * (end-start+1); + delta += val; + tag = 1; + return; + } + + if (left) + { + pushDown(); + left->updateRangeBy(a, b, val+delta); + right->updateRangeBy(a, b, val+delta); + delta = 0; + tag = 0; + info = left->info + right->info; // write your own logic + } + } + + LL queryRange(int a, int b) // query the sum within range [a,b] + { + if (b < start || a > end ) + { + return 0; // check with your own logic + } + if (a <= start && end <=b) + { + return info; // check with your own logic + } + + if (left) + { + pushDown(); + LL ret = left->queryRange(a, b) + right->queryRange(a, b); + info = left->info + right->info; // check with your own logic + return ret; + } + + return info; // should not reach here + } +}; + + +class Solution { +public: + vector countKConstraintSubstrings(string s, int k, vector>& queries) + { + int n = s.size(); + vectorend(n); + int j = 0; + int count0=0, count1=0; + for (int i=0; irets(queries.size()); + SegTreeNode* root = new SegTreeNode(0, n-1, 0); + + int i = n-1; + for (auto q: queries) + { + int a = q[0], b = q[1], idx=q[2]; + while (i>=a) + { + root->updateRangeBy(i, end[i], 1); + i--; + } + rets[idx] = root->queryRange(a,b); + } + + return rets; + } +}; diff --git a/Segment_Tree/3261.Count-Substrings-That-Satisfy-K-Constraint-II/Readme.md b/Segment_Tree/3261.Count-Substrings-That-Satisfy-K-Constraint-II/Readme.md new file mode 100644 index 000000000..fa95f795d --- /dev/null +++ b/Segment_Tree/3261.Count-Substrings-That-Satisfy-K-Constraint-II/Readme.md @@ -0,0 +1,7 @@ +### 3261.Count-Substrings-That-Satisfy-K-Constraint-II + +假设以i作为左边界,那么我们容易得到最远的右边界j,使得[i:j]是最长的valid substring;并且以其中的任何一点作为右边界,都是valid substring. 然后发现,如果将左边界i往右移动一位,那么最远的右边界位置j必然是单调向右移动的。所以我们可以用双指针的方法,求得所有的len[i],表示以i作为左边界,那么我们得到最远的valid substring的右边界。 + +那么对于一个query而言,如何求[l,r]内所有的valid substring呢?考虑到Q的数量很大,我们很容易想到线段树,希望能用log的时间来解决一个query(计算一个区间内的substring之和)。但是一个valid sbustring是需要用两个端点来表示的,这似乎和线段树的应用有些不同。于是我们想到,能否在线段树里用一个点来表示一个substring?于是我们可以尝试将所有的valid substring只用其右端点来表示。举个例子,对于以i为左端点的valid subtring,根据其右端点的位置,我们在i,i+1,i+2,...,len[i]都可以记录+1;也就是说,在线段树上我们可以对[i,len[i]]这段区间整体都加1. 此外,线段树就可以很方便地query所有右端点落入[l,r]内valid substring的个数。 + +但是这里就有个问题,[l,r]内的valid substring,并不等同于右端点落入[l,r]内valid substring。对于后者而言,有些substring的左端点在l的左边,这是需要排除掉的。我们该如何去掉那些左端点在l左边的那些substring呢?在线段树的操作里,似乎并没有什么好办法,但是有一个巧妙的策略:那就是不把“那些左端点位于l左边的那些substring”收录进线段树。这就提示我们可以将queries按照左端点倒序排列依次处理。对于[l,r]这个query而言,我们(只)收录进所有左端点大于等于l的那些substring。此时在线段树内查询[l,r]的区间和,就代表了所有右端点在[l,r]范围内的valid substring,同时这些string的左端点都不会小于l。 diff --git a/Segment_Tree/3624.Number-of-Integers-With-Popcount-Depth-Equal-to-K-II/3624.Number-of-Integers-With-Popcount-Depth-Equal-to-K-II.cpp b/Segment_Tree/3624.Number-of-Integers-With-Popcount-Depth-Equal-to-K-II/3624.Number-of-Integers-With-Popcount-Depth-Equal-to-K-II.cpp new file mode 100644 index 000000000..35f00922a --- /dev/null +++ b/Segment_Tree/3624.Number-of-Integers-With-Popcount-Depth-Equal-to-K-II/3624.Number-of-Integers-With-Popcount-Depth-Equal-to-K-II.cpp @@ -0,0 +1,88 @@ +using ll = long long; + +class BIT{ + public: + int N; + vectorbitArr; // Note: all arrays are 1-index + vectornums; + long long M = 1e9+7; + + void init(int N) + { + this->N = N; + bitArr.resize(N+1); + nums.resize(N+1); + } + + // increase nums[i] by delta + void updateDelta(int i, long long delta) { + int idx = i; + while (idx <= N) + { + bitArr[idx]+=delta; + // bitArr[idx] %= M; + idx+=idx&(-idx); + } + } + + // sum of a range nums[1:j] inclusively + long long queryPreSum(int idx){ + long long result = 0; + while (idx){ + result += bitArr[idx]; + // result %= M; + idx-=idx&(-idx); + } + return result; + } + + // sum of a range nums[i:j] inclusively + long long sumRange(int i, int j) { + return queryPreSum(j)-queryPreSum(i-1); + } +}; + +class Solution { +public: + int getDepth(ll x) { + int count = 0; + while (x!=1ll) { + x = __builtin_popcountll(x); + count++; + } + return count; + } + + vector popcountDepth(vector& nums, vector>& queries) { + int N = nums.size()+1; + vector bit_arr (5); + for (int i=0; i<=4; i++) + bit_arr[i].init(N); + + for (int i=0; irets; + for (auto&q: queries) { + if (q[0]==2) { + ll idx = q[1], val = q[2]; + int depth0 = getDepth(nums[idx]); + int depth1 = getDepth(val); + bit_arr[depth0].updateDelta(idx+1, -1); + bit_arr[depth1].updateDelta(idx+1, 1); + nums[idx] = val; + } else { + int l = q[1], r = q[2], k = q[3]; + if (k>=5) + rets.push_back(0); + else + rets.push_back(bit_arr[k].sumRange(l+1,r+1)); + } + } + + return rets; + + } +}; diff --git a/Segment_Tree/3624.Number-of-Integers-With-Popcount-Depth-Equal-to-K-II/Readme.md b/Segment_Tree/3624.Number-of-Integers-With-Popcount-Depth-Equal-to-K-II/Readme.md new file mode 100644 index 000000000..338adc20e --- /dev/null +++ b/Segment_Tree/3624.Number-of-Integers-With-Popcount-Depth-Equal-to-K-II/Readme.md @@ -0,0 +1,10 @@ +### 3624.Number-of-Integers-With-Popcount-Depth-Equal-to-K-II + +首先我们要意识到,任何一个长整型的popcount depth不可能很大。 + +首先对于<=64的数,我们可以使用穷举的方法,发现最坏情况就是63,它的路径就是63->6->2->1。而剩下任意大于64的长整型(共有64个bit),它的第一步变化之后必然落入[1,64]之间。由之前的结论,其路径最多再走3步就会到1。所以结论是:任意一个长整型,其popcount depth不超过4. + +既然处理任何一个数只需要4步就能求出深度,那我们可以将所有nums都先处理一遍,将所有nums可以按照depth分类。于是对于每一种深度,我们可以知道有哪些数对应,将其index标记为1(否则标记为0),这样用树状数组就可以高效(log的时间)地算出任意区间内有多少数对应该深度(即求区间和),满足了第一类query的要求。同时树状数组支持对单点的动态修改,支持了第二类query的操作。 + +因为深度的种类只有5种,我们只需要开5个这样的树状数组,因此空间复杂度也是符合要求的。 + diff --git a/Segment_Tree/3671.Sum-of-Beautiful-Subsequences/3671.Sum-of-Beautiful-Subsequences.cpp b/Segment_Tree/3671.Sum-of-Beautiful-Subsequences/3671.Sum-of-Beautiful-Subsequences.cpp new file mode 100644 index 000000000..e3a9edca2 --- /dev/null +++ b/Segment_Tree/3671.Sum-of-Beautiful-Subsequences/3671.Sum-of-Beautiful-Subsequences.cpp @@ -0,0 +1,91 @@ +class BIT{ + public: + int N; + vectorbitArr; // Note: all arrays are 1-index + vectornums; + long long M = 1e9+7; + + void init(int N) + { + this->N = N; + bitArr.resize(N+1); + nums.resize(N+1); + } + + // increase nums[i] by delta + void updateDelta(int i, long long delta) { + int idx = i; + while (idx <= N) + { + bitArr[idx]+=delta; + bitArr[idx] %= M; + idx+=idx&(-idx); + } + } + + // sum of a range nums[1:j] inclusively + long long queryPreSum(int idx){ + long long result = 0; + while (idx){ + result += bitArr[idx]; + // result %= M; + idx-=idx&(-idx); + } + return result; + } + + // sum of a range nums[i:j] inclusively + long long sumRange(int i, int j) { + return queryPreSum(j)-queryPreSum(i-1); + } +}; + +long long MOD = 1e9+7; + +class Solution { +public: + int totalBeauty(vector& nums) { + int mx = *max_element(begin(nums), end(nums)); + vector>pos(mx+1); + for (int i=0; iseq(mx+1); + for (int g=1; g<=mx; g++) { + mapmp; + for (int x: pos[g]) mp[nums[x]] = 1; + int idx = 0; + for (auto& [k,v]:mp) v = ++idx; + + vectorarr; + for (int x: pos[g]) arr.push_back(mp[nums[x]]); + + BIT bit; + bit.init(arr.size()); + for (int x: arr) { + long long ans = bit.queryPreSum(x-1) + 1; + seq[g] = (seq[g]+ans)%MOD; + bit.updateDelta(x, ans); + } + } + + vectorret(mx+1); + for (int g=mx; g>=1; g--) { + ret[g] = seq[g]; + for (int j=g*2; j<=mx; j+=g) + ret[g] = (ret[g]-ret[j]+MOD) % MOD; + } + + long long ans = 0; + for (int g=mx; g>=1; g--) { + ans = (ans + g*ret[g]) % MOD; + } + return ans; + } +}; diff --git a/Segment_Tree/3671.Sum-of-Beautiful-Subsequences/Readme.md b/Segment_Tree/3671.Sum-of-Beautiful-Subsequences/Readme.md new file mode 100644 index 000000000..8fe0eea1f --- /dev/null +++ b/Segment_Tree/3671.Sum-of-Beautiful-Subsequences/Readme.md @@ -0,0 +1,12 @@ +### 3671.Sum-of-Beautiful-Subsequences + +此题包含了两个知识点。我们拆开来分析。 + +第一个问题,对于任意的正整数g,如何计算在数组里有多少个strictly increasing subsequence并且序列中每个元素都是“g的倍数”。假设我们已经通过预处理,知道数组里g的倍数位于[i1,i2,...,ik]的位置上。注意我们只需要构造严格递增的序列,所以我们需要只知道它们之间的大小关系和位置关系,但是不需要知道绝对大小和绝对位置,故我们可以离散化,转化为类似[1,3,5,2,4]这样的形式,表示数组里有5个数是g的倍数,且它们的相对位置和相对大小都可以用这样的形式表示出来。于是我们就构造出了这样一个问题:里面有多少个严格递增的序列? + +这是一个经典问题,我们可以用树状数组来解。我们想象一个长度为5的空数组,需要依照上述次序涂黑。首先我们在填写1时,以它结尾的递增序列只有一个,记作f(1)。然后我们填写3时,以它结尾的递增序列就是`f(3)=f(1)+1`,表示以1结尾的递增序列再附加上3,或者单独的3也可以构成一个符合条件的序列。然后我们填写5时,以它为结尾的递增序列就是`f(5)=f(1)+f(3)+1`。然后我们填写2时,以它结尾的递增序列就是`f(2)=f(1)+1`...由此我们可以看出f(x)就是BIT数组前缀和`f(1)+f(2)+...+f(x-1)`再加1.于是我们就可以求出所有的f(x)并且求和,就得出:数组里有多少个严格递增序列、并且每个元素都是“g的倍数”,我们记作p(g). + +在有了p(g)的基础上,我们如何进一步求出数组里有多少个严格递增序列、并且所有元素的GCD恰好是g呢?我们记作这样的解是ret(g),其实就是删去在p(g)里去除“2以上g的倍数”的情况,即`ret(g) = p(g)-ret(2g)-ret(3g)-...-ret(kg)`. 所以我们只需要对g按从大到小的顺序求解q:当解到ret(g)时,p(g)和`ret(2g)...ret(kg)`就都是已知的了。初始条件是对于数组里的最大元素mx,`ret(mx) = 1`. + +最终返回`g*ret(g)`对于g=1,2,..,mx的之和 + diff --git a/Segment_Tree/3721.Longest-Balanced-Subarray-II/3721.Longest-Balanced-Subarray-II.cpp b/Segment_Tree/3721.Longest-Balanced-Subarray-II/3721.Longest-Balanced-Subarray-II.cpp new file mode 100644 index 000000000..3fc3e61e8 --- /dev/null +++ b/Segment_Tree/3721.Longest-Balanced-Subarray-II/3721.Longest-Balanced-Subarray-II.cpp @@ -0,0 +1,94 @@ +class Solution { + +struct SegTree{ + int n; + struct Node {int mn, mx, lazy;}; + vectorst; + SegTree(int _n=0) {init(_n);} + void init(int _n) { + n = _n; + st.assign(4*(n+5), {0,0,0}); + } + void apply(int p, int val){ + st[p].mn += val; + st[p].mx += val; + st[p].lazy += val; + } + void push(int p){ + if(st[p].lazy){ + apply(p<<1, st[p].lazy); + apply(p<<1|1, st[p].lazy); + st[p].lazy = 0; + } + } + void pull(int p){ + st[p].mn = min(st[p<<1].mn, st[p<<1|1].mn); + st[p].mx = max(st[p<<1].mx, st[p<<1|1].mx); + } + void range_add(int p, int l, int r, int L, int R, int val){ + if(L>R || r>1; + range_add(p<<1, l, m, L, R, val); + range_add(p<<1|1, m+1, r, L, R, val); + pull(p); + } + void range_add(int L, int R, int val){ + if(L>R) return; + range_add(1, 0, n, L, R, val); + } + int find_first_equal(int p, int l, int r, int L, int R, int target){ + if(L>R || r target || st[p].mx < target) return -1; + if(l==r){ + return l; + } + push(p); + int m=(l+r)>>1; + int res = find_first_equal(p<<1, l, m, L, R, target); + if(res != -1) return res; + return find_first_equal(p<<1|1, m+1, r, L, R, target); + } + int find_first_equal(int L, int R, int target){ + if(L>R) return -1; + return find_first_equal(1, 0, n, L, R, target); + } + int point_query(int p, int l, int r, int idx){ + if(l==r) return st[p].mn; + push(p); + int m=(l+r)>>1; + if(idx<=m) return point_query(p<<1, l, m, idx); + else return point_query(p<<1|1, m+1, r, idx); + } + int point_query(int idx){ return point_query(1,0,n,idx); } +}; + +public: + int longestBalanced(vector& nums) { + int n = nums.size(); + SegTree seg(n); + unordered_maplastPos; + int ret = 0; + for (int r = 1; r <= n; r++) { + int v = nums[r-1]; + int sign = (v%2==0)?1:-1; + int prev = 0; + auto iter = lastPos.find(v); + if (iter!=lastPos.end()) + prev = iter->second; + if (prev==0) { + seg.range_add(r, n, sign); + } else { + if (prev<=r-1) + seg.range_add(prev, r-1, -sign); + } + lastPos[v] = r; + int cur = seg.point_query(r); + int idx = seg.find_first_equal(0,r-1,cur); + if (idx!=-1) + ret = max(ret, r-idx); + } + return ret; + } +}; diff --git a/Segment_Tree/3721.Longest-Balanced-Subarray-II/Readme.md b/Segment_Tree/3721.Longest-Balanced-Subarray-II/Readme.md new file mode 100644 index 000000000..ced4fb9f7 --- /dev/null +++ b/Segment_Tree/3721.Longest-Balanced-Subarray-II/Readme.md @@ -0,0 +1,13 @@ +### 3721.Longest-Balanced-Subarray-II + +既然是求区间,那么不妨考虑“前缀之差”。根据经验,我们需要记录每个前缀里“unique even numbers与unique odd numbers的个数之差”。任何两个差相同的前缀,意味着一个符合条件的区间,即“unique even numbers与unique odd numbers的个数相等”。 + +对于一个nums[0:i]的前缀,如何表达“unique even numbers与unique odd numbers的个数之差”呢?因为每个unique number可能出现在很多位置,为了便于分析,我们约定只关心每个unique number最后一次出现的位置。记作lastPos[x]。比如偶数4最后一次出现在了a这个位置,那么我们就记录array[a]=1;假设奇数7最后一次出现在了b这个位置,那么我们就记录array[b]=-1.可见,只要计算这个01数组array[0:i]的前缀和,就可以知道nums[0:i]里“unique even numbers与unique odd numbers的个数之差”。 + +所以我们需要构造一棵线段树seg,其中节点seg[i]表示array[0:i]的前缀和。如果seg[i]与之前的某个seg[j]相同,那么就意味着[j:i]是一个符合条件的区间。注意对于线段树,是可以支持log(n)时间去实现`find_first_equal`的操作。这个我们后面再提。 + +现在,我们需要理解为什么必须用线段树而不是普通的数组来记录上面的seg[i]呢?因为随着i的增大,lastPos[]会改变(比如,最新的x=nums[i]会导致`lastPos[x]=i`的更新)。这继而会造成array[]也会改变(显然,array[i]会是一个非零值,而之前x所在的位置的array值会置零)。这继而又会造成array的前缀和,即seg[i]的改变。对于一个mutable的数组,想用log的时间实现`find_first_equal`是不可能的。 + +接下来我们思考,随着前缀下标i的增大,这棵线段树该怎么更新。当前缀移动到i位置时,记x=nums[i],原先的lastPos[x]=j。这就意味着:原来的array[j]从1(或者-1)要变成0,而array[i]要填充为1(或者-1)。对于array的前缀和而言,下标从j到i-1的前缀和(即seg[j]到seg[i-1])都需要撤回1(或者增加1);而最新的seg[i]就是在seg[i-1]的基础上加上array[i]的影响。可见,对于线段树而言,上述的操作本质就是树上的区间更新。 + +解决了线段树怎么更新,接下来讲`find_first_equal`的实现。我们对于线段树的每个节点(对应array的一个前缀和)维护其最大值和最小值两个val。如果max=>target并且min<=target,那么必然在这个节点对应的区间有一个节点的值是target。为什么能保证必然存在target呢,其实在于本题的特点:就是随着元素的增加,前缀和的变化永远是+1或者-1。因此如果前缀和从min变化到max,且target位于min与max之间,那么前缀和必然经过target。由此我们通过线段树二分处理的性质,可以用log时间找到第一个等于target的节点。 diff --git a/Segment_Tree/3915.Maximum-Sum-of-Alternating-Subsequence-With-Distance-at-Least-K/3915.Maximum-Sum-of-Alternating-Subsequence-With-Distance-at-Least-K.cpp b/Segment_Tree/3915.Maximum-Sum-of-Alternating-Subsequence-With-Distance-at-Least-K/3915.Maximum-Sum-of-Alternating-Subsequence-With-Distance-at-Least-K.cpp new file mode 100644 index 000000000..c36363397 --- /dev/null +++ b/Segment_Tree/3915.Maximum-Sum-of-Alternating-Subsequence-With-Distance-at-Least-K/3915.Maximum-Sum-of-Alternating-Subsequence-With-Distance-at-Least-K.cpp @@ -0,0 +1,104 @@ +using ll = long long; + +struct SegmentTree { + int n; + vector tree; + + SegmentTree(int size) { + n = size; + tree.resize(4 * n); + } + + void build(vector& arr, int node, int l, int r) { + if (l == r) { + tree[node] = arr[l]; + return; + } + int mid = (l + r) / 2; + build(arr, node * 2, l, mid); + build(arr, node * 2 + 1, mid + 1, r); + tree[node] = max(tree[node * 2], tree[node * 2 + 1]); + } + + // 单点赋值 + void update_point(int idx, long long val, int node, int l, int r) { + if (l == r) { + tree[node] = val; + return; + } + int mid = (l + r) / 2; + if (idx <= mid) update_point(idx, val, node * 2, l, mid); + else update_point(idx, val, node * 2 + 1, mid + 1, r); + + tree[node] = max(tree[node * 2], tree[node * 2 + 1]); + } + + // 区间赋值(逐点更新实现) + void update_range_val(int ql, int qr, long long val, int node, int l, int r) { + if (ql > r || qr < l) return; + + if (l == r) { + tree[node] = val; + return; + } + + int mid = (l + r) / 2; + update_range_val(ql, qr, val, node * 2, l, mid); + update_range_val(ql, qr, val, node * 2 + 1, mid + 1, r); + + tree[node] = max(tree[node * 2], tree[node * 2 + 1]); + } + + // 区间最大值查询 + long long query_range_max(int ql, int qr, int node, int l, int r) { + if (ql > r || qr < l) return LLONG_MIN; + + if (ql <= l && r <= qr) { + return tree[node]; + } + + int mid = (l + r) / 2; + return max( + query_range_max(ql, qr, node * 2, l, mid), + query_range_max(ql, qr, node * 2 + 1, mid + 1, r) + ); + } +}; + + +class Solution { +public: + long long maxAlternatingSum(vector& nums, int k) { + int n = nums.size(); + ll maxv = 100000; + SegmentTree segUp(maxv), segDown(maxv); + vectorup(n), down(n); + ll ret = 0; + + for (int i=0; i=0) { + int v = nums[i-k]; + segUp.update_range_val(v, v, up[i-k], 1, 1, maxv); + segDown.update_range_val(v, v, down[i-k], 1, 1, maxv); + } + + int v = nums[i]; + ll bestDown = segDown.query_range_max(1, v-1, 1,1,maxv); + if (bestDown == LLONG_MIN) + up[i] = v; + else + up[i] = bestDown + v; + + ll bestUp = segUp.query_range_max(v+1, maxv, 1,1,maxv); + if (bestUp == LLONG_MIN) + down[i] = v; + else + down[i] = bestUp + v; + + ret = max({ret, up[i], down[i]}); + + } + + return ret; + } +}; diff --git a/Segment_Tree/3915.Maximum-Sum-of-Alternating-Subsequence-With-Distance-at-Least-K/Readme.md b/Segment_Tree/3915.Maximum-Sum-of-Alternating-Subsequence-With-Distance-at-Least-K/Readme.md new file mode 100644 index 000000000..38f6d5c47 --- /dev/null +++ b/Segment_Tree/3915.Maximum-Sum-of-Alternating-Subsequence-With-Distance-at-Least-K/Readme.md @@ -0,0 +1,13 @@ +### 3915.Maximum-Sum-of-Alternating-Subsequence-With-Distance-at-Least-K + +和其他wiggle subsequence的题目类似,本题总体上能看出是一个动态规划。 + +我们令up[i]表示以元素i为结尾、且最后一段是上升段的序列最大和;同理,令down[i]表示以元素i为结尾、且最后一段是下降段的序列最大和。 + +对于up[i]而言,它之前能衔接的元素j有两个要求:j必须在[0:i-k]之间,并且nums[j]的数值小于nums[i](记作v)。我们在这个约束下想找最大的down[j]值。我们可以想到维护一个线段树(更新到i-k为止),记录下每种nums元素值所对应的最大down值。这样,我们只需要在线段树区间[1,v-1]内寻找最大值即可。这个最大值加上v本身,就是up[i]的最大值。 + +同理,对于down[i]而言,我们维护一个类似的线段树(也是只更新到i-k为止),记录下每种nums元素值所对应的最大up值。这样,我们需要在线段树区间[v+1, 1e5]内寻找最大值即可。这个最大值加上v本身,就是down[i]的最大值。 + +记得在求up[i]和donw[i]之前,需要分别将donw[i-k]和up[i-k]分别加入到两棵线段树之中。 + +所以本题就是DP的基础上,套了一个区间max的线段树模版。注意线段树的区间对应的是nums的数值,不是nums的长度。所以线段树的长度是固定的,即从1到1e5. diff --git a/Segment_Tree/699.Falling-Squares/699.Falling-Squares_Heap_v1.cpp b/Segment_Tree/699.Falling-Squares/699.Falling-Squares_Heap_v1.cpp index 9522ee658..f1fe39f79 100644 --- a/Segment_Tree/699.Falling-Squares/699.Falling-Squares_Heap_v1.cpp +++ b/Segment_Tree/699.Falling-Squares/699.Falling-Squares_Heap_v1.cpp @@ -1,44 +1,42 @@ class Solution { public: - vector fallingSquares(vector>& positions) - { - mapMap; - - Map[0]=0; - Map[INT_MAX]=0; - - vectorresults; - int cur=0; - - for (auto p:positions) - { - int left=p.first; - int right=p.first+p.second-1; - int h=p.second; - int maxH=0; - + vector fallingSquares(vector > &positions) { + map Map; + + Map[0] = 0; + Map[INT_MAX] = 0; + + vector results; + int cur = 0; + + for (auto p: positions) { + int left = p[0]; + int right = p[0] + p[1] - 1; + int h = p[1]; + int maxH = 0; + auto ptri = Map.lower_bound(left); auto ptrj = Map.upper_bound(right); - - int temp = prev(ptrj,1)->second; - - auto ptr = ptri->first==left? ptri:prev(ptri,1); - while (ptr!=ptrj) - { - maxH=max(maxH, ptr->second); - ptr = next(ptr,1); + + int temp = prev(ptrj, 1)->second; + + auto ptr = ptri->first == left ? ptri : prev(ptri, 1); + while (ptr != ptrj) { + maxH = max(maxH, ptr->second); + ptr = next(ptr, 1); } - if (ptri!=ptrj) - Map.erase(ptri,ptrj); - - Map[left] = maxH+h; - Map[right+1] = temp; - cur = max(cur, maxH+h); - - results.push_back(cur); + if (ptri != ptrj) + Map.erase(ptri, ptrj); + + Map[left] = maxH + h; + if (right + 1 < ptrj->first) + Map[right + 1] = temp; + cur = max(cur, maxH + h); + + results.push_back(cur); } - + return results; - + } }; diff --git a/Segment_Tree/699.Falling-Squares/699.Falling-Squares_Heap_v2.cpp b/Segment_Tree/699.Falling-Squares/699.Falling-Squares_Heap_v2.cpp index 373f43045..b63837efd 100644 --- a/Segment_Tree/699.Falling-Squares/699.Falling-Squares_Heap_v2.cpp +++ b/Segment_Tree/699.Falling-Squares/699.Falling-Squares_Heap_v2.cpp @@ -1,39 +1,37 @@ class Solution { public: - vector fallingSquares(vector>& positions) - { - mapMap; - Map[0]=0; - Map[INT_MAX]=0; - int cur=0; - vectorresults; - - for (int i=0; i fallingSquares(vector > &positions) { + map Map; + Map[0] = 0; + Map[INT_MAX] = 0; + int cur = 0; + vector results; + + for (int i = 0; i < positions.size(); i++) { + int left = positions[i][0]; + int len = positions[i][1]; + int right = left + len - 1; + auto pos1 = Map.lower_bound(left); - - int Hmax=0; - auto pos=pos1; - if (pos->first!=left) pos=prev(pos,1); - while (pos->first <= right) - { + + int Hmax = 0; + auto pos = pos1; + if (pos->first != left) pos = prev(pos, 1); + while (pos->first <= right) { Hmax = max(Hmax, pos->second); - pos = next(pos,1); + pos = next(pos, 1); } - int rightHeight = prev(pos,1)->second; - - Map.erase(pos1,pos); - Map[left]=Hmax+len; - Map[right+1]=rightHeight; - - cur = max(cur, Hmax+len); + int rightHeight = prev(pos, 1)->second; + + Map.erase(pos1, pos); + Map[left] = Hmax + len; + if (right + 1 < pos->first) + Map[right + 1] = rightHeight; + + cur = max(cur, Hmax + len); results.push_back(cur); } - + return results; } }; diff --git a/Heap/1296.Divide-Array-in-Sets-of-K-Consecutive-Numbers/1296.Divide-Array-in-Sets-of-K-Consecutive-Numbers.cpp b/Sorted_Container/1296.Divide-Array-in-Sets-of-K-Consecutive-Numbers/1296.Divide-Array-in-Sets-of-K-Consecutive-Numbers.cpp similarity index 100% rename from Heap/1296.Divide-Array-in-Sets-of-K-Consecutive-Numbers/1296.Divide-Array-in-Sets-of-K-Consecutive-Numbers.cpp rename to Sorted_Container/1296.Divide-Array-in-Sets-of-K-Consecutive-Numbers/1296.Divide-Array-in-Sets-of-K-Consecutive-Numbers.cpp diff --git a/Heap/1296.Divide-Array-in-Sets-of-K-Consecutive-Numbers/Readme.md b/Sorted_Container/1296.Divide-Array-in-Sets-of-K-Consecutive-Numbers/Readme.md similarity index 100% rename from Heap/1296.Divide-Array-in-Sets-of-K-Consecutive-Numbers/Readme.md rename to Sorted_Container/1296.Divide-Array-in-Sets-of-K-Consecutive-Numbers/Readme.md diff --git a/Heap/1348.Tweet-Counts-Per-Frequency/1348.Tweet-Counts-Per-Frequency.cpp b/Sorted_Container/1348.Tweet-Counts-Per-Frequency/1348.Tweet-Counts-Per-Frequency.cpp similarity index 100% rename from Heap/1348.Tweet-Counts-Per-Frequency/1348.Tweet-Counts-Per-Frequency.cpp rename to Sorted_Container/1348.Tweet-Counts-Per-Frequency/1348.Tweet-Counts-Per-Frequency.cpp diff --git a/Heap/1348.Tweet-Counts-Per-Frequency/Readme.md b/Sorted_Container/1348.Tweet-Counts-Per-Frequency/Readme.md similarity index 100% rename from Heap/1348.Tweet-Counts-Per-Frequency/Readme.md rename to Sorted_Container/1348.Tweet-Counts-Per-Frequency/Readme.md diff --git a/Heap/1488.Avoid-Flood-in-The-City/1488.Avoid-Flood-in-The-City.cpp b/Sorted_Container/1488.Avoid-Flood-in-The-City/1488.Avoid-Flood-in-The-City.cpp similarity index 100% rename from Heap/1488.Avoid-Flood-in-The-City/1488.Avoid-Flood-in-The-City.cpp rename to Sorted_Container/1488.Avoid-Flood-in-The-City/1488.Avoid-Flood-in-The-City.cpp diff --git a/Heap/1488.Avoid-Flood-in-The-City/Readme.md b/Sorted_Container/1488.Avoid-Flood-in-The-City/Readme.md similarity index 100% rename from Heap/1488.Avoid-Flood-in-The-City/Readme.md rename to Sorted_Container/1488.Avoid-Flood-in-The-City/Readme.md diff --git a/Heap/1606.Find-Servers-That-Handled-Most-Number-of-Requests/1606.Find-Servers-That-Handled-Most-Number-of-Requests.cpp b/Sorted_Container/1606.Find-Servers-That-Handled-Most-Number-of-Requests/1606.Find-Servers-That-Handled-Most-Number-of-Requests.cpp similarity index 100% rename from Heap/1606.Find-Servers-That-Handled-Most-Number-of-Requests/1606.Find-Servers-That-Handled-Most-Number-of-Requests.cpp rename to Sorted_Container/1606.Find-Servers-That-Handled-Most-Number-of-Requests/1606.Find-Servers-That-Handled-Most-Number-of-Requests.cpp diff --git a/Heap/1606.Find-Servers-That-Handled-Most-Number-of-Requests/Readme.md b/Sorted_Container/1606.Find-Servers-That-Handled-Most-Number-of-Requests/Readme.md similarity index 100% rename from Heap/1606.Find-Servers-That-Handled-Most-Number-of-Requests/Readme.md rename to Sorted_Container/1606.Find-Servers-That-Handled-Most-Number-of-Requests/Readme.md diff --git a/Heap/1675.Minimize-Deviation-in-Array/1675.Minimize-Deviation-in-Array.cpp b/Sorted_Container/1675.Minimize-Deviation-in-Array/1675.Minimize-Deviation-in-Array.cpp similarity index 100% rename from Heap/1675.Minimize-Deviation-in-Array/1675.Minimize-Deviation-in-Array.cpp rename to Sorted_Container/1675.Minimize-Deviation-in-Array/1675.Minimize-Deviation-in-Array.cpp diff --git a/Heap/1675.Minimize-Deviation-in-Array/Readme.md b/Sorted_Container/1675.Minimize-Deviation-in-Array/Readme.md similarity index 100% rename from Heap/1675.Minimize-Deviation-in-Array/Readme.md rename to Sorted_Container/1675.Minimize-Deviation-in-Array/Readme.md diff --git a/Heap/1825.Finding-MK-Average/1825.Finding-MK-Average.cpp b/Sorted_Container/1825.Finding-MK-Average/1825.Finding-MK-Average.cpp similarity index 100% rename from Heap/1825.Finding-MK-Average/1825.Finding-MK-Average.cpp rename to Sorted_Container/1825.Finding-MK-Average/1825.Finding-MK-Average.cpp diff --git a/Heap/1825.Finding-MK-Average/Readme.md b/Sorted_Container/1825.Finding-MK-Average/Readme.md similarity index 100% rename from Heap/1825.Finding-MK-Average/Readme.md rename to Sorted_Container/1825.Finding-MK-Average/Readme.md diff --git a/Heap/1847.Closest-Room/1847.Closest-Room.cpp b/Sorted_Container/1847.Closest-Room/1847.Closest-Room.cpp similarity index 100% rename from Heap/1847.Closest-Room/1847.Closest-Room.cpp rename to Sorted_Container/1847.Closest-Room/1847.Closest-Room.cpp diff --git a/Heap/1847.Closest-Room/Readme.md b/Sorted_Container/1847.Closest-Room/Readme.md similarity index 100% rename from Heap/1847.Closest-Room/Readme.md rename to Sorted_Container/1847.Closest-Room/Readme.md diff --git a/Heap/1912.Design-Movie-Rental-System/1912.Design-Movie-Rental-System.cpp b/Sorted_Container/1912.Design-Movie-Rental-System/1912.Design-Movie-Rental-System.cpp similarity index 100% rename from Heap/1912.Design-Movie-Rental-System/1912.Design-Movie-Rental-System.cpp rename to Sorted_Container/1912.Design-Movie-Rental-System/1912.Design-Movie-Rental-System.cpp diff --git a/Heap/1912.Design-Movie-Rental-System/Readme.md b/Sorted_Container/1912.Design-Movie-Rental-System/Readme.md similarity index 100% rename from Heap/1912.Design-Movie-Rental-System/Readme.md rename to Sorted_Container/1912.Design-Movie-Rental-System/Readme.md diff --git a/Heap/2102.Sequentially-Ordinal-Rank-Tracker/2102.Sequentially-Ordinal-Rank-Tracker_v1.cpp b/Sorted_Container/2102.Sequentially-Ordinal-Rank-Tracker/2102.Sequentially-Ordinal-Rank-Tracker_v1.cpp similarity index 100% rename from Heap/2102.Sequentially-Ordinal-Rank-Tracker/2102.Sequentially-Ordinal-Rank-Tracker_v1.cpp rename to Sorted_Container/2102.Sequentially-Ordinal-Rank-Tracker/2102.Sequentially-Ordinal-Rank-Tracker_v1.cpp diff --git a/Heap/2102.Sequentially-Ordinal-Rank-Tracker/2102.Sequentially-Ordinal-Rank-Tracker_v2.cpp b/Sorted_Container/2102.Sequentially-Ordinal-Rank-Tracker/2102.Sequentially-Ordinal-Rank-Tracker_v2.cpp similarity index 100% rename from Heap/2102.Sequentially-Ordinal-Rank-Tracker/2102.Sequentially-Ordinal-Rank-Tracker_v2.cpp rename to Sorted_Container/2102.Sequentially-Ordinal-Rank-Tracker/2102.Sequentially-Ordinal-Rank-Tracker_v2.cpp diff --git a/Heap/2102.Sequentially-Ordinal-Rank-Tracker/2102.Sequentially-Ordinal-Rank-Tracker_v3.cpp b/Sorted_Container/2102.Sequentially-Ordinal-Rank-Tracker/2102.Sequentially-Ordinal-Rank-Tracker_v3.cpp similarity index 100% rename from Heap/2102.Sequentially-Ordinal-Rank-Tracker/2102.Sequentially-Ordinal-Rank-Tracker_v3.cpp rename to Sorted_Container/2102.Sequentially-Ordinal-Rank-Tracker/2102.Sequentially-Ordinal-Rank-Tracker_v3.cpp diff --git a/Heap/2102.Sequentially-Ordinal-Rank-Tracker/Readme.md b/Sorted_Container/2102.Sequentially-Ordinal-Rank-Tracker/Readme.md similarity index 100% rename from Heap/2102.Sequentially-Ordinal-Rank-Tracker/Readme.md rename to Sorted_Container/2102.Sequentially-Ordinal-Rank-Tracker/Readme.md diff --git a/Heap/220.Contains-Duplicate-III/220.Contains-Duplicate-III.cpp b/Sorted_Container/220.Contains-Duplicate-III/220.Contains-Duplicate-III.cpp similarity index 100% rename from Heap/220.Contains-Duplicate-III/220.Contains-Duplicate-III.cpp rename to Sorted_Container/220.Contains-Duplicate-III/220.Contains-Duplicate-III.cpp diff --git a/Heap/220.Contains-Duplicate-III/Readme.md b/Sorted_Container/220.Contains-Duplicate-III/Readme.md similarity index 100% rename from Heap/220.Contains-Duplicate-III/Readme.md rename to Sorted_Container/220.Contains-Duplicate-III/Readme.md diff --git a/Heap/2213.Longest-Substring-of-One-Repeating-Character/2213.Longest-Substring-of-One-Repeating-Character.cpp b/Sorted_Container/2213.Longest-Substring-of-One-Repeating-Character/2213.Longest-Substring-of-One-Repeating-Character.cpp similarity index 100% rename from Heap/2213.Longest-Substring-of-One-Repeating-Character/2213.Longest-Substring-of-One-Repeating-Character.cpp rename to Sorted_Container/2213.Longest-Substring-of-One-Repeating-Character/2213.Longest-Substring-of-One-Repeating-Character.cpp diff --git a/Heap/2213.Longest-Substring-of-One-Repeating-Character/Readme.md b/Sorted_Container/2213.Longest-Substring-of-One-Repeating-Character/Readme.md similarity index 100% rename from Heap/2213.Longest-Substring-of-One-Repeating-Character/Readme.md rename to Sorted_Container/2213.Longest-Substring-of-One-Repeating-Character/Readme.md diff --git a/Heap/2276.Count-Integers-in-Intervals/2276.Count-Integers-in-Intervals.cpp b/Sorted_Container/2276.Count-Integers-in-Intervals/2276.Count-Integers-in-Intervals.cpp similarity index 100% rename from Heap/2276.Count-Integers-in-Intervals/2276.Count-Integers-in-Intervals.cpp rename to Sorted_Container/2276.Count-Integers-in-Intervals/2276.Count-Integers-in-Intervals.cpp diff --git a/Heap/2276.Count-Integers-in-Intervals/Readme.md b/Sorted_Container/2276.Count-Integers-in-Intervals/Readme.md similarity index 100% rename from Heap/2276.Count-Integers-in-Intervals/Readme.md rename to Sorted_Container/2276.Count-Integers-in-Intervals/Readme.md diff --git a/Heap/2382.Maximum-Segment-Sum-After-Removals/2382.Maximum-Segment-Sum-After-Removals.cpp b/Sorted_Container/2382.Maximum-Segment-Sum-After-Removals/2382.Maximum-Segment-Sum-After-Removals.cpp similarity index 100% rename from Heap/2382.Maximum-Segment-Sum-After-Removals/2382.Maximum-Segment-Sum-After-Removals.cpp rename to Sorted_Container/2382.Maximum-Segment-Sum-After-Removals/2382.Maximum-Segment-Sum-After-Removals.cpp diff --git a/Heap/2382.Maximum-Segment-Sum-After-Removals/Readme.md b/Sorted_Container/2382.Maximum-Segment-Sum-After-Removals/Readme.md similarity index 100% rename from Heap/2382.Maximum-Segment-Sum-After-Removals/Readme.md rename to Sorted_Container/2382.Maximum-Segment-Sum-After-Removals/Readme.md diff --git a/Sorted_Container/2612.Minimum-Reverse-Operations/2612.Minimum-Reverse-Operations.cpp b/Sorted_Container/2612.Minimum-Reverse-Operations/2612.Minimum-Reverse-Operations.cpp new file mode 100644 index 000000000..d4f90937b --- /dev/null +++ b/Sorted_Container/2612.Minimum-Reverse-Operations/2612.Minimum-Reverse-Operations.cpp @@ -0,0 +1,52 @@ +class Solution { +public: + vector minReverseOperations(int n, int p, vector& banned, int k) + { + setodd; + seteven; + setbanned_set(banned.begin(), banned.end()); + for (int i=0; iq; + q.push(p); + vectorrets(n, -1); + rets[p] = 0; + + int step = 0; + while (!q.empty()) + { + step++; + int len = q.size(); + while (len--) + { + int i = q.front(); + q.pop(); + int L0 = max(0, i-k+1); + int j0 = (2*L0+k-1)-i; + + int L1 = min(n-k, i); + int j1 = (2*L1+k-1)-i; + + set*s; + if (j0%2==0) s = &even; + else s = &odd; + + auto iter = s->lower_bound(j0); + while (iter!=s->end() && *iter<=j1) + { + rets[*iter] = step; + q.push(*iter); + s->erase(iter++); + } + } + } + + return rets; + } +}; diff --git a/Sorted_Container/2612.Minimum-Reverse-Operations/Readme.md b/Sorted_Container/2612.Minimum-Reverse-Operations/Readme.md new file mode 100644 index 000000000..9a8588740 --- /dev/null +++ b/Sorted_Container/2612.Minimum-Reverse-Operations/Readme.md @@ -0,0 +1,9 @@ +### 2612.Minimum-Reverse-Operations + +此题类似于jump game,从起点开始,根据滑窗的不同位置,可以将1移动到多个不同的地方。然后下一轮,再根据滑窗的不同位置,可以将1继续移动到不同的地方。依次类推,可以用BFS求出1到达各个位置所用的最短步数(也就是用了几轮BFS)。 + +我们假设1的初始位置是i,滑窗的左右边界是L和R(且`R-L+1=k`),那么1就可以通过翻转从i到新位置`j = L+R-i = 2*L-i-1`,这是一个仅关于L的函数。考虑滑窗长度固定,且必须包含位置i,所以L的最左边可以到达`i-k+1`,最右边可以到达`i`。此外,L不能越界,即必须在[0,n-1]内,所以L的左边界其实是`L0=max(0,i-k+1)`,右边界其实是`min(i,n-1)`. 于是对应的j的移动范围就是`2*L0-i-1`到`2*L1-i-1`之间,并且随着L从小到大移动,j的变动始终是+2. + +我们在尝试进行BFS的时候,最大的问题就是,我们通过i进行一次revert得到的j会有很多位置(因为滑窗可以运动),其中很多j可能是之前已经遍历过的(也就是已经确定了一个更少的步数就可以到达),我们需要挨个检验的话时间复杂度就会很高。本题有巧解。对于一次revert,j的候选点的编号要么都是同奇数(要么都是偶数),并且在奇数(或者偶数)意义上是连续的!所以我们事先将所有编号是奇数的点作为一个集合odd,将所有编号是偶数的点作为一个集合even,那么这次revert相当于在odd(或者even)上删除一段区间range(删除意味着遍历过)。只要集合是有序的,那么我们就可以很快定位到range在集合里的位置,将range在集合里面的元素都删除。因为每个元素只会在集合里最多被删除一次(以后的range定位都不会涉及已经删除的元素),所以我们可以用近乎线性的时间知道每个元素是在什么时候从集合里删除的,这就是可以到达的最小步数。 + +对于banned里面的元素,只需要实现从odd和even里排除即可。 diff --git a/Sorted_Container/2653.Sliding-Subarray-Beauty/2653.Sliding-Subarray-Beauty.cpp b/Sorted_Container/2653.Sliding-Subarray-Beauty/2653.Sliding-Subarray-Beauty.cpp new file mode 100644 index 000000000..a30b6f08d --- /dev/null +++ b/Sorted_Container/2653.Sliding-Subarray-Beauty/2653.Sliding-Subarray-Beauty.cpp @@ -0,0 +1,54 @@ +class Solution { +public: + vector getSubarrayBeauty(vector& nums, int k, int x) + { + multisetSet1; + multisetSet2; + vectorrets; + + for (int i=0; i nums[i]) + { + Set1.erase(Set1.find(v)); + Set2.insert(v); + Set1.insert(nums[i]); + } + else + { + Set2.insert(nums[i]); + } + } + + if (Set1.size() + Set2.size() == k) + { + int v = *Set1.rbegin(); + rets.push_back(min(v, 0)); + } + + if (i>=k-1) + { + int v = nums[i-k+1]; + auto iter = Set2.find(v); + if (iter!=Set2.end()) + Set2.erase(iter); + else + { + Set1.erase(Set1.find(v)); + if (!Set2.empty()) + { + Set1.insert(*Set2.begin()); + Set2.erase(Set2.begin()); + } + } + } + } + + return rets; + } +}; diff --git a/Sorted_Container/2653.Sliding-Subarray-Beauty/Readme.md b/Sorted_Container/2653.Sliding-Subarray-Beauty/Readme.md new file mode 100644 index 000000000..f9ee488d0 --- /dev/null +++ b/Sorted_Container/2653.Sliding-Subarray-Beauty/Readme.md @@ -0,0 +1,8 @@ +### 2653.Sliding-Subarray-Beauty + +本题如果利用`-50 <= nums[i] <= 50`的条件,那么可以变得很容易。在这里我们只讲更一般的解法。 + +和`Dual PQ`的思路一样,设计两个有序容器,分别是装“最小的x的元素”Set1,和“剩余的元素”Set2。对于新元素nums[i],我们需要操作的步骤是: +1. 判断应该将nums[i]放入Set1还是Set2. 如果比Set1的最大元素还大,就放入Set2;否则就将Set1的最大元素转移到Set2,并将nums[i]放入Set1。 +2. 如果i>=x-1,输出Set1里的最大元素作为答案。 +3. 将nums[i-x+1]从集合中移除。需要判断nums[i-x+1]此时在Set1里还是Set2里。如果是前者的话,需要将Set2里的元素转移一个过去, diff --git a/Sorted_Container/2736.Maximum-Sum-Queries/2736.Maximum-Sum-Queries.cpp b/Sorted_Container/2736.Maximum-Sum-Queries/2736.Maximum-Sum-Queries.cpp new file mode 100644 index 000000000..e9a3b9f92 --- /dev/null +++ b/Sorted_Container/2736.Maximum-Sum-Queries/2736.Maximum-Sum-Queries.cpp @@ -0,0 +1,42 @@ +class Solution { +public: + vector maximumSumQueries(vector& nums1, vector& nums2, vector>& queries) + { + map>>Map; + for (int i=0; irets(queries.size(), -1); + + vector>nums; + for (int i=0; ifirst <= x) + { + set>& s = iter->second; + auto iter2 = s.begin(); + while (iter2 != s.end() && iter2->first <= y) + { + rets[iter2->second] = val; + s.erase(iter2++); + } + if (s.empty()) + Map.erase(iter++); + else + iter++; + } + } + + return rets; + } +}; diff --git a/Sorted_Container/2736.Maximum-Sum-Queries/Readme.md b/Sorted_Container/2736.Maximum-Sum-Queries/Readme.md new file mode 100644 index 000000000..c4afc3b96 --- /dev/null +++ b/Sorted_Container/2736.Maximum-Sum-Queries/Readme.md @@ -0,0 +1,11 @@ +### 2736.Maximum-Sum-Queries + +如果我们将每个query独立地去做,需要暴力地扫所有的nums。一种常见的应对思路是`Off-line Querying`,将query进行某种意义上的排序,通常先解决的query会对后面的query帮助。但是这个思路似乎对本题没有帮助。比如说,将query按照x从大到小排序,随着query的逐一解答,我们可用的nums也会逐渐增多,但是并不能帮我们方便地兼顾“满足关于y的约束”以及“取最大sum”。 + +但是此题还有另外一种对偶的思路,将nums进行某种意义上的排序。我们发现,对于sum最大的num,任何满足x和y约束的所有query,必然会取该sum作为答案,既然找到了答案,那么就可以从待求的query的集合中删除。为了容易找到这些满足约束的query,我们可以将所有query先按照x排序,再按照y排序,构造二层的数据结构。这样,在第一层,任何x小于nums1[j]的query都会入选;然后在对应的第二层,任何y小于nums2[j]的query都可以被选中,标记它们的答案是sum。可以发现,这些被选中的query是分块连续的,我们可以很方便地删除。 + +同理,我们再处理sum为次大的num,删除所有答案是它的query。以此类推。 + +分析时间复杂度:我们令num的个数是m,query的个数是n。我们对于每个num,都会在query集合里删除答案对应是num的query。注意在第二层,每个query只会被访问和删除一次。所以代码核心的时间复杂度是`M+N`. 不过预处理有一个对num和query分别排序的过程。 + +注意,为了提高效率,如果某个二层集合里的query被删空了,务必把它们的一层指针也移除。 diff --git a/Sorted_Container/2907.Maximum-Profitable-Triplets-With-Increasing-Prices-I/2907.Maximum-Profitable-Triplets-With-Increasing-Prices-I_v1.cpp b/Sorted_Container/2907.Maximum-Profitable-Triplets-With-Increasing-Prices-I/2907.Maximum-Profitable-Triplets-With-Increasing-Prices-I_v1.cpp new file mode 100644 index 000000000..8e57d325c --- /dev/null +++ b/Sorted_Container/2907.Maximum-Profitable-Triplets-With-Increasing-Prices-I/2907.Maximum-Profitable-Triplets-With-Increasing-Prices-I_v1.cpp @@ -0,0 +1,144 @@ +class SegTreeNode +{ + public: + SegTreeNode* left = NULL; + SegTreeNode* right = NULL; + int start, end; + int info; // the maximum value of the range + bool tag; + + SegTreeNode(int a, int b, int val) // init for range [a,b] with val + { + tag = 0; + start = a, end = b; + if (a==b) + { + info = val; + return; + } + int mid = (a+b)/2; + if (left==NULL) + { + left = new SegTreeNode(a, mid, val); + right = new SegTreeNode(mid+1, b, val); + info = max(left->info, right->info); // check with your own logic + } + } + + SegTreeNode(int a, int b, vector& val) // init for range [a,b] with the same-size array val + { + tag = 0; + info = 0; + start = a, end = b; + if (a==b) + { + info = val[a]; + return; + } + int mid = (a+b)/2; + if (left==NULL) + { + left = new SegTreeNode(a, mid, val); + right = new SegTreeNode(mid+1, b, val); + info = left->info + right->info; // check with your own logic + } + } + + void pushDown() + { + if (tag==1 && left) + { + left->info = info; + right->info = info; + left->tag = 1; + right->tag = 1; + tag = 0; + } + } + + void updateRange(int a, int b, int val) // set range [a,b] with val + { + if (b < start || a > end ) // not covered by [a,b] at all + return; + if (a <= start && end <=b) // completely covered within [a,b] + { + info = val; + tag = 1; + return; + } + + if (left) + { + pushDown(); + left->updateRange(a, b, val); + right->updateRange(a, b, val); + info = max(left->info, right->info); // write your own logic + } + } + + int queryRange(int a, int b) // query the maximum value within range [a,b] + { + if (b < start || a > end ) + { + return INT_MIN/2; // check with your own logic + } + if (a <= start && end <=b) + { + return info; // check with your own logic + } + + if (left) + { + pushDown(); + int ret = max(left->queryRange(a, b), right->queryRange(a, b)); + info = max(left->info, right->info); // check with your own logic + return ret; + } + + return info; // should not reach here + } + +}; + +class Solution { +public: + int maxProfit(vector& prices, vector& profits) + { + setSet(prices.begin(), prices.end()); + unordered_mapMap; + int m = 0; + for (int x: Set) + { + Map[x] = m; + m++; + } + + SegTreeNode* root1 = new SegTreeNode(0, m-1, -1); // Set the leaf nodes with initVals. + SegTreeNode* root2 = new SegTreeNode(0, m-1, -1); // Set the leaf nodes with initVals. + + int n = prices.size(); + vectorleft(n, -1); + for (int i=0; iqueryRange(0, Map[prices[i]]-1); + if (profits[i] > root1->queryRange(Map[prices[i]], Map[prices[i]])) + root1->updateRange(Map[prices[i]], Map[prices[i]], profits[i]); // set the range [start, end] with val + } + + vectorright(n, -1); + for (int i=n-1; i>=0; i--) + { + right[i] = root2->queryRange(Map[prices[i]]+1, m-1); + if (profits[i] > root2->queryRange(Map[prices[i]], Map[prices[i]])) + root2->updateRange(Map[prices[i]], Map[prices[i]], profits[i]); // set the range [start, end] with val + } + + int ret = -1; + for (int i=0; i& prices, vector& profits) + { + int n = prices.size(); + mapMap; + + vectorleft(n, -1); + for (int i=0; isecond; + } + if (Map.find(prices[i])!=Map.end() && profits[i]<=Map[prices[i]]) + continue; + if (profits[i] <= left[i]) + continue; + Map[prices[i]] = profits[i]; + + iter = Map.upper_bound(prices[i]); + while (iter!=Map.end() && iter->second <= profits[i]) + iter = Map.erase(iter); + } + + Map.clear(); + vectorright(n, -1); + for (int i=n-1; i>=0; i--) + { + auto iter = Map.upper_bound(prices[i]); + if (iter!=Map.end()) + { + right[i] = iter->second; + } + if (Map.find(prices[i])!=Map.end() && profits[i]<=Map[prices[i]]) + continue; + if (profits[i] <= right[i]) + continue; + Map[prices[i]] = profits[i]; + + iter = Map.find(prices[i]); + map::reverse_iterator rit(iter); + // Note rit is actually at a one-position diff before iter. + vectorto_delete; + while (rit!=Map.rend() && rit->second <= profits[i]) + { + int key = rit->first; + rit = next(rit); + to_delete.push_back(key); + } + for (auto key: to_delete) Map.erase(key); + } + + int ret = -1; + for (int i=0; i& nums) + { + int n = nums.size(); + vectorarr(n); + for (int i=0; idp; + LL ret = LLONG_MIN; + + for (int i=0; isecond + nums[i]); + } + else + { + dp[x] = nums[i]; + } + + ret = max(ret, dp[x]); + + iter = dp.find(x); + iter = next(iter); + while (iter!=dp.end() && iter->second <= dp[x]) + iter = dp.erase(iter); + } + + return ret; + } +}; diff --git a/Sorted_Container/2926.Maximum-Balanced-Subsequence-Sum/Readme.md b/Sorted_Container/2926.Maximum-Balanced-Subsequence-Sum/Readme.md new file mode 100644 index 000000000..d8ad9e38f --- /dev/null +++ b/Sorted_Container/2926.Maximum-Balanced-Subsequence-Sum/Readme.md @@ -0,0 +1,31 @@ +### 2926.Maximum-Balanced-Subsequence-Sum + +很明显,变形一下式子就有```nums[i_j] - i_j >= nums[i_(j-1)] - i_(j-1)```. 我们令新数组`arr[i] = nums[i]-i`,我们就是想要在arr里面找一个递增的subsequence,记做{k},使得这个subsequence对应的 {nums[k]} 的和能够最大。 + +很容易看出可以用o(N^2)的dp来做。令dp[i]表示以i为结尾的递增subsequence的最大nums之和。那么就有 +```cpp +for (int i=0; i=B,那么对于任何一个新元素arr[i]=x,我们如果可以把x接在b后面构造子序列,那么显然不如我们把x接在a后面构成子序列更优。这样我们就可以把b从dp里弹出去。 + +所以我们将dp按照key和value都递增的顺序排列后,一个最大的好处出现了。对于任何一个新元素arr[i]=x,我们不需要在dp里遍历所有key小于x的元素,只需要知道恰好小于等于x的key(假设是y),那么就有`dp[x]=dp[y]+nums[i]`。任何key比y小的元素,虽然都可以接上x,但是它们的value并没有dp[y]有优势。 + +当我们确定dp[x]的最优值之后,再将x插入dp里面。记得此时要向后依次检查比x大的那些key,看它们的value(也就是dp值)是否小于dp[x],是的话就将他们弹出去。 + +时间复杂度:对于任何的arr[i]=x,我们在dp里面按照key二分查询恰好小于等于x的key,是log(n)。所以总的时间复杂度是o(NlogN). 有人会问,似乎每个回合,都有线性弹出的操作,但其实总共你最多只会弹出N个元素,这个弹出操作的总是也只是o(N),与循环无关。 + + diff --git a/Sorted_Container/2940.Find-Building-Where-Alice-and-Bob-Can-Meet/2940.Find-Building-Where-Alice-and-Bob-Can-Meet.cpp b/Sorted_Container/2940.Find-Building-Where-Alice-and-Bob-Can-Meet/2940.Find-Building-Where-Alice-and-Bob-Can-Meet.cpp new file mode 100644 index 000000000..a3c30e7b2 --- /dev/null +++ b/Sorted_Container/2940.Find-Building-Where-Alice-and-Bob-Can-Meet/2940.Find-Building-Where-Alice-and-Bob-Can-Meet.cpp @@ -0,0 +1,47 @@ +class Solution { +public: + vector leftmostBuildingQueries(vector& heights, vector>& queries) + { + int n = heights.size(); + + for (int i=0; i&a, vector&b){ + return a[1]>b[1]; + }); + + vectorrets(queries.size()); + int i = n-1; + mapMap; + for (auto& query: queries) + { + int a = query[0], b = query[1], idx = query[2]; + while (i>=b) + { + while (!Map.empty() && heights[i] >= (Map.begin()->first)) + Map.erase(Map.begin()); + Map[heights[i]] = i; + i--; + } + + if (heights[a] < heights[b] || a==b) + { + rets[idx] = b; + continue; + } + + int m = max(heights[a],heights[b]); + auto iter = Map.upper_bound(m); + if (iter!=Map.end()) + rets[idx] = iter->second; + else + rets[idx] = -1; + } + + return rets; + } +}; diff --git a/Sorted_Container/2940.Find-Building-Where-Alice-and-Bob-Can-Meet/Readme.md b/Sorted_Container/2940.Find-Building-Where-Alice-and-Bob-Can-Meet/Readme.md new file mode 100644 index 000000000..614774896 --- /dev/null +++ b/Sorted_Container/2940.Find-Building-Where-Alice-and-Bob-Can-Meet/Readme.md @@ -0,0 +1,11 @@ +### 2940.Find-Building-Where-Alice-and-Bob-Can-Meet + +我们考虑一个query所给的两个位置a和b(其中aheights[y],那么事实上y就可以从容器里移除。因为x更靠近左边且更高,任何满足(a,b)->y的query,必然也满足(a,b)->x且x是比y更优的解(更靠近左边)。这就提示我们,如果我们将heights里的元素按照从右往左的顺序加入有序容器的话,那么就可以用上述的性质:新柱子的加入可以弹出所有比它矮的旧柱子。这就导致了这个有序容器里的柱子不仅是按照height递增的,而且他们对应的index也是递增的。也就是说,有序容器里对于任意的heights[x] i`(for i>b),同时更新容器移除陈旧的值(即那些相比于i,更靠右且更矮的柱子)。然后一个upper_bound解决该query。往容器里添加和删除元素的数据量都是线性的。 + +此外,本题需要处理两个小细节。如果heights[a]==heights[b]以及a==b的这两种情况,直接输出答案b即可。 diff --git a/Sorted_Container/2945.Find-Maximum-Non-decreasing-Array-Length/2945.Find-Maximum-Non-decreasing-Array-Length.cpp b/Sorted_Container/2945.Find-Maximum-Non-decreasing-Array-Length/2945.Find-Maximum-Non-decreasing-Array-Length.cpp new file mode 100644 index 000000000..0d65ac09a --- /dev/null +++ b/Sorted_Container/2945.Find-Maximum-Non-decreasing-Array-Length/2945.Find-Maximum-Non-decreasing-Array-Length.cpp @@ -0,0 +1,39 @@ +using LL = long long; +class Solution { +public: + int findMaximumLength(vector& nums) + { + int n = nums.size(); + nums.insert(nums.begin(), 0); + + vectordp(n+1,-1); + vectorlen(n+1,-1); + vectorpresum(n+1); + for (int i=1; i<=n; i++) + presum[i] = presum[i-1] + nums[i]; + + dp[0] = 0; + len[0] = 0; + int ret = 0; + mapMap; + Map[0] = 0; + for (int i=1; i<=n; i++) + { + auto iter = Map.upper_bound(presum[i]); + if (iter!=Map.begin()) + { + int j = prev(iter)->second; + len[i] = len[j]+1; + dp[i] = presum[i] - presum[j]; + } + + while (!Map.empty() && Map.rbegin()->first >= presum[i]+dp[i]) + Map.erase(prev(Map.end())); + + Map[presum[i]+dp[i]] = i; + } + + return len[n]; + + } +}; diff --git a/Sorted_Container/2945.Find-Maximum-Non-decreasing-Array-Length/Readme.md b/Sorted_Container/2945.Find-Maximum-Non-decreasing-Array-Length/Readme.md new file mode 100644 index 000000000..18971673c --- /dev/null +++ b/Sorted_Container/2945.Find-Maximum-Non-decreasing-Array-Length/Readme.md @@ -0,0 +1,25 @@ +### 2945.Find-Maximum-Non-decreasing-Array-Length + +我们考虑,如果以nums[i]为某段subarray的结尾,那么我们在[1:i]前缀里能够得到的符合条件的最长序列。我们记最后这段subarray sum为dp[i]. 显然,我们需要找到一个位置j,使得dp[j]<=dp[i](其中dp[i]=sum[j+1:i])。为了使得序列尽量长,我们自然希望dp[i]能尽量小,故在所有符合条件的j里,我们一定会找最大的j。因此我们可以有这段dp代码: +```cpp +for (int i=1; i<=n; i++) +{ + LL sum = nums[i]; + int j = i-1; + while (j>=0 && sum < dp[j]) + { + sum += nums[j]; + j--; + } + dp[i] = sum; + len[i] = len[j]+1; +} +return len[n]; +``` +但是这个算法的时间复杂度是o(N^2)。 + +我们将关系式`dp[j]<=dp[i]`改写为`dp[j]<=presum[i]-presum[j]`,即`presum[i] >= presum[j]+dp[j]`. 显然,我们将所有已经得到的那些映射`presum[j]+dp[j] -> j`(因为下标小于i,故是已知量),提前放入一个有序map里,用二分搜索就可以找到对于i而言符合条件的key的范围。那么如何再找到其中最大的j呢?理论上我们需要把这些key都遍历一遍,检查他们的value。但我们会想到一个常见的套路:如果保证这个map不仅是按照key递增有序的、同时也是按照value递增有序的,那么我们就只需要一次二分搜索即可定位恰好小于等于presum[i]的key,那个key所对应的value就是我们想要的最大j,而不需要再遍历寻找value的最大值。 + +根据以上的数据结构,我们就可以轻松求出i所对应的j,以及dp[i]和len[i]。接下来我们需要将`presum[i]+dp[i] -> i`放入map里去。注意,我们依然想要保证map按照key和value都是递增有序的。事实上,我们将`presum[i]+dp[i]`作为key插入map之后,map里比其大的key所对应的value都必然小于i(因为它们是nums里位于i之前的index),这些元素都可以从map里删去。这是因为它们的key既大(不容易让后续的presum接上),value也小(index也靠前),各方面都不及`presum[i]+dp[i] -> i`优秀,今后注定不会被用到。将他们弹出之后,我们发现,map依然保持了我们想要的双递增的性质。 + +故这样的算法时间复杂度就是o(nlogn). diff --git a/Heap/295.Find-Median-from-Data-Stream/295.Find-Median-from-Data-Stream.cpp b/Sorted_Container/295.Find-Median-from-Data-Stream/295.Find-Median-from-Data-Stream.cpp similarity index 100% rename from Heap/295.Find-Median-from-Data-Stream/295.Find-Median-from-Data-Stream.cpp rename to Sorted_Container/295.Find-Median-from-Data-Stream/295.Find-Median-from-Data-Stream.cpp diff --git a/Heap/295.Find-Median-from-Data-Stream/295.Find-Median-from-Data-Stream_v2.cpp b/Sorted_Container/295.Find-Median-from-Data-Stream/295.Find-Median-from-Data-Stream_v2.cpp similarity index 100% rename from Heap/295.Find-Median-from-Data-Stream/295.Find-Median-from-Data-Stream_v2.cpp rename to Sorted_Container/295.Find-Median-from-Data-Stream/295.Find-Median-from-Data-Stream_v2.cpp diff --git a/Heap/295.Find-Median-from-Data-Stream/Readme.md b/Sorted_Container/295.Find-Median-from-Data-Stream/Readme.md similarity index 100% rename from Heap/295.Find-Median-from-Data-Stream/Readme.md rename to Sorted_Container/295.Find-Median-from-Data-Stream/Readme.md diff --git a/Sorted_Container/3013.Divide-an-Array-Into-Subarrays-With-Minimum-Cost-II/3013.Divide-an-Array-Into-Subarrays-With-Minimum-Cost-II.cpp b/Sorted_Container/3013.Divide-an-Array-Into-Subarrays-With-Minimum-Cost-II/3013.Divide-an-Array-Into-Subarrays-With-Minimum-Cost-II.cpp new file mode 100644 index 000000000..bc7aaec45 --- /dev/null +++ b/Sorted_Container/3013.Divide-an-Array-Into-Subarrays-With-Minimum-Cost-II/3013.Divide-an-Array-Into-Subarrays-With-Minimum-Cost-II.cpp @@ -0,0 +1,58 @@ +using LL = long long; +class Solution { +public: + long long minimumCost(vector& nums, int k, int dist) + { + int n = nums.size(); + + multisetSet1; + multisetSet2; + + LL sum = 0; + LL ret = LLONG_MAX; + + k--; + + for (int i=1; i=dist+1) + { + ret = min(ret, sum); + + int t = nums[i-dist]; + if (Set2.find(t)!=Set2.end()) + Set2.erase(Set2.find(t)); + else + { + Set1.erase(Set1.find(t)); + sum -= t; + if (!Set2.empty()) + { + Set1.insert(*Set2.begin()); + sum += *Set2.begin(); + Set2.erase(Set2.begin()); + } + } + } + } + + return ret + nums[0]; + + } +}; diff --git a/Sorted_Container/3013.Divide-an-Array-Into-Subarrays-With-Minimum-Cost-II/Readme.md b/Sorted_Container/3013.Divide-an-Array-Into-Subarrays-With-Minimum-Cost-II/Readme.md new file mode 100644 index 000000000..e647631d7 --- /dev/null +++ b/Sorted_Container/3013.Divide-an-Array-Into-Subarrays-With-Minimum-Cost-II/Readme.md @@ -0,0 +1,11 @@ +### 3013.Divide-an-Array-Into-Subarrays-With-Minimum-Cost-II + +本题的本质就是从nums[1]开始,寻找一个长度为dist+1的滑窗,使得里面top k smallest的元素和最小。 + +对于求top k smallest,有常规的套路,就是用两个multiset。将滑窗内的top k smallest放入Set1,其余元素放入Set2. + +当滑窗移动时,需要加入进入的新元素k。我们需要考察是否能进入Set1(与尾元素比较)。如果能,那么需要将Set1的尾元素取出,放入Set2. 否则,将k放入Set2。 + +同理,当滑窗移动时,我们需要移除离开滑窗的旧元素k。我们考察k是否是Set1的元素。如果是,那么我们需要将Set1的k取出,同时将Set2的首元素加入进Set1里。 + +以上操作不断更新Set1的时候(加入元素和弹出元素),同时维护一个Set1元素的和变量sum,找到全局最小值即可。 diff --git a/Heap/352.Data-Stream-as-Disjoint-Intervals/352.Data Stream as Disjoint Intervals.cpp b/Sorted_Container/352.Data-Stream-as-Disjoint-Intervals/352.Data Stream as Disjoint Intervals.cpp similarity index 100% rename from Heap/352.Data-Stream-as-Disjoint-Intervals/352.Data Stream as Disjoint Intervals.cpp rename to Sorted_Container/352.Data-Stream-as-Disjoint-Intervals/352.Data Stream as Disjoint Intervals.cpp diff --git a/Heap/352.Data-Stream-as-Disjoint-Intervals/352.Data-Stream-as-Disjoint-Intervals-v2.cpp b/Sorted_Container/352.Data-Stream-as-Disjoint-Intervals/352.Data-Stream-as-Disjoint-Intervals-v2.cpp similarity index 100% rename from Heap/352.Data-Stream-as-Disjoint-Intervals/352.Data-Stream-as-Disjoint-Intervals-v2.cpp rename to Sorted_Container/352.Data-Stream-as-Disjoint-Intervals/352.Data-Stream-as-Disjoint-Intervals-v2.cpp diff --git a/Heap/352.Data-Stream-as-Disjoint-Intervals/352.Data-Stream-as-Disjoint-Intervals-v3.cpp b/Sorted_Container/352.Data-Stream-as-Disjoint-Intervals/352.Data-Stream-as-Disjoint-Intervals-v3.cpp similarity index 100% rename from Heap/352.Data-Stream-as-Disjoint-Intervals/352.Data-Stream-as-Disjoint-Intervals-v3.cpp rename to Sorted_Container/352.Data-Stream-as-Disjoint-Intervals/352.Data-Stream-as-Disjoint-Intervals-v3.cpp diff --git a/Heap/352.Data-Stream-as-Disjoint-Intervals/Readme.md b/Sorted_Container/352.Data-Stream-as-Disjoint-Intervals/Readme.md similarity index 100% rename from Heap/352.Data-Stream-as-Disjoint-Intervals/Readme.md rename to Sorted_Container/352.Data-Stream-as-Disjoint-Intervals/Readme.md diff --git a/Heap/363.Max-Sum-of-Rectangle-No-Larger-Than-K/363.Max-Sum-of-Rectangle-No-Larger-Than-K.cpp b/Sorted_Container/363.Max-Sum-of-Rectangle-No-Larger-Than-K/363.Max-Sum-of-Rectangle-No-Larger-Than-K.cpp similarity index 100% rename from Heap/363.Max-Sum-of-Rectangle-No-Larger-Than-K/363.Max-Sum-of-Rectangle-No-Larger-Than-K.cpp rename to Sorted_Container/363.Max-Sum-of-Rectangle-No-Larger-Than-K/363.Max-Sum-of-Rectangle-No-Larger-Than-K.cpp diff --git a/Heap/363.Max-Sum-of-Rectangle-No-Larger-Than-K/Readme.md b/Sorted_Container/363.Max-Sum-of-Rectangle-No-Larger-Than-K/Readme.md similarity index 100% rename from Heap/363.Max-Sum-of-Rectangle-No-Larger-Than-K/Readme.md rename to Sorted_Container/363.Max-Sum-of-Rectangle-No-Larger-Than-K/Readme.md diff --git a/Sorted_Container/3672.Sum-of-Weighted-Modes-in-Subarrays/3672.Sum-of-Weighted-Modes-in-Subarrays.cpp b/Sorted_Container/3672.Sum-of-Weighted-Modes-in-Subarrays/3672.Sum-of-Weighted-Modes-in-Subarrays.cpp new file mode 100644 index 000000000..5d8ff6837 --- /dev/null +++ b/Sorted_Container/3672.Sum-of-Weighted-Modes-in-Subarrays/3672.Sum-of-Weighted-Modes-in-Subarrays.cpp @@ -0,0 +1,33 @@ +class Solution { +public: + long long modeWeight(vector& nums, int k) { + map>freq2val; + unordered_mapval2freq; + long long ret = 0; + + for (int i=0; i=k-1) { + int x = *(freq2val.rbegin()->second.begin()); + ret += (long long)x*val2freq[x]; + + x = nums[i-k+1]; + int f = val2freq[x]; + freq2val[f].erase(x); + if (freq2val[f].empty()) + freq2val.erase(f); + val2freq[x] = f-1; + if (f!=1) + freq2val[f-1].insert(x); + } + } + return ret; + } +}; diff --git a/Sorted_Container/3672.Sum-of-Weighted-Modes-in-Subarrays/Readme.md b/Sorted_Container/3672.Sum-of-Weighted-Modes-in-Subarrays/Readme.md new file mode 100644 index 000000000..6b1b98d65 --- /dev/null +++ b/Sorted_Container/3672.Sum-of-Weighted-Modes-in-Subarrays/Readme.md @@ -0,0 +1,31 @@ +### 3672.Sum-of-Weighted-Modes-in-Subarrays + +比较容易想到设计两个容器: +```cpp +map>freq2val; +unordered_mapval2freq; +``` +freq2val[f]里面放所有频率为f的元素(按照从小到大排序)。这样每个sliding window,我们就可以用`*(freq2val.rbegin()->second.begin())`找到其中的众数。val2[freq]顾名思义就是每种元素的频次。 + +每次我们加入一个新元素x,可以查到它的f。于是小心地更新这两个容器即可 +```cpp +// 先删除{x,f} +freq2val[f].erase(x); +if (freq2val[f].empty()) + freq2val.erase(f); +// 再加入{x,f+1} +val2freq[x]=f+1; +freq2val[f+1].insert(x); +``` +同理,每次我们删除一个旧元素x,可以查到它的f。也是小心地更新这两个容器即可 +```cpp +// 先删除{x,f} +freq2val[f].erase(x); +if (freq2val[f].empty()) + freq2val.erase(f); +// 再加入{x,f-1} +val2freq[x] = f-1; +if (f!=1) + freq2val[f-1].insert(x); +``` +关键就是及时清理freq2val里没有值的key(即空频次),保证freq2val的结尾元素是有意义的。 diff --git a/Heap/480.Sliding-Window-Median/480.Sliding-Window-Median.cpp b/Sorted_Container/480.Sliding-Window-Median/480.Sliding-Window-Median.cpp similarity index 100% rename from Heap/480.Sliding-Window-Median/480.Sliding-Window-Median.cpp rename to Sorted_Container/480.Sliding-Window-Median/480.Sliding-Window-Median.cpp diff --git a/Heap/480.Sliding-Window-Median/Readme.md b/Sorted_Container/480.Sliding-Window-Median/Readme.md similarity index 100% rename from Heap/480.Sliding-Window-Median/Readme.md rename to Sorted_Container/480.Sliding-Window-Median/Readme.md diff --git a/Heap/632.Smallest-Range-Covering-Elements-from-K-Lists/632.Smallest-Range-Covering-Elements-from-K-Lists.cpp b/Sorted_Container/632.Smallest-Range-Covering-Elements-from-K-Lists/632.Smallest-Range-Covering-Elements-from-K-Lists.cpp similarity index 100% rename from Heap/632.Smallest-Range-Covering-Elements-from-K-Lists/632.Smallest-Range-Covering-Elements-from-K-Lists.cpp rename to Sorted_Container/632.Smallest-Range-Covering-Elements-from-K-Lists/632.Smallest-Range-Covering-Elements-from-K-Lists.cpp diff --git a/Heap/632.Smallest-Range-Covering-Elements-from-K-Lists/Readme.md b/Sorted_Container/632.Smallest-Range-Covering-Elements-from-K-Lists/Readme.md similarity index 100% rename from Heap/632.Smallest-Range-Covering-Elements-from-K-Lists/Readme.md rename to Sorted_Container/632.Smallest-Range-Covering-Elements-from-K-Lists/Readme.md diff --git a/Heap/729.My-Calendar-I/729.My-Calendar-I.cpp b/Sorted_Container/729.My-Calendar-I/729.My-Calendar-I.cpp similarity index 100% rename from Heap/729.My-Calendar-I/729.My-Calendar-I.cpp rename to Sorted_Container/729.My-Calendar-I/729.My-Calendar-I.cpp diff --git a/Heap/729.My-Calendar-I/Readme.md b/Sorted_Container/729.My-Calendar-I/Readme.md similarity index 100% rename from Heap/729.My-Calendar-I/Readme.md rename to Sorted_Container/729.My-Calendar-I/Readme.md diff --git a/Heap/855.Exam-Room/855.Exam-Room.cpp b/Sorted_Container/855.Exam-Room/855.Exam-Room.cpp similarity index 100% rename from Heap/855.Exam-Room/855.Exam-Room.cpp rename to Sorted_Container/855.Exam-Room/855.Exam-Room.cpp diff --git a/Heap/855.Exam-Room/Readme.md b/Sorted_Container/855.Exam-Room/Readme.md similarity index 100% rename from Heap/855.Exam-Room/Readme.md rename to Sorted_Container/855.Exam-Room/Readme.md diff --git a/Heap/975.Odd-Even-Jump/975.Odd-Even-Jump.cpp b/Sorted_Container/975.Odd-Even-Jump/975.Odd-Even-Jump.cpp similarity index 100% rename from Heap/975.Odd-Even-Jump/975.Odd-Even-Jump.cpp rename to Sorted_Container/975.Odd-Even-Jump/975.Odd-Even-Jump.cpp diff --git a/Heap/975.Odd-Even-Jump/Readme.md b/Sorted_Container/975.Odd-Even-Jump/Readme.md similarity index 100% rename from Heap/975.Odd-Even-Jump/Readme.md rename to Sorted_Container/975.Odd-Even-Jump/Readme.md diff --git a/Stack/032.Longest-Valid-Parentheses/Readme.md b/Stack/032.Longest-Valid-Parentheses/Readme.md index b7595b214..8946d5401 100644 --- a/Stack/032.Longest-Valid-Parentheses/Readme.md +++ b/Stack/032.Longest-Valid-Parentheses/Readme.md @@ -8,6 +8,6 @@ 由此,if possible,我们可以为每一个右括号i,寻找与之匹配的左括号j的位置(即离它左边最近的、可以匹配的左括号)。并且我们可以确定,[j:i]这对括号内的字符肯定也是已经正确匹配了的。 -但是[j:i]就一定是以j结尾的最长的合法字串了吗?不一定。此时观察,将栈顶元素j退栈“对消”之后,此时新的栈顶元素对应的位置并不一定是与j相邻的。中间这段“空隙”意味着什么呢?对,这段“空隙”是之前已经退栈了的其他合法字符串。所以我们可以在区间[j:i]的左边再加上这段长度。因此,真正的“以j结尾的最长的合法字串”的长度是```i - Stack.top()```。注意stack存放的是所有字符的index。 +但是[j:i]就一定是以i结尾的最长的合法字串了吗?不一定。此时观察,将栈顶元素j退栈“对消”之后,此时新的栈顶元素对应的位置并不一定是与j相邻的。中间这段“空隙”意味着什么呢?对,这段“空隙”是之前已经退栈了的其他合法字符串。所以我们可以在区间[j:i]的左边再加上这段长度。因此,真正的“以j结尾的最长的合法字串”的长度是```i - Stack.top()```。注意stack存放的是所有字符的index。 [Leetcode Link](https://leetcode.com/problems/longest-valid-parentheses) diff --git a/Stack/1504.Count-Submatrices-With-All-Ones/1504.Count-Submatrices-With-All-Ones.cpp b/Stack/1504.Count-Submatrices-With-All-Ones/1504.Count-Submatrices-With-All-Ones.cpp new file mode 100644 index 000000000..f4a7699bb --- /dev/null +++ b/Stack/1504.Count-Submatrices-With-All-Ones/1504.Count-Submatrices-With-All-Ones.cpp @@ -0,0 +1,35 @@ +class Solution { +public: + int numSubmat(vector>& mat) + { + int m = mat.size(), n = mat[0].size(); + vectorh(n+1, 0); + + int ret = 0; + + for (int i=0; istk; + stk.push(0); + int c = 0; + for (int j=1; j<=n; j++) + { + while (!stk.empty() && h[stk.top()] > h[j]) + { + int p1 = stk.top(); + stk.pop(); + int p2 = stk.top(); + c = c - (p1-p2)*(h[p1]-h[j]); + } + c += h[j]; + ret += c; + stk.push(j); + } + } + + return ret; + } +}; diff --git a/Stack/1504.Count-Submatrices-With-All-Ones/Readme.md b/Stack/1504.Count-Submatrices-With-All-Ones/Readme.md new file mode 100644 index 000000000..95f8c2de8 --- /dev/null +++ b/Stack/1504.Count-Submatrices-With-All-Ones/Readme.md @@ -0,0 +1,15 @@ +### 1504.Count-Submatrices-With-All-Ones + +此题的数据量非常小,可以o(MMN)暴力解决。但是有更巧妙的o(MN)做法。 + +和85相同的技巧,我们逐行处理,更新以第i行为底座的histogram。然后逐列处理histogram里面的柱子,我们试图用单调栈来判定:以第j根柱子为右边界的矩形有多少个。 + +我们想象,如果histogram里面的柱子都是递增的。假设以第j-1根柱子为右边界的矩形有count[j-1]个,并且第j根柱子比第j-1根的高,那么将这些count[j-1]个矩形向右延伸靠到第j根柱子上的话,都会变成有效的count[j]。此外,我们只需要再计数仅包括第j根柱子本身的矩形,故`count[j] = count[j-1] + nums[j]`. + +当我们如果遇到第j根柱子矮于第j-1根柱子呢?那么并不是所有count[j-1]个矩形都可以继承并延伸成为j的一部分。我们需要退回那些“超高”的部分,即高度差为`nums[j]-nums[j-1]`的这部分矩形我们要吐出去,剩余的矩形才能继承成为j的一部分。此外,我们发现,如果nums[j-2]也高于nums[j]的话,这样的回吐过程还要继续进行下去。 + +于是这一切都提示我们用单调栈。 + +使用单调栈时特别要注意,假设栈顶元素的index是p1,次栈顶元素的index是p2,p1与p2不一定连着的。这是因为之前p1将(p2,p1)之间的元素都逼出栈了。但这并不是说中间没有柱子了,而是意味着,(p2,p1)之间存在着与p1等高的柱子。所以p1退栈的时候,需要退出的矩形数目其实是`(p1-p2)*(nums[p1]-nums[j])`. + +退栈之后记得别忘了算上nums[j](也就是仅包含第j根柱子的矩形),并将j压入栈顶。 diff --git a/Stack/2751.Robot-Collisions/2751.Robot-Collisions.cpp b/Stack/2751.Robot-Collisions/2751.Robot-Collisions.cpp new file mode 100644 index 000000000..dcdfbe400 --- /dev/null +++ b/Stack/2751.Robot-Collisions/2751.Robot-Collisions.cpp @@ -0,0 +1,54 @@ +class Solution { +public: + vector survivedRobotsHealths(vector& positions, vector& healths, string directions) + { + int n = positions.size(); + vector>robots; + for (int i=0; i>Stack; + for (int i=0; i 0) + Stack.push_back(robots[i]); + } + } + + sort(Stack.begin(), Stack.end(), [](vector&a, vector&b){return a[3]rets; + for (int i=0; i>& nodes) + { + stackstk; + stk.push(nodes[0][0]); + for (int i=1; i& nums) + { + int n = nums.size(); + vector>arr; + int ret = 0; + for (int i=0; isecond; + ret = max(ret, i-k+1); + } + if (arr.empty() || nums[i]>arr.back().first) + arr.push_back({nums[i], i}); + } + return ret; + } +}; diff --git a/Stack/2863.Maximum-Length-of-Semi-Decreasing-Subarrays/2863.Maximum-Length-of-Semi-Decreasing-Subarrays_v2.cpp b/Stack/2863.Maximum-Length-of-Semi-Decreasing-Subarrays/2863.Maximum-Length-of-Semi-Decreasing-Subarrays_v2.cpp new file mode 100644 index 000000000..af933ae09 --- /dev/null +++ b/Stack/2863.Maximum-Length-of-Semi-Decreasing-Subarrays/2863.Maximum-Length-of-Semi-Decreasing-Subarrays_v2.cpp @@ -0,0 +1,24 @@ +class Solution { +public: + int maxSubarrayLength(vector& nums) + { + int n = nums.size(); + stackstk; + for (int i=0; inums[stk.top()]) + stk.push(i); + } + + int ret = 0; + for (int i=n-1; i>=0; i--) + { + while (!stk.empty() && nums[stk.top()] > nums[i]) + { + ret = max(ret, i-stk.top()+1); + stk.pop(); + } + } + return ret; + } +}; diff --git a/Stack/2863.Maximum-Length-of-Semi-Decreasing-Subarrays/Readme.md b/Stack/2863.Maximum-Length-of-Semi-Decreasing-Subarrays/Readme.md new file mode 100644 index 000000000..63ad38d50 --- /dev/null +++ b/Stack/2863.Maximum-Length-of-Semi-Decreasing-Subarrays/Readme.md @@ -0,0 +1,21 @@ +### 2863.Maximum-Length-of-Semi-Decreasing-Subarrays + +此题和 `962.Maximum-Width-Ramp` 一模一样。 + +#### 解法1 +我们维护一个递增的“数组”arr,这是为了方便二分。对于新元素nums[i],我们用二分法在arr里找到第一个大于nums[i]的元素nums[j],于是对于i而言,它的最大跨度就是`i-j+1`. + +如果nums[i]大于数组的尾元素,就加入arr。反之,那么我们就再不用考虑i,这是因为它“又小又晚”,不会为后续的元素带来更大的跨度。 + +#### 解法2 +我们先构造一个单调递增的栈,注意我们从不退栈。方法如下: +```cpp +for (int i=0; inums[stk.top()]) + stk.push(i); +} +``` +这样的做法是我们希望尽量收录更早且更高的元素。任何更晚出现的、更小的元素,都不可能成为最优配对(i,j)中的i。 + +然后我们从后往前遍历nums[i],我们持续退栈直至找到恰好比nums[i]大的元素j。退掉的元素不可惜,因为如果j与i是一个合法配对,那么任何大于j的元素都不会与小于i的元素组成更好的配对。 diff --git a/Stack/2866.Beautiful-Towers-II/2866.Beautiful-Towers-II.cpp b/Stack/2866.Beautiful-Towers-II/2866.Beautiful-Towers-II.cpp new file mode 100644 index 000000000..299b4d379 --- /dev/null +++ b/Stack/2866.Beautiful-Towers-II/2866.Beautiful-Towers-II.cpp @@ -0,0 +1,52 @@ +using LL = long long; +class Solution { +public: + long long maximumSumOfHeights(vector& maxHeights) + { + maxHeights.insert(maxHeights.begin(), 0); + maxHeights.push_back(0); + + vectorleft = helper(maxHeights); + + reverse(maxHeights.begin(), maxHeights.end()); + vectorright = helper(maxHeights); + reverse(right.begin(), right.end()); + + reverse(maxHeights.begin(), maxHeights.end()); + + LL ret = 0; + + for (int i=0; ihelper(vectormaxHeights) + { + int n = maxHeights.size(); + stackstk; + vectorarr(n); + LL sum = 0; + stk.push(0); + arr[i] = 0; + for (int i=1; i maxHeights[i]`时,我们令 +```cpp +p1 = stk.top(); +stk.pop(); +p2 = stk.top(); +``` +我们对栈顶元素p1退栈时,要“回退”的面积其实是`(p1-p2)*maxHeights[p1]`,也就是说,之前[p2+1, p1]这一段最理想的状态是都与maxHeights[p1]平齐,这样既不超过p1的约束,也最大化了总面积。 + +同理,退完p1之后,如果发现`maxHeights[p2] > maxHeights[i]`时,我们依然要继续退栈,同上,退出一段与maxHeights[p2]平齐的高度。 + +当所有的回退完成之后,我们保证了maxHeights[i]高于当前的栈顶元素(假设为pp),那么意味着从[pp+1,i]这段区间我们都可以最大化设置为maxHeights[i]。 + +此时的总面积就是从左往右截止到i位置,为了保持递增关系,能够得到的最大面积,记做left[i]. + +同理,我们将上面的过程反过来,从右往左做一遍,得到从右往左截止到i位置,为了保持递增关系,能够得到的最大面积,记做right[i]. + +那么以i为peak的最大总面积就是`area[i] = left[i]+right[i]-maxHeights[i]`. + +我们在所有的area[i]取全局最大值即可。 + +此题的解法和`084.Largest-Rectangle-in-Histogram`非常类似。 + diff --git a/Stack/3113.Find-the-Number-of-Subarrays-Where-Boundary-Elements-Are-Maximum/3113.Find-the-Number-of-Subarrays-Where-Boundary-Elements-Are-Maximum.cpp b/Stack/3113.Find-the-Number-of-Subarrays-Where-Boundary-Elements-Are-Maximum/3113.Find-the-Number-of-Subarrays-Where-Boundary-Elements-Are-Maximum.cpp new file mode 100644 index 000000000..413f3da6b --- /dev/null +++ b/Stack/3113.Find-the-Number-of-Subarrays-Where-Boundary-Elements-Are-Maximum/3113.Find-the-Number-of-Subarrays-Where-Boundary-Elements-Are-Maximum.cpp @@ -0,0 +1,29 @@ +using LL = long long; +class Solution { +public: + long long numberOfSubarrays(vector& nums) + { + int n = nums.size(); + vectorprevGreater(n, -1); + + stackstk; + for (int i=0; i>Map; + LL ret = 0; + for (int i=0; i& nums) { + int n = nums.size(); + vectornextGreater(n, -1); + vectorprevGreater(n, -1); + vectorst; + for (int i=0; i=0; i--) { + while (!st.empty() && nums[st.back()]=2) ret++; + if (nextGreater[i]!=-1 && nextGreater[i]-i>=2) ret++; + } + return ret; + } +}; diff --git a/Stack/3676.Count-Bowl-Subarrays/Readme.md b/Stack/3676.Count-Bowl-Subarrays/Readme.md new file mode 100644 index 000000000..066f63299 --- /dev/null +++ b/Stack/3676.Count-Bowl-Subarrays/Readme.md @@ -0,0 +1,7 @@ +### 3676.Count-Bowl-Subarrays + +非常有趣的题目。 + +我们很容易想到,对于每个nums[i]考虑其作为一个端点时,另一个端点应该在哪些位置。我们不妨认为nums[i]是左边且较低的端点,那么我们很容易找到对应的next greater element,比如说j,这是下一个可以作为右端点的位置,而这恰恰也是它所对应的唯一的右端点。如果再往右寻找右端点,那么j处在bowl内部就高于了左端点,不符合条件。 + +于是我们就可以得出结论,对于每个nums[i],只有唯一的next greater element可以配对为右边且更高的端点。同理,对于每个nums[i],只有唯一的prev greater element可以配对为左边且更高的端点。这样我们就枚举除了所有的bowl的形状。 diff --git a/Stack/962.Maximum-Width-Ramp/Readme.md b/Stack/962.Maximum-Width-Ramp/Readme.md index badfd8ada..35c823a92 100644 --- a/Stack/962.Maximum-Width-Ramp/Readme.md +++ b/Stack/962.Maximum-Width-Ramp/Readme.md @@ -20,5 +20,7 @@ ``` 绝妙的下一步是:从后往前依次考察A,对于每个A[i],我们从栈尾依次弹出元素直至遇到一个恰好小于等于A[i]的索引j,那么(j,i)就是关乎A[i]我们能得到的最宽的配对。至于那些已经弹出栈的元素,其实丢了就丢了,并不会对答案有更多的贡献。比如说,j+1和i-1即使配对成功,也不能超越(j,i)的宽度。这样将A从后往前扫一遍,就能找到最宽的配对。 +类似的题有 `2863. Maximum Length of Semi-Decreasing Subarrays` -[Leetcode Link](https://leetcode.com/problems/maximum-width-ramp) \ No newline at end of file + +[Leetcode Link](https://leetcode.com/problems/maximum-width-ramp) diff --git a/String/028.Implement-strStr/028.Implement-strStr-KMP.cpp b/String/028.Implement-strStr/028.Implement-strStr-KMP.cpp index 39ba57963..7de750598 100644 --- a/String/028.Implement-strStr/028.Implement-strStr-KMP.cpp +++ b/String/028.Implement-strStr/028.Implement-strStr-KMP.cpp @@ -18,7 +18,7 @@ class Solution { for (int i=1; i0 && haystack[i]!=needle[j]) + while (j>0 && (j==needle.size() || haystack[i]!=needle[j])) j = suf[j-1]; dp[i] = j + (haystack[i]==needle[j]); if (dp[i]==needle.size()) diff --git a/String/2781.Length-of-the-Longest-Valid-Substring/2781.Length-of-the-Longest-Valid-Substring.cpp b/String/2781.Length-of-the-Longest-Valid-Substring/2781.Length-of-the-Longest-Valid-Substring.cpp new file mode 100644 index 000000000..97c4358c1 --- /dev/null +++ b/String/2781.Length-of-the-Longest-Valid-Substring/2781.Length-of-the-Longest-Valid-Substring.cpp @@ -0,0 +1,50 @@ +using LL = long long; +class Solution { + unordered_setSet; + unordered_map>Map; +public: + int longestValidSubstring(string word, vector& forbidden) + { + for (auto& s: forbidden) + { + LL code = 0; + for (auto ch: s) + code = (code << 5) + (ch-'a'+1); + Set.insert(code); + } + + for (int len = 1; len<=10; len++) + helper(word, len); + + int n = word.size(); + int rightBound = n; + int ret = 0; + for (int i=n-1; i>=0; i--) + { + if (Map.find(i)!=Map.end()) + { + for (int j: Map[i]) + rightBound = min(rightBound, j); + } + ret = max(ret, rightBound-i); + } + return ret; + + } + + void helper(string&word, int len) + { + int n = word.size(); + LL code = 0; + for (int i=0; i=len) + code &= (1LL<<(5*(len-1)))-1; + + code = (code << 5) + word[i]-'a'+1; + + if (i>=len-1 && Set.find(code)!=Set.end()) + Map[i-len+1].push_back(i); + } + } +}; diff --git a/String/2781.Length-of-the-Longest-Valid-Substring/Readme.md b/String/2781.Length-of-the-Longest-Valid-Substring/Readme.md new file mode 100644 index 000000000..ed33e9eb0 --- /dev/null +++ b/String/2781.Length-of-the-Longest-Valid-Substring/Readme.md @@ -0,0 +1,9 @@ +### 2781.Length-of-the-Longest-Valid-Substring + +注意到forbidden里面的字符串都很短,长度不超过10,我们可以用一个26进制的长度来编码的话,二进制的bit只需要最多50位,故一个64位长整形就能满足。这样,我们在word里走10遍不同长度的固定滑窗,每个滑窗对应一个编码,如果与forbidden里的编码相同,那么就意味着这个滑窗对应一个forbidden里的字符串。这样,我们就可以高效地知道word里所有的forbidden字串的位置。 + +接下来要做的就是在word里找到一个最长的区间,里面不包括任何完整的forbidden字串。这是一个典型的区间问题。我们考虑这样一个子问题:以i为右端点的区间,其左端点最远可以到哪里能符合条件?我们注意到,任何右边界超过i的forbidden子串都不会影响结果(他们肯定不会被完整包含)。所以我们只需要考虑所有右端点不超过i的forbidden子串,为了不完整包括它们中的任何一个,我们显然会取这些forbidden子串的所有左边界里的最大值j,这样[j+1,i]的区间就会符合条件。 + +于是我们可以归纳出算法。将所有forbidden子串按照右边界排序。这样我们从左往右扫描位置i时,就可以将顺次将所有右边界不超过i的子串加入集合,并从中更新它们左边界的最大值j。于是[j+1,i]就对应了以i为右端点、且不包括任何完整forbidden子串的最大区间。 + +同理,如果将所有forbidden子串按照左边界排序,也可以适用同样的算法,只不过从右往左扫一遍。 diff --git a/String/3008.Find-Beautiful-Indices-in-the-Given-Array-II/3008.Find-Beautiful-Indices-in-the-Given-Array-II.cpp b/String/3008.Find-Beautiful-Indices-in-the-Given-Array-II/3008.Find-Beautiful-Indices-in-the-Given-Array-II.cpp new file mode 100644 index 000000000..4c65f9dd0 --- /dev/null +++ b/String/3008.Find-Beautiful-Indices-in-the-Given-Array-II/3008.Find-Beautiful-Indices-in-the-Given-Array-II.cpp @@ -0,0 +1,64 @@ +class Solution { + vector strStr(string haystack, string needle) + { + vectorrets; + + int n = haystack.size(); + int m = needle.size(); + if (m==0) return {}; + if (n==0) return {}; + + vector suf = preprocess(needle); + + vectordp(n,0); + dp[0] = (haystack[0]==needle[0]); + if (m==1 && dp[0]==1) + rets.push_back(0); + + + for (int i=1; i0 && haystack[i]!=needle[j]) + j = suf[j-1]; + dp[i] = j + (haystack[i]==needle[j]); + if (dp[i]==needle.size()) + rets.push_back(i-needle.size()+1); + } + return rets; + } + + vector preprocess(string s) + { + int n = s.size(); + vectordp(n,0); + for (int i=1; i=1 && s[j]!=s[i]) + { + j = dp[j-1]; + } + dp[i] = j + (s[j]==s[i]); + } + return dp; + } + +public: + vector beautifulIndices(string s, string a, string b, int k) + { + vector A = strStr(s,a); + vector B = strStr(s,b); + + vectorrets; + for (int i:A) + { + auto iter1 = lower_bound(B.begin(), B.end(), i-k); + auto iter2 = upper_bound(B.begin(), B.end(), i+k); + if (iter2-iter1>=1) + rets.push_back(i); + } + return rets; + + } +}; diff --git a/String/3008.Find-Beautiful-Indices-in-the-Given-Array-II/Readme.md b/String/3008.Find-Beautiful-Indices-in-the-Given-Array-II/Readme.md new file mode 100644 index 000000000..4b821348d --- /dev/null +++ b/String/3008.Find-Beautiful-Indices-in-the-Given-Array-II/Readme.md @@ -0,0 +1,5 @@ +### 3008.Find-Beautiful-Indices-in-the-Given-Array-II + +很显然,我们只需要找出a在s中出现的所有位置pos1,以及b在s中出现的所有位置pos2。这个用KMP的模板即可。 + +对于pos1中的每个位置i,我们只需要查找`i-k`在pos2里的位置(lower_bound,第一个大于等于该数的迭代器),以及`i+k`在pos2里的位置(upper_bound,第一个大于该数的迭代器),两个位置之差即代表有多少pos2的元素位于[i-k, i+k]之间。 diff --git a/String/3023.Find-Pattern-in-Infinite-Stream-I/3023.Find-Pattern-in-Infinite-Stream-I.cpp b/String/3023.Find-Pattern-in-Infinite-Stream-I/3023.Find-Pattern-in-Infinite-Stream-I.cpp new file mode 100644 index 000000000..e45b8ad7f --- /dev/null +++ b/String/3023.Find-Pattern-in-Infinite-Stream-I/3023.Find-Pattern-in-Infinite-Stream-I.cpp @@ -0,0 +1,56 @@ +/** + * Definition for an infinite stream. + * class InfiniteStream { + * public: + * InfiniteStream(vector bits); + * int next(); + * }; + */ +class Solution { +public: + vector preprocess(vector s) + { + int n = s.size(); + vectordp(n,0); + for (int i=1; i=1 && s[j]!=s[i]) + { + j = dp[j-1]; + } + dp[i] = j + (s[j]==s[i]); + } + return dp; + } + + int findPattern(InfiniteStream* stream, vector& pattern) + { + int m = pattern.size(); + if (m==0) return 0; + + vector suf = preprocess(pattern); + + unordered_mapdp; + + int num = stream->next(); + dp[0] = (num==pattern[0]); + if (m==1 && dp[0]==1) + return 0; + + int i = 1; + while (1) + { + int num = stream->next(); + + int j = dp[i-1]; + while (j>0 && (j==pattern.size() || num!=pattern[j])) + j = suf[j-1]; + dp[i] = j + (num==pattern[j]); + if (dp[i]==m) + return i-pattern.size()+1; + i++; + } + return -1; + } +}; diff --git a/String/3023.Find-Pattern-in-Infinite-Stream-I/Readme.md b/String/3023.Find-Pattern-in-Infinite-Stream-I/Readme.md new file mode 100644 index 000000000..e1736235c --- /dev/null +++ b/String/3023.Find-Pattern-in-Infinite-Stream-I/Readme.md @@ -0,0 +1,5 @@ +### 3023.Find-Pattern-in-Infinite-Stream-I + +此题是KMP的模板题。先将pattern进行预处理求得它的前缀数组`suf`。然后对stream产生的nums[i]逐位处理,利用`suf`计算nums的前缀数组dp[i]。其中dp[i]的定义是以nums[i]结尾的最长后缀,同时也是pattern的前缀。 + +当发现某处的dp[i]等于pattern的长度m时,说明`i-m+1`就是匹配的位置。 diff --git a/String/3031.Minimum-Time-to-Revert-Word-to-Initial-State-II/3031.Minimum-Time-to-Revert-Word-to-Initial-State-II.cpp b/String/3031.Minimum-Time-to-Revert-Word-to-Initial-State-II/3031.Minimum-Time-to-Revert-Word-to-Initial-State-II.cpp new file mode 100644 index 000000000..adcf23444 --- /dev/null +++ b/String/3031.Minimum-Time-to-Revert-Word-to-Initial-State-II/3031.Minimum-Time-to-Revert-Word-to-Initial-State-II.cpp @@ -0,0 +1,35 @@ +class Solution { +public: + vector longestPrefix(string s) + { + int n = s.size(); + vectordp(n); + dp[0] = 0; + + for (int i=1; i=1 && s[j]!=s[i]) + { + j = dp[j-1]; + } + dp[i] = j + (s[j]==s[i]); + } + + return dp; + } + + int minimumTimeToInitialState(string word, int k) + { + int n = word.size(); + vectorlcp = longestPrefix (word); + int len = lcp[n-1]; + while (len!=0 && (n-len)%k!=0) + len = lcp[len-1]; + + if (len!=0) + return (n-len)/k; + else + return (n%k==0)?(n/k):(n/k+1); + } +}; diff --git a/String/3031.Minimum-Time-to-Revert-Word-to-Initial-State-II/3031.Minimum-Time-to-Revert-Word-to-Initial-State-II_v2.cpp b/String/3031.Minimum-Time-to-Revert-Word-to-Initial-State-II/3031.Minimum-Time-to-Revert-Word-to-Initial-State-II_v2.cpp new file mode 100644 index 000000000..30c2e84a4 --- /dev/null +++ b/String/3031.Minimum-Time-to-Revert-Word-to-Initial-State-II/3031.Minimum-Time-to-Revert-Word-to-Initial-State-II_v2.cpp @@ -0,0 +1,12 @@ +class Solution { +public: + int minimumTimeToInitialState(string word, int k) + { + int t=1; + int n = word.size(); + char* s = &(word[0]); + while (t*k longestPrefix(string s) + { + int n = s.size(); + vectordp(n); + dp[0] = 0; + + for (int i=1; i=1 && s[j]!=s[i]) + { + j = dp[j-1]; + } + dp[i] = j + (s[j]==s[i]); + } + + return dp; + } + + void add(TrieNode* root, string& word, unordered_set&Set) + { + TrieNode* node = root; + int n = word.size(); + for (int i=0; inext[word[i]-'a']==NULL) + node->next[word[i]-'a'] = new TrieNode(); + node = node->next[word[i]-'a']; + if (Set.find(i+1)!=Set.end()) + node->count++; + } + } + + int find(TrieNode* root, string& word) + { + TrieNode* node = root; + int n = word.size(); + for (int i=0; inext[word[i]-'a']==NULL) + return 0; + node = node->next[word[i]-'a']; + } + return node->count; + } + + + long long countPrefixSuffixPairs(vector& words) + { + LL ret = 0; + for (int i=words.size()-1; i>=0; i--) + { + ret += find(root, words[i]); + + int n = words[i].size(); + vectorlcp = longestPrefix (words[i]); + unordered_setSet; + int len = lcp[n-1]; + while (len!=0) + { + Set.insert(len); + len = lcp[len-1]; + } + Set.insert(n); + + add(root, words[i], Set); + } + return ret; + } +}; diff --git a/String/3045.Count-Prefix-and-Suffix-Pairs-II/Readme.md b/String/3045.Count-Prefix-and-Suffix-Pairs-II/Readme.md new file mode 100644 index 000000000..180ff71c7 --- /dev/null +++ b/String/3045.Count-Prefix-and-Suffix-Pairs-II/Readme.md @@ -0,0 +1,7 @@ +### 3045.Count-Prefix-and-Suffix-Pairs-II + +对于一对pair里的两个字符串而言,前者是后者的一部分,所以我们必然需要先处理后者。故我们需要从后往前遍历字符串。 + +对于一个字符串s,要使得其他字符串既是它的前缀,也是它的后缀,这是一个比较苛刻的条件。我们很容易联想到KMP算法,可以用线性的时间就能找到s里的所有符合条件的子串(参考`LeetCode 3031 Minimum Time to Revert Word to Initial State II`)。对于这些子串(字符串的“前缀&后缀”),我们必然会将其放入一棵字典树里。这样,对于其他字符串t,我们可以通过搜索字典树进行匹配,从而得知t同时是多少字符串的“前缀&后缀”。 + +在字典树的数据结构里,我们会给每个节点标记count。任何需要填入字典树的子串,我们都会在其路径的最后一个节点的count增1. 对于待查询的字符串t,如果它与字典树里的某个路径匹配,那么该路径的最后一个节点的count就代表了t能配对的字符串的数量。 diff --git a/String/3388.Count-Beautiful-Splits-in-an-Array/3388.Count-Beautiful-Splits-in-an-Array.cpp b/String/3388.Count-Beautiful-Splits-in-an-Array/3388.Count-Beautiful-Splits-in-an-Array.cpp new file mode 100644 index 000000000..87348ffea --- /dev/null +++ b/String/3388.Count-Beautiful-Splits-in-an-Array/3388.Count-Beautiful-Splits-in-an-Array.cpp @@ -0,0 +1,48 @@ +using LL = long long; + +class Solution { + const LL P = 53; + const LL M = 1e9 + 7; + + LL get_hash(const vector& prefix_hash, const vector& p_powers, int i, int j) + { + return (prefix_hash[j] - (prefix_hash[i-1] * p_powers[j-i+1]) % M + M) % M; + } + +public: + int beautifulSplits(vector& nums) + { + int n = nums.size(); + nums.insert(nums.begin(), 0); + int count = 0; + + vector prefix_hash(n+1, 0); + vector p_powers(n+1, 1); + + for (int i = 1; i <= n; i++) + { + prefix_hash[i] = (prefix_hash[i - 1] * P + nums[i]) % M; + p_powers[i] = (p_powers[i - 1] * P) % M; + } + + for (int i = 2; i < n; i++) + for (int j = i + 1; j <= n; j++) + { + LL nums1_hash = get_hash(prefix_hash, p_powers, 1, i-1); + LL nums2_hash = get_hash(prefix_hash, p_powers, i, j-1); + int len1 = i-1; + int len2 = j-i; + int len3 = n-j+1; + + int flag =0; + if ((len2>=len1) && nums1_hash == get_hash(prefix_hash, p_powers, i, i+len1-1)) + flag = 1; + + if (len3>=len2 && nums2_hash == get_hash(prefix_hash, p_powers, j, j+len2-1)) + flag = 1; + count += flag; + } + + return count; + } +}; diff --git a/String/3388.Count-Beautiful-Splits-in-an-Array/Readme.md b/String/3388.Count-Beautiful-Splits-in-an-Array/Readme.md new file mode 100644 index 000000000..5fe95028c --- /dev/null +++ b/String/3388.Count-Beautiful-Splits-in-an-Array/Readme.md @@ -0,0 +1,8 @@ +### 3388.Count-Beautiful-Splits-in-an-Array + +根据n的数量级,考虑如果暴力枚举两处分界点的话,那么需要能以o(1)的时间判定一段subarray是否是另一段subarray的前缀。此时常见的方法只有rolling hash。事实上,每个nums[i]的数值有上限50,故可以类比于字符串的rolling hash,方法应该是可行的。 + +需要注意的几个细节: +1. 每个nums[i]的数值上限是50,故可以选取质数53作为进制。 +2. 一段区间的`hash[i:j] = prefix_hash[j] - prefix_hash[i-1] * power[j-i+1]`,同时取余的过程要始终保证是正数。 +3. 判定subarray1是否subarray2的前缀时,要保证subarray2的长度不能小于subarray1. 同理判定subarray2是否subarray3的前缀,也需要考虑这个约束。 diff --git a/String/556.Next-Greater-Element-III/556.Next-Greater-Element-III.cpp b/String/556.Next-Greater-Element-III/556.Next-Greater-Element-III.cpp deleted file mode 100644 index 2d2ad0681..000000000 --- a/String/556.Next-Greater-Element-III/556.Next-Greater-Element-III.cpp +++ /dev/null @@ -1,43 +0,0 @@ -class Solution { -public: - int nextGreaterElement(int n) - { - if (n==0) return -1; - - vectornum; - while (n>0) - { - num.push_back(n%10); - n=n/10; - } - - vectorp; - p.push_back(num[0]); - int i=1; - while (i=num[i-1]) - { - p.push_back(num[i]); - i++; - } - if (i==num.size()) return -1; // all the digits are descending - - int j=0; - while (p[j]<=num[i]) j++; - swap(num[i],p[j]); - - sort(p.begin(),p.end()); - reverse(p.begin(),p.end()); - - for (int k=0; k=0; i--) - result = result*10+num[i]; - - if (result>INT_MAX) - return -1; - else - return result; - } -}; diff --git a/String/564.Find-the-Closest-Palindrome/564.Find-the-Closest-Palindrome.cpp b/String/564.Find-the-Closest-Palindrome/564.Find-the-Closest-Palindrome.cpp index 4d25336c1..29796c550 100644 --- a/String/564.Find-the-Closest-Palindrome/564.Find-the-Closest-Palindrome.cpp +++ b/String/564.Find-the-Closest-Palindrome/564.Find-the-Closest-Palindrome.cpp @@ -2,115 +2,79 @@ class Solution { public: string nearestPalindromic(string n) { - int N=n.size(); - string s1,s2,s3; - - if (N%2==1) - { - string t=n.substr(0,N/2+1); - long long num=convert(t); - string t1,t2; - - // candidate 1 - t1 = to_string(num); - t2 = t1; - reverse(t2.begin(),t2.end()); - s1 = t1.substr(0,N/2)+t2; - - // candidate 2 - t1 = to_string(num-1); - t2=t1; - reverse(t2.begin(),t2.end()); - s2 = t1.substr(0,N/2)+t2; - - // candidate 3 - t1 = to_string(num+1); - t2=t1; - reverse(t2.begin(),t2.end()); - s3 = t1.substr(0,N/2)+t2; + string a = makeSmaller(n); + string b = makeGreater(n); + if (stoll(b)-stoll(n) >= stoll(n)-stoll(a)) + return a; + else + return b; + } - cout<= 0; i--) { - string t=n.substr(0,N/2); - long long num=convert(t); - string t1,t2; - - //candidate 1 - t1 = n.substr(0,N/2); - reverse(t1.begin(),t1.end()); - s1 = to_string(num)+t1; - - //candidate 2 - t1 = to_string(num-1); - if (t1=="0") - s2="9"; - else if (t1.size()==t.size()) + int d = s[i]-'0'-carry; + if (d>=0) { - t2=t1; - reverse(t2.begin(),t2.end()); - s2=t1+t2; + s[i] = '0'+d; + carry = 0; } - else if (t1.size()!=t.size()) + else { - t2=t1; - reverse(t2.begin(),t2.end()); - s2=t1+'9'+t2; + s[i] = '9'; + carry = 1; } - - //candidate 3 - t1 = to_string(num+1); - if (t1.size()==t.size()) + s[m-1-i] = s[i]; + } + if (s[0]=='0' && m>1) + return string(m - 1, '9'); + else + return s; + } + + string makeGreater(const string &n) + { + const int m = n.length(); + string s = n; + for (int i = 0, j = m - 1; i <= j;) + s[j--] = s[i++]; + if (s > n) { + return s; + } + + int carry = 1; + for (int i = (m - 1)/2; i >= 0; i--) + { + int d = s[i]-'0'+carry; + if (d<=9) { - t2=t1; - reverse(t2.begin(),t2.end()); - s3=t1+t2; + s[i] = '0'+d; + carry = 0; } - else if (t1.size()!=t.size()) + else { - t2=t1; - reverse(t2.begin(),t2.end()); - t1.pop_back(); - s3=t1+t2; + s[i] = '0'; + carry = 1; } - - cout<> adj[MAXN]; + int up[MAXN][LOGN+1]; + int depth[MAXN]; + ll distRoot[MAXN]; + + void dfs(int cur, int parent) + { + up[cur][0] = parent; + for(auto &[v,w]: adj[cur]) + { + if(v == parent) continue; + depth[v] = depth[cur] + 1; + distRoot[v] = distRoot[cur] + w; + dfs(v, cur); + } + } + + int lca(int a, int b) + { + if(depth[a] < depth[b]) swap(a,b); + int diff = depth[a] - depth[b]; + for(int k = 0; k <= LOGN; k++){ + if(diff & (1<= 0; k--){ + if(up[a][k] != up[b][k]){ + a = up[a][k]; + b = up[b][k]; + } + } + return up[a][0]; + } + + ll dist(int a, int b) + { + int c = lca(a,b); + return distRoot[a] + distRoot[b] - 2*distRoot[c]; + } + + ll stepUp(int u, int k) { + for (int i=LOGN; i>=0; i--) { + if ((k>>i)&1) { + u = up[u][i]; + } + } + return u; + } + + void solve(vector>& edges) { + for (auto& edge: edges) + { + int u = edge[0], v = edge[1], w = edge[2]; + adj[u].push_back({v,w}); + adj[v].push_back({u,w}); + } + + depth[0] = 0; + distRoot[0] = 0; + dfs(0, 0); + + for(int k = 1; k <= LOGN; k++) { + for(int v = 0; v < n; v++) { + up[v][k] = up[up[v][k-1]][k-1]; + } + } + + // Solve your problem. + } diff --git a/Template/Bit_manipulation/Count_bit_1_numbers.cpp b/Template/Bit_manipulation/Count_bit_1_numbers.cpp index 2a58bea73..f7c977484 100644 --- a/Template/Bit_manipulation/Count_bit_1_numbers.cpp +++ b/Template/Bit_manipulation/Count_bit_1_numbers.cpp @@ -1 +1,3 @@ __builtin_popcount(state) + +__builtin_popcountll(state) diff --git a/Template/CPP_LANG/Struct.cpp b/Template/CPP_LANG/Struct.cpp new file mode 100644 index 000000000..903b99e08 --- /dev/null +++ b/Template/CPP_LANG/Struct.cpp @@ -0,0 +1,10 @@ +struct State { + int mask, stage; + double dist; + bool operator<(State const& o) const { + return dist > o.dist; + } +}; + +// The top element is the one with smallest dist. +priority_queuepq; diff --git a/Template/CPP_LANG/fill_memset.cpp b/Template/CPP_LANG/fill_memset.cpp new file mode 100644 index 000000000..5e0723f1b --- /dev/null +++ b/Template/CPP_LANG/fill_memset.cpp @@ -0,0 +1,15 @@ +main() { + int arr[10]; + fill(arr, arr+10, 3); + + int arr2[2][5]; + fill(&arr2[0][0], &arr2[0][0]+10, 3); + + vectorarr3(10); + fill(arr3.begin(), arr3.end(), 3); + + int low[n+10][K+10]; memset(low, 0x3f, sizeof(low)); // set 0x3f3f3f3f for all. This is a big positive number + int upp[n+10][K+10]; memset(upp, 0xcf, sizeof(upp)); // set 0xcfcfcfcf for all. This is a big negative number. +} + +// 如果 low 不是真正的二维数组(例如你用了 int** low 或 vector> low),那么 sizeof(low) 不会返回元素占用的总字节数(对于指针 it'll be pointer size),memset 的长度参数会不正确导致越界或只初始化了指针。如果使用动态分配/vector,请用 std::fill 或 fill_n diff --git a/Template/CPP_LANG/lamda.cpp b/Template/CPP_LANG/lamda.cpp new file mode 100644 index 000000000..9fa542a8b --- /dev/null +++ b/Template/CPP_LANG/lamda.cpp @@ -0,0 +1,15 @@ +# 定义允许递归的的lamda函数 +std::function dfs = [&](int l, int r) { + int mid = (l+r)/2; + dfs(l, mid-1); + dfs(mid, r)); +} + +# 定义允许递归的、带有返回值的lamda函数 +std::function dfs = [&](int l, int r) -> ll { + int mid = (l+r)/2; + return min(dfs(l, mid-1) + dfs(mid, r)); +}; + +return dfs(0, n-1); + diff --git a/Template/Inverse_Element/Inverse_Element.cpp b/Template/Inverse_Element/Inverse_Element.cpp index 7b30a1420..1d2ae4f3b 100644 --- a/Template/Inverse_Element/Inverse_Element.cpp +++ b/Template/Inverse_Element/Inverse_Element.cpp @@ -1,50 +1,41 @@ #include #define LL long long using namespace std; -const LL N = 1e6+7, mod = 998244353; +const LL N = 1e6+7, MOD = 998244353; /*********************************/ // Linear method to compute inv[i] -void main() +vectorcompute_inv(int n) { - LL inv[N]; + vectorinv(n+1); inv[1] = 1; - for(int i=2; i>= 1; + if (N <= 0) { + return 1; } - return ret; + long long y = quickPow(x, N / 2) % MOD; + return N % 2 == 0 ? (y * y % MOD) : (y * y % MOD * x % MOD); } -LL inv(LL x) +long long inv(long long x) { - return quickPow(x, mod - 2); + return quickPow(x, MOD - 2); } /*****************************/ -LL inv(int x) +long long compute_inv(int x) { LL s = 1; - for (; x > 1; x = mod%x) - s = s*(mod-mod/x)%mod; + for (; x > 1; x = MOD%x) + s = s*(MOD-MOD/x)%MOD; return s; } diff --git a/Template/Math/Combination-Number.cpp b/Template/Math/Combination-Number.cpp index 534f6b472..7d71ee0ee 100644 --- a/Template/Math/Combination-Number.cpp +++ b/Template/Math/Combination-Number.cpp @@ -1,20 +1,45 @@ using LL = long long; -main() + +/*********************************/ +// Version 1: compute all C(n,m) saved in comb +long long comb[1000][1000]; +for (int i = 0; i <= n; ++i) +{ + comb[i][i] = comb[i][0] = 1; + if (i==0) continue; + for (int j = 1; j < i; ++j) + { + comb[i][j] = comb[i - 1][j - 1] + comb[i - 1][j]; + } +} + +/*********************************/ +// Version 1.5: compute all C(n,m) saved in comb with maximum space capability +int comb[5001][2501]; +int getComb(int m, int n) { - // compute all C(n,m) saved in comb - long long comb[1000][1000]; - for (int i = 0; i <= n; ++i) - { - comb[i][i] = comb[i][0] = 1; - if (i==0) continue; - for (int j = 1; j < i; ++j) - { - comb[i][j] = comb[i - 1][j - 1] + comb[i - 1][j]; - } - } -} - -// Compute C(n,m) on demand + if (m factorial; + +vector GetFactorial(LL N) +{ + vectorrets(N+1); + rets[0] = 1; + for (int i=1; i<=N; i++) + rets[i] = rets[i-1] * i % M; + return rets; +} + +long long quickPow(long long x, long long N) { + if (N == 0) { + return 1; + } + LL y = quickPow(x, N / 2) % M; + return N % 2 == 0 ? (y * y % M) : (y * y % M * x % M); +} + +LL comb(LL m, LL n) +{ + if (n>m) return 0; + LL a = factorial[m]; + LL b = factorial[n] * factorial[m-n] % M; + LL inv_b = quickPow(b, (M-2)); + + return a * inv_b % M; +} + +/*********************************/ +// Version 4: Compute C(m,n) on demand with module M +const LL MOD = 1e9 + 7; +vector factorial; +vector GetFactorial(LL N) +{ + vectorrets(N+1); + rets[0] = 1; + for (int i=1; i<=N; i++) + rets[i] = rets[i-1] * i % MOD; + return rets; +} + +long long quickPow(long long x, long long N) { + if (N == 0) { + return 1; + } + LL y = quickPow(x, N / 2) % MOD; + return N % 2 == 0 ? (y * y % MOD) : (y * y % MOD * x % MOD); +} + +LL comb(LL m, LL n) +{ + if (n>m) return 0; + LL a = factorial[m]; + LL b = factorial[n] * factorial[m-n] % MOD; + LL inv_b = quickPow(b, (MOD-2)); + + return a * inv_b % MOD; +} diff --git a/Template/Math/Lucas.cpp b/Template/Math/Lucas.cpp new file mode 100644 index 000000000..27c293bd0 --- /dev/null +++ b/Template/Math/Lucas.cpp @@ -0,0 +1,20 @@ +/* + To compute C(n,m) % p, when p is a small prime (and we cannot use Fermat's Little Theorem) + https://oi-wiki.org/math/number-theory/lucas/ +*/ + +long long Lucas(long long n, long long m, long long p) { + if (m == 0) return 1; + return (C(n % p, m % p) * Lucas(n / p, m / p, p)) % p; +} + +long long C(int n, int m) +{ + long long cnt = 1; + for(int i = 0; i < m; i++) + { + cnt *= n - i; + cnt /= i + 1; + } + return cnt; +} diff --git a/Template/Math/Matrix_Multiplication.cpp b/Template/Math/Matrix_Multiplication.cpp new file mode 100644 index 000000000..e39973756 --- /dev/null +++ b/Template/Math/Matrix_Multiplication.cpp @@ -0,0 +1,59 @@ +// 矩阵乘法 C = A * B +vector> matMul(const vector>& A, const vector>& B) { + int n = A.size(); + int m = B[0].size(); + int K = B.size(); // A: n x K, B: K x m + vector> C(n, vector(m, 0)); + for (int i = 0; i < n; ++i) { + for (int k = 0; k < K; ++k) { + ll aik = A[i][k]; + if (aik == 0) continue; + for (int j = 0; j < m; ++j) { + C[i][j] += (aik * B[k][j]) % MOD; + if (C[i][j] >= MOD) C[i][j] -= MOD; + } + } + } + return C; +} + +// 矩阵自乘 (square) +vector> matMulSquare(const vector>& A, const vector>& B) { + return matMul(A, B); +} + +// 矩阵快速幂 T^e +vector> matPow(vector> base, long long e) { + int K = base.size(); + // initialize res = identity + vector> res(K, vector(K, 0)); + for (int i = 0; i < K; ++i) res[i][i] = 1; + while (e > 0) { + if (e & 1) res = matMul(res, base); + base = matMul(base, base); + e >>= 1; + } + return res; +} + +// 矩阵乘向量 v' = M * v +vector matVecMul(const vector>& M, const vector& v) { + int n = M.size(); + int m = v.size(); + vector r(n, 0); + for (int i = 0; i < n; ++i) { + ll sum = 0; + for (int j = 0; j < m; ++j) { + if (M[i][j] == 0) continue; + sum += (M[i][j] * v[j]) % MOD; + if (sum >= MOD) sum -= MOD; + } + r[i] = sum % MOD; + } + return r; +} + +v_0 = {....}; +T = {{...}}; +vector>T_p = matPow(T, n-1); +vectorv_n = matVecMul(T_p, v_0); diff --git a/Template/Math/Primes.cpp b/Template/Math/Primes.cpp index 9c8f08d03..a31dc999e 100644 --- a/Template/Math/Primes.cpp +++ b/Template/Math/Primes.cpp @@ -20,3 +20,17 @@ vectorEratosthenes(int n) } return primes; } + +// Find the smallest prime factor for each element +vectorComputeSpf(int N) { + vectorspf(N+1); + for (int i = 0; i <= N; ++i) spf[i] = i; + for (int i = 2; i * i <= maxA; ++i) { + if (spf[i] == i) { + for (int j = i * i; j <= N; j += i) { + if (spf[j] == j) spf[j] = i; + } + } + } + return spf; +} diff --git a/Template/Math/QuickPow.cpp b/Template/Math/QuickPow.cpp index e03f18f17..481124e39 100644 --- a/Template/Math/QuickPow.cpp +++ b/Template/Math/QuickPow.cpp @@ -1,5 +1,14 @@ class Solution { +long long MOD = 1e9+7; public: + long long quickMul(long long x, long long N) { + if (N == 0) { + return 1; + } + LL y = quickMul(x, N / 2) % MOD; + return N % 2 == 0 ? (y * y % MOD) : (y * y % MOD * x % MOD); + } + double quickMul(double x, long long N) { if (N == 0) { return 1.0; @@ -8,8 +17,8 @@ class Solution { return N % 2 == 0 ? y * y : y * y * x; } - double myPow(double x, int n) { - long long N = n; - return N >= 0 ? quickMul(x, N) : 1.0 / quickMul(x, -N); + // n can be negative. + double myPow(double x, int n) { + return n >= 0 ? quickMul(x, n) : 1.0 / quickMul(x, -n); } }; diff --git a/Template/SegmentTree/Array-Based/range-max.cpp b/Template/SegmentTree/Array-Based/range-max.cpp new file mode 100644 index 000000000..75739ac97 --- /dev/null +++ b/Template/SegmentTree/Array-Based/range-max.cpp @@ -0,0 +1,85 @@ +#include +using namespace std; + +struct SegmentTree { + int n; + vector tree; + + SegmentTree(int size) { + n = size; + tree.resize(4 * n); + } + + void build(vector& arr, int node, int l, int r) { + if (l == r) { + tree[node] = arr[l]; + return; + } + int mid = (l + r) / 2; + build(arr, node * 2, l, mid); + build(arr, node * 2 + 1, mid + 1, r); + tree[node] = max(tree[node * 2], tree[node * 2 + 1]); + } + + // 单点赋值 + void update_point(int idx, long long val, int node, int l, int r) { + if (l == r) { + tree[node] = val; + return; + } + int mid = (l + r) / 2; + if (idx <= mid) update_point(idx, val, node * 2, l, mid); + else update_point(idx, val, node * 2 + 1, mid + 1, r); + + tree[node] = max(tree[node * 2], tree[node * 2 + 1]); + } + + // 区间赋值(逐点更新实现) + void update_range_val(int ql, int qr, long long val, int node, int l, int r) { + if (ql > r || qr < l) return; + + if (l == r) { + tree[node] = val; + return; + } + + int mid = (l + r) / 2; + update_range_val(ql, qr, val, node * 2, l, mid); + update_range_val(ql, qr, val, node * 2 + 1, mid + 1, r); + + tree[node] = max(tree[node * 2], tree[node * 2 + 1]); + } + + // 区间最大值查询 + long long query_range_max(int ql, int qr, int node, int l, int r) { + if (ql > r || qr < l) return LLONG_MIN; + + if (ql <= l && r <= qr) { + return tree[node]; + } + + int mid = (l + r) / 2; + return max( + query_range_max(ql, qr, node * 2, l, mid), + query_range_max(ql, qr, node * 2 + 1, mid + 1, r) + ); + } +}; + + +int main() { + vector arr = {1, 3, 5, 7, 9, 11}; + int n = arr.size(); + + SegmentTree st(n); + // initialize the seg tree with arr[0:n-1] + st.build(arr, 1, 0, n - 1); + + // query the max within range [1:4] + cout << st.query(1, 4, 1, 0, n-1) << endl; + + // update the range [1:3] with the new value 100 + st.update(1, 3, 100, 1, 0, n-1); + + cout << st.query(1, 4, 1, 0, n-1) << endl; +} diff --git a/Template/SegmentTree/range_bitwise_and.cpp b/Template/SegmentTree/range_bitwise_and.cpp new file mode 100644 index 000000000..ccc023344 --- /dev/null +++ b/Template/SegmentTree/range_bitwise_and.cpp @@ -0,0 +1,68 @@ +class SegmentTree { +private: + vector tree; + int n; + + void build(vector& nums, int node, int start, int end) + { + if (start == end) { + tree[node] = nums[start]; + } else { + int mid = (start + end) / 2; + build(nums, 2 * node, start, mid); + build(nums, 2 * node + 1, mid + 1, end); + tree[node] = tree[2 * node] & tree[2 * node + 1]; + } + } + + void update(int node, int start, int end, int L, int R, int val) + { + if (R < start || end < L) { + return; + } + if (L <= start && end <= R) { + tree[node] = val; + return; + } + int mid = (start + end) / 2; + update(2 * node, start, mid, L, R, val); + update(2 * node + 1, mid + 1, end, L, R, val); + tree[node] = tree[2 * node] & tree[2 * node + 1]; + } + + int query(int node, int start, int end, int L, int R) + { + if (R < start || end < L) { + return INT_MAX; // Identity for AND operation (all bits set) + } + if (L <= start && end <= R) { + return tree[node]; + } + int mid = (start + end) / 2; + int leftAnd = query(2 * node, start, mid, L, R); + int rightAnd = query(2 * node + 1, mid + 1, end, L, R); + return leftAnd & rightAnd; + } + +public: + SegmentTree(vector& nums) { + n = nums.size(); + tree.resize(4 * n, 0); + build(nums, 1, 0, n - 1); + } + + void rangeUpdate(int L, int R, int val) { + update(1, 0, n - 1, L, R, val); + } + + int rangeAnd(int L, int R) { + return query(1, 0, n - 1, L, R); + } +}; + +int main() +{ + int n = nums.size(); + SegmentTree segTree(nums); + int ret = segTree.rangeAnd(a, b); +} diff --git a/Template/SegmentTree/range_max.cpp b/Template/SegmentTree/range_max.cpp index 67c9b9ef7..e0d6fe36c 100644 --- a/Template/SegmentTree/range_max.cpp +++ b/Template/SegmentTree/range_max.cpp @@ -25,6 +25,25 @@ class SegTreeNode } } + SegTreeNode(int a, int b, vector& val) // init for range [a,b] with the same-size array val + { + tag = 0; + info = 0; + start = a, end = b; + if (a==b) + { + info = val[a]; + return; + } + int mid = (a+b)/2; + if (left==NULL) + { + left = new SegTreeNode(a, mid, val); + right = new SegTreeNode(mid+1, b, val); + info = max(left->info, right->info); // check with your own logic + } + } + void pushDown() { if (tag==1 && left) @@ -61,7 +80,7 @@ class SegTreeNode { if (b < start || a > end ) { - return INT_MIN; // check with your own logic + return INT_MIN/2; // check with your own logic } if (a <= start && end <=b) { @@ -83,7 +102,7 @@ class SegTreeNode int main() { - SegTreeNode* root = new SegTreeNode(0, length-1, 0); + SegTreeNode* root = new SegTreeNode(0, length-1, initVals); // Set the leaf nodes with initVals. for (auto& update: updates) { diff --git a/Template/SegmentTree/range_min.cpp b/Template/SegmentTree/range_min.cpp new file mode 100644 index 000000000..5d39bde09 --- /dev/null +++ b/Template/SegmentTree/range_min.cpp @@ -0,0 +1,117 @@ +class SegTreeNode +{ + public: + SegTreeNode* left = NULL; + SegTreeNode* right = NULL; + int start, end; + int info; // the maximum value of the range + bool tag; + + SegTreeNode(int a, int b, int val) // init for range [a,b] with val + { + tag = 0; + start = a, end = b; + if (a==b) + { + info = val; + return; + } + int mid = (a+b)/2; + if (left==NULL) + { + left = new SegTreeNode(a, mid, val); + right = new SegTreeNode(mid+1, b, val); + info = min(left->info, right->info); // check with your own logic + } + } + + SegTreeNode(int a, int b, vector& val) // init for range [a,b] with the same-size array val + { + tag = 0; + info = 0; + start = a, end = b; + if (a==b) + { + info = val[a]; + return; + } + int mid = (a+b)/2; + if (left==NULL) + { + left = new SegTreeNode(a, mid, val); + right = new SegTreeNode(mid+1, b, val); + info = min(left->info, right->info); // check with your own logic + } + } + + void pushDown() + { + if (tag==1 && left) + { + left->info = info; + right->info = info; + left->tag = 1; + right->tag = 1; + tag = 0; + } + } + + void updateRange(int a, int b, int val) // set range [a,b] with val + { + if (b < start || a > end ) // not covered by [a,b] at all + return; + if (a <= start && end <=b) // completely covered within [a,b] + { + info = val; + tag = 1; + return; + } + + if (left) + { + pushDown(); + left->updateRange(a, b, val); + right->updateRange(a, b, val); + info = min(left->info, right->info); // write your own logic + } + } + + int queryRange(int a, int b) // query the maximum value within range [a,b] + { + if (b < start || a > end ) + { + return INT_MAX/2; // check with your own logic + } + if (a <= start && end <=b) + { + return info; // check with your own logic + } + + if (left) + { + pushDown(); + int ret = min(left->queryRange(a, b), right->queryRange(a, b)); + info = min(left->info, right->info); // check with your own logic + return ret; + } + + return info; // should not reach here + } + +}; + +int main() +{ + SegTreeNode* root = new SegTreeNode(0, length-1, initVals); // Set the leaf nodes with initVals. + + for (auto& update: updates) + { + int start = update[0], end = update[1], val = update[2]; + root->updateRange(start, end ,val); // set the range [start, end] with val + } + + vectorrets(length); + for (int i=0; iqueryRange(i, i); // get single node val + return rets; +} diff --git a/Template/SegmentTree/range_sum.cpp b/Template/SegmentTree/range_sum.cpp index c52051e69..4ebcb2d98 100644 --- a/Template/SegmentTree/range_sum.cpp +++ b/Template/SegmentTree/range_sum.cpp @@ -28,6 +28,25 @@ class SegTreeNode } } + SegTreeNode(int a, int b, vector& val) // init for range [a,b] with the same-size array val + { + lazy_tag = 0; + lazy_val = 0; + start = a, end = b; + if (a==b) + { + info = val[a]; + return; + } + int mid = (a+b)/2; + if (left==NULL) + { + left = new SegTreeNode(a, mid, val); + right = new SegTreeNode(mid+1, b, val); + info = left->info + right->info; // check with your own logic + } + } + void pushDown() { if (lazy_tag==1 && left) @@ -86,7 +105,7 @@ class SegTreeNode int main() { - SegTreeNode* root = new SegTreeNode(0, length-1, 0); + SegTreeNode* root = new SegTreeNode(0, length-1, initVals); // Set the leaf nodes with initVals. for (auto& update: updates) { diff --git a/Template/SegmentTree/range_sum_increase_by.cpp b/Template/SegmentTree/range_sum_increase_by.cpp index 578a1f61f..92bba4ce0 100644 --- a/Template/SegmentTree/range_sum_increase_by.cpp +++ b/Template/SegmentTree/range_sum_increase_by.cpp @@ -1,4 +1,5 @@ using LL = long long; +LL M = 1e9+7; class SegTreeNode { public: @@ -26,13 +27,34 @@ class SegTreeNode right = new SegTreeNode(mid+1, b, val); info = left->info + right->info; // check with your own logic } - } + } + + SegTreeNode(int a, int b, vector& val) // init for range [a,b] with the same-size array val + { + tag = 0; + delta = 0; + start = a, end = b; + if (a==b) + { + info = val[a]; + return; + } + int mid = (a+b)/2; + if (left==NULL) + { + left = new SegTreeNode(a, mid, val); + right = new SegTreeNode(mid+1, b, val); + info = left->info + right->info; // check with your own logic + } + } void pushDown() { if (tag==1 && left) { + left->info += delta * (left->end - left->start + 1); left->delta += delta; + right->info += delta * (right->end - right->start + 1); right->delta += delta; left->tag = 1; right->tag = 1; @@ -47,21 +69,24 @@ class SegTreeNode return; if (a <= start && end <=b) // completely covered within [a,b] { + info += val * (end-start+1); delta += val; tag = 1; return; } if (left) - { - pushDown(); - left->updateRangeBy(a, b, val); - right->updateRangeBy(a, b, val); + { + pushDown(); + left->updateRangeBy(a, b, val+delta); + right->updateRangeBy(a, b, val+delta); + delta = 0; + tag = 0; info = left->info + right->info; // write your own logic } } - LL queryRange(int a, int b) // query the maximum value within range [a,b] + LL queryRange(int a, int b) // query the sum within range [a,b] { if (b < start || a > end ) { @@ -69,8 +94,8 @@ class SegTreeNode } if (a <= start && end <=b) { - return info + delta*(end-start+1); // check with your own logic - } + return info; // check with your own logic + } if (left) { @@ -83,3 +108,20 @@ class SegTreeNode return info; // should not reach here } }; + +int main() +{ + SegTreeNode* root = new SegTreeNode(0, length-1, initVals); // Set the leaf nodes with initVals. + + for (auto& update: updates) + { + int start = update[0], end = update[1], val = update[2]; + root->updateRange(start, end ,val); // increase the range [start, end] by val + } + + for (auto& query: queries) + { + int start = query[0], end = query[1]; + ret[i] = root->queryRange(start, end); // get the range sum over [start, end] + } +} diff --git a/Template/Union_Find/union_find.cpp b/Template/Union_Find/union_find.cpp new file mode 100644 index 000000000..ff864f0e7 --- /dev/null +++ b/Template/Union_Find/union_find.cpp @@ -0,0 +1,23 @@ +struct DSU { + vector p, r; + DSU(int n): p(n,-1), r(n,0) { + iota(p.begin(), p.end(), 0); + } + int find(int x) { + return p[x]==x ? x : p[x]=find(p[x]); + } + bool unite(int a, int b) { + a = find(a); b = find(b); + if (a == b) return false; + if (r[a] < r[b]) swap(a,b); + p[b] = a; + if (r[a]==r[b]) ++r[a]; + return true; + } +}; + +int main { + DSU dsu(n); + dsu.unite(u,v); + if (dsu.find(u)==dsu.find(v) {} +} diff --git a/Thinking/2860.Happy-Students/2860.Happy-Students.cpp b/Thinking/2860.Happy-Students/2860.Happy-Students.cpp new file mode 100644 index 000000000..f69cd1bbe --- /dev/null +++ b/Thinking/2860.Happy-Students/2860.Happy-Students.cpp @@ -0,0 +1,22 @@ +class Solution { +public: + int countWays(vector& nums) + { + sort(nums.begin(), nums.end()); + int n = nums.size(); + + int ret = 0; + for (int i=0; i+1 nums[i]) && (i+1 < nums[i+1])) + ret++; + } + + if (0 < nums[0]) + ret++; + if (n > nums[n-1]) + ret++; + + return ret; + } +}; diff --git a/Thinking/2860.Happy-Students/Readme.md b/Thinking/2860.Happy-Students/Readme.md new file mode 100644 index 000000000..9c9f9d793 --- /dev/null +++ b/Thinking/2860.Happy-Students/Readme.md @@ -0,0 +1,9 @@ +### 2860.Happy-Students + +我们发现,将nums按照从小到大排序后,如果第i个同学选中并且happy,那么比他小的同学必须选中才能happy。如果第j个同学没选中并且happy,那么比他大的同学也一定要不被选中才能happy。 + +因为所有的同学都happy,这就告诉我们,所有选中的同学必然是相邻的,所有没有选中的同学必然是相邻的。所以我们需要找到这个分界点。只需要遍历所有的间隔位置,判断如果左边选中、右边不选中,是否能够满足让他们两个happy(其他人自然自动满足)。 + +注意这样的分界点没有连续性,它可能离散地出现在任何位置。 + +此外注意全部选中和全部不选两种特殊情况。 diff --git a/Thinking/2862.Maximum-Element-Sum-of-a-Complete-Subset-of-Indices/2862.Maximum-Element-Sum-of-a-Complete-Subset-of-Indices.cpp b/Thinking/2862.Maximum-Element-Sum-of-a-Complete-Subset-of-Indices/2862.Maximum-Element-Sum-of-a-Complete-Subset-of-Indices.cpp new file mode 100644 index 000000000..e46a3de45 --- /dev/null +++ b/Thinking/2862.Maximum-Element-Sum-of-a-Complete-Subset-of-Indices/2862.Maximum-Element-Sum-of-a-Complete-Subset-of-Indices.cpp @@ -0,0 +1,21 @@ +using LL = long long; +class Solution { +public: + long long maximumSum(vector& nums) + { + int n = nums.size(); + + int k = 1; + LL ret = 0; + while (k<=n) + { + LL sum = 0; + for (int i=1; k*i*i<=n; i++) + sum += nums[k*i*i-1]; + ret = max(ret, sum); + k++; + } + + return ret; + } +}; diff --git a/Thinking/2862.Maximum-Element-Sum-of-a-Complete-Subset-of-Indices/Readme.md b/Thinking/2862.Maximum-Element-Sum-of-a-Complete-Subset-of-Indices/Readme.md new file mode 100644 index 000000000..00d2b3b4e --- /dev/null +++ b/Thinking/2862.Maximum-Element-Sum-of-a-Complete-Subset-of-Indices/Readme.md @@ -0,0 +1,15 @@ +### 2862.Maximum-Element-Sum-of-a-Complete-Subset-of-Indices + +我们分析一下“任意两个下标i与j的乘积是完全平方数”的含义。我们将i分解为`i=a*x^2`,其中x^2是i里包含的最大平方因子。同理,分解`j=b*y^2`。为了使得```i*j```依然是平方数,那么必然要求`a==b`. 同理,与{i,j}属于同一个集合里的其他下标元素,必然也必须能分解为`a*z^2`的形式。 + +所以为了最大化这个集合(不仅指集合元素的数目,也指element-sum),集合元素里的那些“最大平方因子”必然是`1^2, 2^2, 3^3, 4^2 ... `直至n。然后我们再穷举`a=1,2,3...`. 就可以构造出所有可能的最优集合,即 + +```1*1, 1*4,1*9, 1*16, ...``` + +```2*1, 2*4,2*9, 2*16, ...``` + +```3*1, 3*4,3*9, 3*16, ...``` + +直至集合最小元素的上限是n。 + +那么我们穷举这些元素的时间复杂度是多少呢?对于`*1`而言,我们穷举了n次。对于`*4`而言,我们穷举了n/4次。对于`*9`而言,我们穷举了n/9次。所以总的穷举数目为`n/1 + n/4 + n/9 + ...`,它是和小于2n的序列。故总的时间复杂度是o(N). diff --git a/Thinking/2910.Minimum-Number-of-Groups-to-Create-a-Valid-Assignment/2910.Minimum-Number-of-Groups-to-Create-a-Valid-Assignment.cpp b/Thinking/2910.Minimum-Number-of-Groups-to-Create-a-Valid-Assignment/2910.Minimum-Number-of-Groups-to-Create-a-Valid-Assignment.cpp new file mode 100644 index 000000000..811db33dc --- /dev/null +++ b/Thinking/2910.Minimum-Number-of-Groups-to-Create-a-Valid-Assignment/2910.Minimum-Number-of-Groups-to-Create-a-Valid-Assignment.cpp @@ -0,0 +1,36 @@ +class Solution { +public: + int minGroupsForValidAssignment(vector& nums) + { + int n = nums.size(); + unordered_mapMap; + for (int x: nums) Map[x]++; + + int m = INT_MAX; + for (auto [_, x]: Map) + m = min(m, x); + + for (int k=m+1; k>=1; k--) + { + int count = 0; + for (auto [_, x]: Map) + { + int q = x / k; + int r = x % k; + if (r==0 || k-r <= q+1) + { + count += ceil(x*1.0 / k); + } + else + { + count = -1; + break; + } + + } + if (count != -1) return count; + } + + return 0; + } +}; diff --git a/Thinking/2910.Minimum-Number-of-Groups-to-Create-a-Valid-Assignment/Readme.md b/Thinking/2910.Minimum-Number-of-Groups-to-Create-a-Valid-Assignment/Readme.md new file mode 100644 index 000000000..326690df2 --- /dev/null +++ b/Thinking/2910.Minimum-Number-of-Groups-to-Create-a-Valid-Assignment/Readme.md @@ -0,0 +1,11 @@ +### 2910.Minimum-Number-of-Groups-to-Create-a-Valid-Assignment + +我们首先将所有元素的频率收集起来放入一个数组arr。本题就是将arr里的每个元素都做拆分,要求最多只能拆分出两种相邻的数字(记做k和k-1)。求最少能拆分出多少个数字来。 + +本题的一个坑就是二分搜索是不成立的。这是说拆分的越多就越容易,这里没有单调性。比如说,[10,20]可以拆分出k=10, 即[10,10,10];但不能拆分出k=9;但是又可以拆分出k=5,即[5,5,5,5,5,5]. + +本题的解法其实就是暴力尝试。假设arr的长度是n,出现最小的频次是m,那么我们就从`k=m+1,m,...,1`逐个尝试,找到最大的k使得所有arr的元素都能成功拆分成若干个k或k-1的和。这样的时间复杂度看上去是0(mn). 事实上mn有制约关系,如果nums的种类各不相同,那么m就是1,n就是1e5;如果nums的种类完全相同,那么m就是1e5,n就是1. 事实上,o(mn)就是1e5数量级。 + +接下来我们考虑,如果给定了k,如何判定某个arr的元素x能成功拆封成若干个k或k-1之和?我们将x尽量拆分出最多的k来,得到`q = x/k`个group,以及余数`r = x%k`. 此时我们还差`k-r`才能凑出一个k。如果`k-r<=q+1`,意味着我们从之前的这些group里各自都拆借1加到最后一个“落单”的group,而那些变动的group里含有的元素就是`k-1`,依然符合要求. 注意,如果`r==0`时,需要另外处理这个corner case。 + +最终当我们找到最大的k,使得所有arr的x都能成立时,我们用`ceil(x*1.0/k)`即可计算出总共分成的group的数目。 diff --git a/Thinking/2939.Maximum-Xor-Product/2939.Maximum-Xor-Product.cpp b/Thinking/2939.Maximum-Xor-Product/2939.Maximum-Xor-Product.cpp new file mode 100644 index 000000000..d1cba2eb6 --- /dev/null +++ b/Thinking/2939.Maximum-Xor-Product/2939.Maximum-Xor-Product.cpp @@ -0,0 +1,63 @@ +using LL = long long; +LL M = 1e9+7; +class Solution { +public: + int maximumXorProduct(long long a, long long b, int n) + { + LL A = ((a>>n)<>n)<=0; k--) + { + LL bit1 = ((a>>k)&1LL); + LL bit2 = ((b>>k)&1LL); + if (bit1==bit2) + { + a = a - (bit1<B) + { + for (int k=n-1; k>=0; k--) + { + LL bit1 = ((a>>k)&1LL); + LL bit2 = ((b>>k)&1LL); + if (bit1==bit2) + { + a = a - (bit1<& coins, int target) + { + sort(coins.begin(), coins.end()); + int limit = 0; + int i = 0; + int ret = 0; + while (limit < target) + { + if (i==coins.size() || limit+1 < coins[i]) + { + ret++; + limit += limit+1; + } + else + { + limit += coins[i]; + i++; + } + } + + return ret; + } +}; diff --git a/Thinking/2952.Minimum-Number-of-Coins-to-be-Added/Readme.md b/Thinking/2952.Minimum-Number-of-Coins-to-be-Added/Readme.md new file mode 100644 index 000000000..e89c2d779 --- /dev/null +++ b/Thinking/2952.Minimum-Number-of-Coins-to-be-Added/Readme.md @@ -0,0 +1,5 @@ +### 2952.Minimum-Number-of-Coins-to-be-Added + +将所有的coins排序后,假设当前已有的硬币能够组成任意[0, limit]之间的面额,那么又得到一枚面值是x的硬币,此时能够组成多少种面额呢?显然,当新硬币不用时,我们依然能构造出[0, limit];当使用新硬币时,我们可以构造出[x, limit+x]。当这两段区间不连接时,即`limit+1=x`时,则意味着我们可以构造出任意[0,limit+x]区间内的面额。 + +此题和`330.Patching-Array`和`1798.Maximum-Number-of-Consecutive-Values-You-Can-Make`非常相似。 diff --git a/Thinking/2957.Remove-Adjacent-Almost-Equal-Characters/2957.Remove-Adjacent-Almost-Equal-Characters.cpp b/Thinking/2957.Remove-Adjacent-Almost-Equal-Characters/2957.Remove-Adjacent-Almost-Equal-Characters.cpp new file mode 100644 index 000000000..fdb7f18cd --- /dev/null +++ b/Thinking/2957.Remove-Adjacent-Almost-Equal-Characters/2957.Remove-Adjacent-Almost-Equal-Characters.cpp @@ -0,0 +1,18 @@ +class Solution { +public: + int removeAlmostEqualCharacters(string word) + { + int n = word.size(); + int ret = 0; + for (int i=0; i tx || sy > ty) return -1; + if (sx == tx && sy == ty) return 0; + + if (tx < ty) + return minMoves(sy, sx, ty, tx); + + if (tx==ty) { + int temp1 = minMoves(sy, sx, 0, ty); + if (temp1!=-1) return temp1+1; + + int temp2 = minMoves(sy, sx, tx, 0); + if (temp2!=-1) return temp2+1; + + return -1; + } + + if (tx > 2*ty) { + if (tx%2==1) return -1; + int temp = minMoves(sx, sy, tx/2, ty); + if (temp==-1) return -1; + else return temp+1; + } else { + int temp = minMoves(sx, sy, tx-ty, ty); + if (temp==-1) return -1; + else return temp+1; + } + + return -1; + } +}; diff --git a/Thinking/3609.Minimum-Moves-to-Reach-Target-in-Grid/Readme.md b/Thinking/3609.Minimum-Moves-to-Reach-Target-in-Grid/Readme.md new file mode 100644 index 000000000..b53881186 --- /dev/null +++ b/Thinking/3609.Minimum-Moves-to-Reach-Target-in-Grid/Readme.md @@ -0,0 +1,19 @@ +### 3609.Minimum-Moves-to-Reach-Target-in-Grid + +注意到sx,sy和tx,ty都是正数,所有的操作只能单向地使得数值变大。 + +我们思考(sx,sy)变成(tx,ty)的过程中的最后一步,即从(a,b)->(tx,ty)。因为增量`m=max(a,b)`很大,所以无论是第一个分量还是第二个分量,加上m之后都会大于另一个。所以我们从tx和ty的大小比较中就可以知道,最后一次操作是作用在了哪个分量上面。这里我们分情况讨论. + +如果tx>ty,那么显然最后一次操作是加在了第一个分量上面。即`tx=a+max(a,b), ty=b`。继续分类讨论 +1. 如果max(a,b)=a,则有tx=2a,继而得到`a=tx/2, b=ty`,这等价于`tx>=2*ty`. 也就是说,在此情况下,(tx,ty)的前一步必然是(tx/2,ty)。于是我们发现如果tx不能被2整除,那么前一步是不存在的;反之我们可以递归处理成`minMoves(sx,sy,tx/2,ty)+1`即可. + +2. 如果max(a,b)=a,则有tx=a+b,继而得到`a=tx-ty, b=ty`这等价于`tx<2*ty`. 在此情况下,(tx,ty)的前一步必然是(tx-ty,ty)。于是我们可以递归处理成`minMoves(sx,sy,tx-ty,ty)+1`即可. + +如果tx(x,x)。继续分类讨论: +1. 如果操作是作用于第一个分量上面,`x=a+max(a,b), x=b`,必然有a==0. 继而b=x,原题转化为`minMoves(sx,sy,0,x)+1` + +2. 如果操作是作用于第二个分量上面,`x=a, x=b+max(a,b)`,必然有b==0. 继而a=x,原题转化为`minMoves(sx,sy,x,0)+1` + +最终的边界条件就是`sx==tx && sy==ty`时,返回0. 如果tx和ty的任意分量小于sx和sy,那么返回-1. diff --git a/Thinking/3644.Maximum-K-to-Sort-a-Permutation/3644.Maximum-K-to-Sort-a-Permutation.cpp b/Thinking/3644.Maximum-K-to-Sort-a-Permutation/3644.Maximum-K-to-Sort-a-Permutation.cpp new file mode 100644 index 000000000..cafe46d5e --- /dev/null +++ b/Thinking/3644.Maximum-K-to-Sort-a-Permutation/3644.Maximum-K-to-Sort-a-Permutation.cpp @@ -0,0 +1,21 @@ +class Solution { +public: + int sortPermutation(vector& nums) { + int required_mask = -1; + bool is_sorted = true; + int n = nums.size(); + + for (int i = 0; i < n; ++i) { + if (nums[i] != i) { + is_sorted = false; + required_mask &= i; + } + } + + if (is_sorted) { + return 0; + }else { + return required_mask; + } + } +}; diff --git a/Thinking/3644.Maximum-K-to-Sort-a-Permutation/Readme.md b/Thinking/3644.Maximum-K-to-Sort-a-Permutation/Readme.md new file mode 100644 index 000000000..e69a3b14c --- /dev/null +++ b/Thinking/3644.Maximum-K-to-Sort-a-Permutation/Readme.md @@ -0,0 +1,7 @@ +### 3644.Maximum-K-to-Sort-a-Permutation + +这道题的第一反应是一定会有解吗?事实上本题的关键在于nums的值是0到n-1的排列。如果我们想到,0与任何数字的位与都是0,因此我们可以通过0与任意数字的交换来实现数组的重排序。举个例子,如果当前0是在index=4上,我们就寻找数组里的5并假设其在index=p的位置。通过(4,p)的交换,我们就把5送到了它应该去的位置(即idx=4),而把0送到了另一个p的位置。由此重复类似的操作。故k=0必然是一个解。 + +既然一定有解,那么最优解是什么呢?我们只需要考虑那些不在期望位置、必须重排的元素,记作v1,v2,...,vt。答案就是令k为这些元素的bitwise AND的结果。为什么这么神奇呢?首先k一定比这些v都要小,故k一定是落在[0,n-1]之间的。其次,我们发现v1&k, v2&k, ..., vt&k的结果一定都是k。所以我们只需要寻找nums的k所在的那个元素,把它类比于上述的0,就可以实现nums里对v1,v2,..,vt的任意重排。 + +有没有比k更好的答案了呢?不会,如果我们的交换涉及到比上述vi更多的元素,那么得到的bitwise AND的结果一定更小,结果也就一定比k更差。 diff --git a/Thinking/3660.Jump-Game-IX/3660.Jump-Game-IX.cpp b/Thinking/3660.Jump-Game-IX/3660.Jump-Game-IX.cpp new file mode 100644 index 000000000..ce95f215a --- /dev/null +++ b/Thinking/3660.Jump-Game-IX/3660.Jump-Game-IX.cpp @@ -0,0 +1,23 @@ +class Solution { +public: + vector maxValue(vector& nums) { + int n = nums.size(); + vectorpreMax(n); + vectorsufMin(n); + for (int i=0; i=0; i--) + sufMin[i] = min((i==n-1)?INT_MAX:sufMin[i+1], nums[i]); + + vectorrets(n); + rets[n-1] = preMax[n-1]; + for (int i=n-2; i>=0; i--) { + if (preMax[i]>sufMin[i+1]) + rets[i] = rets[i+1]; + else + rets[i] = preMax[i]; + } + + return rets; + } +}; diff --git a/Thinking/3660.Jump-Game-IX/Readme.md b/Thinking/3660.Jump-Game-IX/Readme.md new file mode 100644 index 000000000..31b1a3df6 --- /dev/null +++ b/Thinking/3660.Jump-Game-IX/Readme.md @@ -0,0 +1,11 @@ +### 3660.Jump-Game-IX + +此题乍看是个图论的问题,彼此可以跳转的点可以认为是联通的。但是构建所有的边需要n^2的复杂度。 + +我们定义preMax[i]表示前i个元素(包括自身)里的最大值。考察任意的rets[i],如果答案只在左边的话,那么答案就是preMax[i]。但是也有可能答案在右边。从i能往右跳转到哪些地方呢?我们势必会先借助于preMax[i],因为从preMax[i]跳转的话可以最大范围地覆盖到[i+1:n-1]里的可跳转区域。此时两种情况: + +1. 如果`preMax[i]sufMin[i+1]`,那么我们可以有这样一条跳转路径:i->preMax[i]->sufMin[i]->i+1. 最后一步跳转的依据是:根据定义,sufMin[i]是[i+1:n-1]里的最小值,必然小于等于nums[i+1].由此可知i与i+1是联通的,必然有`rets[i]=rets[i+1]`. + +由此我们发现了rets从后往前的递推关系。因为rets[n-1]必然等于preMax[n-1],由此可以根据以上的结论,依次推出i=n-2,n-1,...,0的答案。 diff --git a/Tree/099.Recover-Binary-Search-Tree/Readme.md b/Tree/099.Recover-Binary-Search-Tree/Readme.md index f85c3a72c..30b27bc3b 100644 --- a/Tree/099.Recover-Binary-Search-Tree/Readme.md +++ b/Tree/099.Recover-Binary-Search-Tree/Readme.md @@ -1,8 +1,8 @@ ### 099.Recover-Binary-Search-Tree -因为是BST,所以按先序遍历访问下来应该是一个递增的数列。如果一个递增的数列里出现两个数字的对调,那么会有两个尖峰。显然,第一个尖峰的顶和第二个尖峰的谷,就是被掉包的那两个数字。 +因为是BST,所以按中序遍历访问下来应该是一个递增的数列。如果一个递增的数列里出现两个数字的对调,那么会有两个尖峰。显然,第一个尖峰的顶和第二个尖峰的谷,就是被掉包的那两个数字。 -本题按先序遍历访问BST(采用DFS递归的方法)。初始化三个公共变量 +本题按中序遍历访问BST(采用DFS递归的方法)。初始化三个公共变量 ```cpp TreeNode* first=NULL; TreeNode* Second=NULL; @@ -17,4 +17,4 @@ TreeNode* CurMax=new TreeNode(INT_MIN); 这里还有一个关键点:如果整个树的两个掉包元素是相邻的,那么整个遍历只会找到一个尖峰。所以这里未雨绸缪的技巧是,在处理第一个尖峰时,同时把第二个掉包元素也设置 second==node. 后续如果找到了第二个尖峰,则second会被覆盖。 -[Leetcode Link](https://leetcode.com/problems/recover-binary-search-tree) \ No newline at end of file +[Leetcode Link](https://leetcode.com/problems/recover-binary-search-tree) diff --git a/Tree/2277.Closest-Node-to-Path-in-Tree/2277.Closest-Node-to-Path-in-Tree.cpp b/Tree/2277.Closest-Node-to-Path-in-Tree/2277.Closest-Node-to-Path-in-Tree_v1.cpp similarity index 100% rename from Tree/2277.Closest-Node-to-Path-in-Tree/2277.Closest-Node-to-Path-in-Tree.cpp rename to Tree/2277.Closest-Node-to-Path-in-Tree/2277.Closest-Node-to-Path-in-Tree_v1.cpp diff --git a/Tree/2277.Closest-Node-to-Path-in-Tree/2277.Closest-Node-to-Path-in-Tree_v2.cpp b/Tree/2277.Closest-Node-to-Path-in-Tree/2277.Closest-Node-to-Path-in-Tree_v2.cpp new file mode 100644 index 000000000..30f174ab5 --- /dev/null +++ b/Tree/2277.Closest-Node-to-Path-in-Tree/2277.Closest-Node-to-Path-in-Tree_v2.cpp @@ -0,0 +1,90 @@ +using ll = long long; +const int MAXN = 100000; +const int LOGN = 17; +class Solution { +public: + vector> adj[MAXN]; + int up[MAXN][LOGN+1]; + int depth[MAXN]; + ll distRoot[MAXN]; + + void dfs(int cur, int parent) + { + up[cur][0] = parent; + for(auto &[v,w]: adj[cur]) + { + if(v == parent) continue; + depth[v] = depth[cur] + 1; + distRoot[v] = distRoot[cur] + w; + dfs(v, cur); + } + } + + int lca(int a, int b) + { + if(depth[a] < depth[b]) swap(a,b); + int diff = depth[a] - depth[b]; + for(int k = 0; k <= LOGN; k++){ + if(diff & (1<= 0; k--){ + if(up[a][k] != up[b][k]){ + a = up[a][k]; + b = up[b][k]; + } + } + return up[a][0]; + } + + ll dist(int a, int b) + { + int c = lca(a,b); + return distRoot[a] + distRoot[b] - 2*distRoot[c]; + } + + ll stepUp(int u, int k) { + for (int i=LOGN; i>=0; i--) { + if ((k>>i)&1) { + u = up[u][i]; + } + } + return u; + } + + vector closestNode(int n, vector>& edges, vector>& query) { + for (auto& edge: edges) + { + int u = edge[0], v = edge[1]; + adj[u].push_back({v,1}); + adj[v].push_back({u,1}); + } + + depth[0] = 0; + distRoot[0] = 0; + dfs(0, 0); + + vectorrets; + for(int k = 1; k <= LOGN; k++) { + for(int v = 0; v < n; v++) { + up[v][k] = up[up[v][k-1]][k-1]; + } + } + + for (auto&q: query) { + int u = q[0], v = q[1], k = q[2]; + vector>ans; + int uv = lca(u,v); + int uk = lca(u,k); + int vk = lca(v,k); + ans.push_back({depth[uv], uv}); + ans.push_back({depth[uk], uk}); + ans.push_back({depth[vk], vk}); + sort(ans.rbegin(), ans.rend()); + + rets.push_back(ans[0].second); + } + + return rets; + } +}; diff --git a/Tree/2277.Closest-Node-to-Path-in-Tree/Readme.md b/Tree/2277.Closest-Node-to-Path-in-Tree/Readme.md index 706a92508..f6633c046 100644 --- a/Tree/2277.Closest-Node-to-Path-in-Tree/Readme.md +++ b/Tree/2277.Closest-Node-to-Path-in-Tree/Readme.md @@ -1,5 +1,13 @@ ### 2277.Closest-Node-to-Path-in-Tree +#### 解法1:DFS + 本题的时间复杂度要求是o(N^2),所以常规解法是,从node开始dfs得到所有节点到node的距离dist2node。然后从start开始dfs整棵树,对于能够通往end的这个分支上的节点,取最小的dist2node。 本题还有一种比较精彩的解法。先遍历所有的点作为起点,dfs整棵树,这样得到全局的matrix[i][j]表示任意两点之间的距离。然后对于start,我们遍历它的邻居j,发现如果有```matrix[start][end]==matrix[j][end]+1```,说明j是位于从start到end的路径上。依次递归下去,就能直接从start走向end,沿途中取最小的matrix[j][node]. + +#### 解法2:LCA + Binary Lifting +一个非常好用的结论:在一棵树里,w点到u->v路径最近的点,其实就是以下三个点里深度最大的那一个:lca(u,v), lca(u,w), lca(v,w) + +我们用binary lifting模板就可以很容易地得到这三个点的位置,取depth最大的那个即可。 + diff --git a/Tree/2581.Count-Number-of-Possible-Root-Nodes/2581.Count-Number-of-Possible-Root-Nodes.cpp b/Tree/2581.Count-Number-of-Possible-Root-Nodes/2581.Count-Number-of-Possible-Root-Nodes.cpp new file mode 100644 index 000000000..a35eb63e7 --- /dev/null +++ b/Tree/2581.Count-Number-of-Possible-Root-Nodes/2581.Count-Number-of-Possible-Root-Nodes.cpp @@ -0,0 +1,52 @@ +class Solution { + vector next[100005]; + unordered_set guess[100005]; + int k; + int ret = 0; +public: + int rootCount(vector>& edges, vector>& guesses, int k) { + this->k = k; + int n = edges.size()+1; + for (auto& e: edges) + { + next[e[0]].push_back(e[1]); + next[e[1]].push_back(e[0]); + } + for (auto& g: guesses) + guess[g[0]].insert(g[1]); + + int count = dfs(0, -1); + + dfs2(0, -1, count); + + return ret; + } + + int dfs(int cur, int parent) + { + int count = 0; + for (int nxt: next[cur]) + { + if (nxt==parent) continue; + count += dfs(nxt, cur); + if (guess[cur].find(nxt)!=guess[cur].end()) + count +=1; + } + return count; + } + + void dfs2(int cur, int parent, int count) + { + if (count >= k) ret++; + for (int nxt: next[cur]) + { + if (nxt==parent) continue; + int temp = count; + if (guess[cur].find(nxt)!=guess[cur].end()) + temp -= 1; + if (guess[nxt].find(cur)!=guess[nxt].end()) + temp += 1; + dfs2(nxt, cur, temp); + } + } +}; diff --git a/Tree/2581.Count-Number-of-Possible-Root-Nodes/Readme.md b/Tree/2581.Count-Number-of-Possible-Root-Nodes/Readme.md new file mode 100644 index 000000000..b6495713f --- /dev/null +++ b/Tree/2581.Count-Number-of-Possible-Root-Nodes/Readme.md @@ -0,0 +1,9 @@ +### 2581.Count-Number-of-Possible-Root-Nodes + +既然题目问的是“有多少节点作为根符合要求”,那么我们自然就会思考遍历每个节点作为根的情况。因为节点总数是1e5,所以我们只能用线性的时间遍历完整棵树,并且对于每个根的情况下,用o(1)的时候做出判断。 + +对于这种题目,有一种常见的套路就是“移根”。假设当前节点A作为根时,答案为a,那么以A的某个邻接节点B未做根时,答案能否快速从a转化而来呢? + +假设当前节点A作为根时,它对应的guesses里面有x个顺序的猜想(猜对了),y个逆序的猜想(猜错了)。那么我们转而考虑以B为根时,顺逆序唯一改变的边其实就只有AB之间的路径。所以如果AB边原本是一个顺序的猜想,那么此刻就会变成逆序;如果AB边原本是一个逆序的猜想,那么此刻就会变成顺序。 + +所以本题的做法就是,先以任意节点(比如说0)为根,一遍dfs计算有多少正确的guess,假设叫做count。然后递归处理它相邻的节点作为根的情况,只需要考察这条相邻边的正逆序变化改变了多少猜想,更新count即可。 diff --git a/Tree/2858.Minimum-Edge-Reversals-So-Every-Node-Is-Reachable/2858.Minimum-Edge-Reversals-So-Every-Node-Is-Reachable.cpp b/Tree/2858.Minimum-Edge-Reversals-So-Every-Node-Is-Reachable/2858.Minimum-Edge-Reversals-So-Every-Node-Is-Reachable.cpp new file mode 100644 index 000000000..04a9aeeb6 --- /dev/null +++ b/Tree/2858.Minimum-Edge-Reversals-So-Every-Node-Is-Reachable/2858.Minimum-Edge-Reversals-So-Every-Node-Is-Reachable.cpp @@ -0,0 +1,53 @@ +class Solution { + vector> next[100005]; + vectorrets; +public: + vector minEdgeReversals(int n, vector>& edges) + { + for (auto& edge: edges) + { + int a = edge[0], b = edge[1]; + next[a].push_back({b, 1}); + next[b].push_back({a, -1}); + } + + int count = dfs1(0, -1); + + rets.resize(n); + + dfs2(0, -1, count); + + return rets; + } + + int dfs1(int cur, int parent) + { + int ret = 0; + for (auto& [nxt, dir]: next[cur]) + { + if (nxt==parent) continue; + if (dir==1) + ret += dfs1(nxt, cur); + else + { + ret += dfs1(nxt, cur) + 1; + } + } + return ret; + } + + void dfs2(int cur, int parent, int count) + { + rets[cur] = count; + for (auto& [nxt, dir]: next[cur]) + { + if (nxt==parent) continue; + if (dir == 1) + dfs2(nxt, cur, count+1); + else + dfs2(nxt, cur, count-1); + } + } + + +}; diff --git a/Tree/2858.Minimum-Edge-Reversals-So-Every-Node-Is-Reachable/Readme.md b/Tree/2858.Minimum-Edge-Reversals-So-Every-Node-Is-Reachable/Readme.md new file mode 100644 index 000000000..ac266b68d --- /dev/null +++ b/Tree/2858.Minimum-Edge-Reversals-So-Every-Node-Is-Reachable/Readme.md @@ -0,0 +1,7 @@ +### 2858.Minimum-Edge-Reversals-So-Every-Node-Is-Reachable + +典型的移根技巧。 + +先用一遍DFS,以node 0为根遍历全树,计算node的reversal edge的数目count. + +然后第二遍DFS,从node 0开始。当dfs从节点i转移至邻接的节点j时,以节点i为根的树的reversal edge count,与节点j为根的树的reversal edge count,其实只相差了"i->j"这条边而已。如果这条边对于i而言是顺边,那么对于j而言就是逆边。反之亦然。所以他们之间的结果只是相差+1/-1而已。 diff --git a/Tree/2920.Maximum-Points-After-Collecting-Coins-From-All-Nodes/2920.Maximum-Points-After-Collecting-Coins-From-All-Nodes.cpp b/Tree/2920.Maximum-Points-After-Collecting-Coins-From-All-Nodes/2920.Maximum-Points-After-Collecting-Coins-From-All-Nodes.cpp new file mode 100644 index 000000000..9238a78ac --- /dev/null +++ b/Tree/2920.Maximum-Points-After-Collecting-Coins-From-All-Nodes/2920.Maximum-Points-After-Collecting-Coins-From-All-Nodes.cpp @@ -0,0 +1,53 @@ +class Solution { + int memo[100005][14]; + vectornext[100005]; +public: + int maximumPoints(vector>& edges, vector& coins, int k) + { + int n = edges.size()+1; + for (int i=0; i& coins, int k) + { + if (reduced >= 13) reduced = 13; + + if (memo[cur][reduced]!=INT_MIN/2) + return memo[cur][reduced]; + + int sum1 = helper(coins[cur], reduced) - k; + for (int nxt: next[cur]) + { + if (nxt == parent) continue; + sum1 += dfs(nxt, cur, reduced, coins, k); + } + + int sum2 = helper(coins[cur], reduced)/2; + for (int nxt: next[cur]) + { + if (nxt == parent) continue; + sum2 += dfs(nxt, cur, reduced+1, coins, k); + } + + memo[cur][reduced] = max(sum1, sum2); + return memo[cur][reduced]; + } +}; diff --git a/Tree/2920.Maximum-Points-After-Collecting-Coins-From-All-Nodes/Readme.md b/Tree/2920.Maximum-Points-After-Collecting-Coins-From-All-Nodes/Readme.md new file mode 100644 index 000000000..4bc7d7ee3 --- /dev/null +++ b/Tree/2920.Maximum-Points-After-Collecting-Coins-From-All-Nodes/Readme.md @@ -0,0 +1,5 @@ +### 2920.Maximum-Points-After-Collecting-Coins-From-All-Nodes + +常规的DFS。对于每个节点,我们需要知道它的祖先节点们总共做了几次“减半”操作,才能确定自身能够得到多少coins。所以DFS的参数里必须包含这个量。 + +对于DFS而言,我们总是与记忆化结合,可以优化时间复杂度。根据参数,记忆化数组需要的维度是`T*N`,其中T是对每个节点而言可能做多的“减半”操作次数。注意到任意节点的初始coins是1e4,这就意味着祖先最多累积13次减半操作,就可以让自身的coins变为零而不再变化。所以DFS的空间复杂度是可以接受的。 diff --git a/Tree/2925.Maximum-Score-After-Applying-Operations-on-a-Tree/2925.Maximum-Score-After-Applying-Operations-on-a-Tree.cpp b/Tree/2925.Maximum-Score-After-Applying-Operations-on-a-Tree/2925.Maximum-Score-After-Applying-Operations-on-a-Tree.cpp new file mode 100644 index 000000000..39a7c0038 --- /dev/null +++ b/Tree/2925.Maximum-Score-After-Applying-Operations-on-a-Tree/2925.Maximum-Score-After-Applying-Operations-on-a-Tree.cpp @@ -0,0 +1,51 @@ +using LL = long long; +class Solution { + vectornext[20005]; + LL subtree[20005]; + vectorvalues; +public: + long long maximumScoreAfterOperations(vector>& edges, vector& values) + { + this->values = values; + for (auto& edge: edges) + { + int a = edge[0], b = edge[1]; + next[a].push_back(b); + next[b].push_back(a); + } + + dfs0(0, -1); + return dfs(0, -1); + + } + + LL dfs0(int cur, int parent) + { + LL sum = values[cur]; + for (int nxt: next[cur]) + { + if (nxt==parent) continue; + sum += dfs0(nxt, cur); + } + subtree[cur] = sum; + return sum; + } + + + LL dfs(int cur, int parent) + { + if (next[cur].size()==1 && cur!=0) + { + return 0; + } + + LL sum = values[cur]; + for (int nxt: next[cur]) + { + if (nxt==parent) continue; + sum += dfs(nxt, cur); + } + + return max(sum, subtree[cur]-values[cur]); + } +}; diff --git a/Tree/2925.Maximum-Score-After-Applying-Operations-on-a-Tree/Readme.md b/Tree/2925.Maximum-Score-After-Applying-Operations-on-a-Tree/Readme.md new file mode 100644 index 000000000..d3768cfd2 --- /dev/null +++ b/Tree/2925.Maximum-Score-After-Applying-Operations-on-a-Tree/Readme.md @@ -0,0 +1,9 @@ +### 2925.Maximum-Score-After-Applying-Operations-on-a-Tree + +我们令dfs(cur)表示以cur为根的子树保持healthy时,能够取得的最高分。 + +我们容易发现,从root一路往下时,只要在某个节点node采取了“不取”的策略,那么之后就没有继续往下走的必要了。因为从root到node再到它的任何一个leaf,这个path sum肯定不会是零。所以我们必然会贪心地将node以下所有节点的value都取走。 + +所以我们在dfs的过程中,如果遍历到了某个节点,其隐含的意思就是从root到node之间的路径都“扫荡”光了。此时如果node依然采用了“取”的策略,那么我们必须保证node的所有子树path都是healthy的才行。于是就是递归处理dfs(nxt)即可。 + +边界条件是对于leaf node,它必须不取,否则连它也取的话,则意味着从root到leaf的path每个节点都取光了,必然不是healthy。 diff --git a/Tree/2973.Find-Number-of-Coins-to-Place-in-Tree-Nodes/2973.Find-Number-of-Coins-to-Place-in-Tree-Nodes.cpp b/Tree/2973.Find-Number-of-Coins-to-Place-in-Tree-Nodes/2973.Find-Number-of-Coins-to-Place-in-Tree-Nodes.cpp new file mode 100644 index 000000000..333479b16 --- /dev/null +++ b/Tree/2973.Find-Number-of-Coins-to-Place-in-Tree-Nodes/2973.Find-Number-of-Coins-to-Place-in-Tree-Nodes.cpp @@ -0,0 +1,46 @@ +using LL = long long; +class Solution { + vectornext[20005]; + vectorchildren[20005]; + vectorrets; +public: + vector placedCoins(vector>& edges, vector& cost) + { + for (auto& edge: edges) + { + int a = edge[0], b = edge[1]; + next[a].push_back(b); + next[b].push_back(a); + } + + rets.resize(cost.size()); + dfs(0, -1, cost); + return rets; + } + + void dfs(int cur, int parent, vector&cost) + { + vectortemp; + for (int nxt: next[cur]) + { + if (nxt==parent) continue; + dfs(nxt, cur, cost); + for (int x:children[nxt]) + temp.push_back(x); + } + temp.push_back(cost[cur]); + + sort(temp.begin(), temp.end()); + int n = temp.size(); + if (n < 3) + rets[cur] = 1; + else + rets[cur] = max(0LL, max(temp[n-3]*temp[n-2]*temp[n-1], temp[0]*temp[1]*temp[n-1])); + + if (n>=1) children[cur].push_back(temp[0]); + if (n>=2) children[cur].push_back(temp[1]); + if (n>=5) children[cur].push_back(temp[n-3]); + if (n>=4) children[cur].push_back(temp[n-2]); + if (n>=3) children[cur].push_back(temp[n-1]); + } +}; diff --git a/Tree/2973.Find-Number-of-Coins-to-Place-in-Tree-Nodes/Readme.md b/Tree/2973.Find-Number-of-Coins-to-Place-in-Tree-Nodes/Readme.md new file mode 100644 index 000000000..1b67960b6 --- /dev/null +++ b/Tree/2973.Find-Number-of-Coins-to-Place-in-Tree-Nodes/Readme.md @@ -0,0 +1,14 @@ +### 2973.Find-Number-of-Coins-to-Place-in-Tree-Nodes + +本题只需要常规的DFS,对于每个节点,假设它的子树的所有的cost都收集在了temp里并保持有序,那么根据题意,收益其实就是这两者的最大值: +``` +max(temp[n-1]*temp[n-2]*temp[n-3], temp[0]*temp[1]*temp[n-1]); +``` +这是因为,三元素乘积的最大值,要么是三个最大正数的乘积,要么是两个最小负数和一个最大正数的乘积。对于前者,我们只需要盲目地取最大的三个数即可(如果不存在三个正数,那么它自然也不会是最优解);对于后者,我们也只需要盲目地取两个最小值和一个最大值即可(如果不存在两个负数,那么它自然也不会是最优解)。 + +综上,我们最多只用到了temp里的五个元素即可。并且在向上传递时,也只需要最多返回五个元素即可。分别是: +1. 最小值temp[0],当n>=1 +2. 次小值temp[1],当n>=2 +3. 第三大值temp[n-3],当n>=5时才能保证该元素不与2重复。 +4. 第二大值temp[n-2],当n>=4时才能保证该元素不与2重复。 +5. 第一大值temp[n-1],当n>=3时才能保证该元素不与2重复。 diff --git a/Tree/3203.Find-Minimum-Diameter-After-Merging-Two-Trees/3203.Find-Minimum-Diameter-After-Merging-Two-Trees.cpp b/Tree/3203.Find-Minimum-Diameter-After-Merging-Two-Trees/3203.Find-Minimum-Diameter-After-Merging-Two-Trees.cpp new file mode 100644 index 000000000..9039d329e --- /dev/null +++ b/Tree/3203.Find-Minimum-Diameter-After-Merging-Two-Trees/3203.Find-Minimum-Diameter-After-Merging-Two-Trees.cpp @@ -0,0 +1,63 @@ +class Solution { +public: + int minimumDiameterAfterMerge(vector>& edges1, vector>& edges2) + { + int a = treeDiameter(edges1); + int b = treeDiameter(edges2); + return max({(a+1)/2+(b+1)/2+1, a, b}); + } + + int treeDiameter(vector>& edges) + { + int n = edges.size()+1; + vector>next(n); + for (auto edge: edges) + { + next[edge[0]].push_back(edge[1]); + next[edge[1]].push_back(edge[0]); + } + auto t1 = bfs(next, 0); + auto t2 = bfs(next, t1.first); + return t2.second; + } + + pair bfs(vector>&next, int u) + { + int n = next.size(); + vectordis(n, -1); + queue q; + q.push(u); + + dis[u] = 0; + + while (!q.empty()) + { + int t = q.front(); + q.pop(); + + for (auto it = next[t].begin(); it != next[t].end(); it++) + { + int v = *it; + if (dis[v] == -1) + { + q.push(v); + dis[v] = dis[t] + 1; + } + } + } + + int maxDis = 0; + int nodeIdx = 0; + + for (int i = 0; i < n; i++) + { + if (dis[i] > maxDis) + { + maxDis = dis[i]; + nodeIdx = i; + } + } + return make_pair(nodeIdx, maxDis); + } + +}; diff --git a/Tree/3203.Find-Minimum-Diameter-After-Merging-Two-Trees/Readme.md b/Tree/3203.Find-Minimum-Diameter-After-Merging-Two-Trees/Readme.md new file mode 100644 index 000000000..07ab373b9 --- /dev/null +++ b/Tree/3203.Find-Minimum-Diameter-After-Merging-Two-Trees/Readme.md @@ -0,0 +1,9 @@ +### 3203.Find-Minimum-Diameter-After-Merging-Two-Trees + +关于树的最大路径(直径),我们已经有`1245.Tree-Diameter`的做法。 + +在本题中,我们要求联通后的树的直径最小,那么联通的两个点,必定在各自树的直径的中点位置。这可以用简单的反证法推理:假设联通点是树的节点A,那么根据直径定义,我们需要寻找A到树里离它的最远端点。我们可以至少找到这样一条路径:A先到直径的中点M,再从M走到直径的一个端点(固定为d/2长度)。这条路径显然总长于将A点直接设置于M处的方案。故联通点设置在M处,可以最小化A离它最短端点。 + +所以本题的一个解就是 `ceil(d1/2) + ceil(d2/2) + 1`。 + +但是注意,联通树的最大直径不一定要一定同时经过树1和树2。比如,如果树1远远大于树2,那么d1本身就可能是联通树的最大直径。类似的d2也是。所以本题是要在三个可能答案中取最大的那个。 diff --git a/Tree/3715.Sum-of-Perfect-Square-Ancestors/3715.Sum-of-Perfect-Square-Ancestors.cpp b/Tree/3715.Sum-of-Perfect-Square-Ancestors/3715.Sum-of-Perfect-Square-Ancestors.cpp new file mode 100644 index 000000000..469d0fad6 --- /dev/null +++ b/Tree/3715.Sum-of-Perfect-Square-Ancestors/3715.Sum-of-Perfect-Square-Ancestors.cpp @@ -0,0 +1,63 @@ +class Solution { + vectorfreq; + vectorsf; + long long ret = 0; + vector>next; +public: + void dfs(int cur, int parent){ + ret += freq[sf[cur]]; + freq[sf[cur]]+=1; + for (int nxt: next[cur]) { + if (nxt==parent) continue; + dfs(nxt, cur); + } + freq[sf[cur]]--; + } + + vectorComputeSpf(int N) { + vectorspf(N+1); + for (int i = 0; i <= N; ++i) spf[i] = i; + for (int i = 2; i * i <= N; ++i) { + if (spf[i] == i) { + for (int j = i * i; j <= N; j += i) { + if (spf[j] == j) spf[j] = i; + } + } + } + return spf; + } + + + long long sumOfAncestors(int n, vector>& edges, vector& nums) { + int maxA = *max_element(nums.begin(), nums.end()); + vectorspf = ComputeSpf(maxA); + + freq.resize(maxA+1); + sf.resize(n); + for (int i=0; i1) { + int p = spf[x]; + int cnt = 0; + while (x%p==0) { + x/=p; + cnt++; + } + if (cnt%2==1) ret*=p; + } + sf[i] = ret; + } + + next.resize(n); + for (auto& e: edges) { + int u = e[0], v = e[1]; + next[u].push_back(v); + next[v].push_back(u); + } + + dfs(0, -1); + + return ret; + } +}; diff --git a/Tree/3715.Sum-of-Perfect-Square-Ancestors/Readme.md b/Tree/3715.Sum-of-Perfect-Square-Ancestors/Readme.md new file mode 100644 index 000000000..244a9ef21 --- /dev/null +++ b/Tree/3715.Sum-of-Perfect-Square-Ancestors/Readme.md @@ -0,0 +1,9 @@ +### 3715.Sum-of-Perfect-Square-Ancestors + +本题的关键点在于,如果a与b的乘积是一个完全平方数,那么将a除去属于完全平方数的因子记作aa,将b除去属于完全平方数的因子记作bb,那么aa与bb必须相等。因为此时aa与bb都不含任何重复的质因子,那么`aa*bb`就不可能再能被开方。 + +所以我们用dfs探索这棵树的每一条支路,对于沿途所经历的节点x,都把属于完全平方数的因子剥离,只剩下其“非完全平方数因子”sf[nums[x]]放入一个Hash表中。对于dfs过程中的下一个节点i,进行同样的剥离,得到sf[nums[i]],查看这个数是否已经存在于Hash之中。Hash有多少即意味着节点i的祖先里有多少个节点能与之配对相乘得到完全平方数。 + +剥离完全平方数的方法:枚举该数的所有质因子,如果此质因子出现过多次,只保留一次。剩下的部分就是“非完全平方数因子”。 + +为了加快“枚举该数的所有质因子”,我们可以在计算埃氏筛的同时,存储所有数的最小质因子spf[x]。这样对于x的因数分解,我们可以快速找到突破口,即第一个质因子spf[x]. diff --git a/Tree/3772.Maximum-Subgraph-Score-in-a-Tree/3772.Maximum-Subgraph-Score-in-a-Tree.cpp b/Tree/3772.Maximum-Subgraph-Score-in-a-Tree/3772.Maximum-Subgraph-Score-in-a-Tree.cpp new file mode 100644 index 000000000..9952520c5 --- /dev/null +++ b/Tree/3772.Maximum-Subgraph-Score-in-a-Tree/3772.Maximum-Subgraph-Score-in-a-Tree.cpp @@ -0,0 +1,46 @@ +class Solution { + vectornext[100005]; + int w[100005]; + int dp1[100005]; + int dp2[100005]; + +public: + void dfs1(int cur, int parent) { + dp1[cur] = w[cur]; + for (int nxt: next[cur]) { + if (nxt==parent) continue; + dfs1(nxt, cur); + dp1[cur] += max(0, dp1[nxt]); + } + } + + void dfs2(int cur, int parent) { + for (int nxt: next[cur]) { + if (nxt==parent) continue; + dp2[nxt] = max(0, dp1[cur]-max(0, dp1[nxt]) + dp2[cur]); + dfs2(nxt, cur); + } + } + + vector maxSubgraphScore(int n, vector>& edges, vector& good) { + for (int i=0; irets(n); + for (int i=0; i parent这条edge做切割,一部分是以node为根的子树部分,另一部分是以parent往外发散的部分(可以看做是以parent为根的子树)。 + +对于前者,典型的子树递归,这显然是可以用dfs求解的。我们令dfs1(cur)表示以cur出发往下发展,在其为根的子树里得分最大的subgraph。显然有 +``` +dfs1(cur) = score[cur] + sum{max(0, dfs1(child)} over all children. +``` +对于后者,我们就需要用到移根的策略进行递归。我们令dfs2(cur)表示从cur往上走能得到最大score的subgraph(可看做以parent为根的另一半子树),并将最大得分记作dp2[cur]。我们考察cur的一个child、对于dp2[child]而言,它在几何上包括两部分:cur子树里去掉child分支的subgraph(即dp1[cur]-dp1[child]),以及更靠外的dp2[cur]. 所以有两者的移根关系 +``` +dfs2(child) = max(0, max(0, dp1[cur]-dp1[child]) + dp2[cur]); +``` +这里面dfs2需要注意两个细节。 + +首先dp1[cur]可能本来就不包括child分支(如果当child分支可能是负数时),所以我们在算cur为根、但除child分支之外的subgraph时,应该用 max(0, dp1[cur]-dp1[child]). + +其次dp2[child]不能为负数。即如果从cur往上走的subgraph最大值依然为负数,那么索性dp2[child]的贡献应该降为零。 + +最终每个node所能扩展的subgraph构成的最大值就是dp1[node]+dp2[node]. diff --git a/Trie/208.Implement-Trie--Prefix-Tree/Readme.md b/Trie/208.Implement-Trie--Prefix-Tree/Readme.md index b00c62ee4..d7aed2b8b 100644 --- a/Trie/208.Implement-Trie--Prefix-Tree/Readme.md +++ b/Trie/208.Implement-Trie--Prefix-Tree/Readme.md @@ -7,4 +7,4 @@ 4. 在Trie树中找指定的前缀(不需要找到叶子节点) -[Leetcode Link](https://leetcode.com/problems/implement-trie--prefix-tree) \ No newline at end of file +[Leetcode Link](https://leetcode.com/problems/implement-trie-prefix-tree) \ No newline at end of file diff --git a/Trie/2935.Maximum-Strong-Pair-XOR-II/2935.Maximum-Strong-Pair-XOR-II.cpp b/Trie/2935.Maximum-Strong-Pair-XOR-II/2935.Maximum-Strong-Pair-XOR-II.cpp new file mode 100644 index 000000000..68771f3e6 --- /dev/null +++ b/Trie/2935.Maximum-Strong-Pair-XOR-II/2935.Maximum-Strong-Pair-XOR-II.cpp @@ -0,0 +1,80 @@ +class Solution { + class TrieNode + { + public: + TrieNode* next[2]; + int count = 0; + TrieNode(){ + for (int i=0; i<2; i++) + next[i] = NULL; + } + }; + TrieNode* root; + +public: + int maximumStrongPairXor(vector& nums) + { + root = new TrieNode(); + + sort(nums.begin(), nums.end()); + int j = 0; + int ret = INT_MIN/2; + for (int i=0; i=0; k--) + { + int bit = ((num>>k)&1); + if (node->next[bit]==NULL) + node->next[bit] = new TrieNode(); + node = node->next[bit]; + node->count+=1; + } + } + + void remove(int num) + { + TrieNode* node = root; + for (int k=31; k>=0; k--) + { + int bit = ((num>>k)&1); + node = node->next[bit]; + node->count-=1; + } + } + + int dfs(int num, TrieNode* node, int k) + { + if (k==-1) return 0; + int bit = (num>>k)&1; + if (bit == 0) + { + if (node->next[1] && node->next[1]->count > 0) + return dfs(num, node->next[1], k-1) + (1<next[0] && node->next[0]->count > 0) + return dfs(num, node->next[0], k-1); + } + else + { + if (node->next[0] && node->next[0]->count > 0) + return dfs(num, node->next[0], k-1) + (1<next[1] && node->next[1]->count > 0) + return dfs(num, node->next[1], k-1); + } + + return INT_MIN/2; + } +}; diff --git a/Trie/2935.Maximum-Strong-Pair-XOR-II/Readme.md b/Trie/2935.Maximum-Strong-Pair-XOR-II/Readme.md new file mode 100644 index 000000000..78d055414 --- /dev/null +++ b/Trie/2935.Maximum-Strong-Pair-XOR-II/Readme.md @@ -0,0 +1,8 @@ +### 2935.Maximum-Strong-Pair-XOR-II + +观察`|x - y| <= min(x, y)`, 假设x是其中较大的那个,很容易推得`y<=x<=2y`。所以我们将nums从小到大排序之后,考察某个数作为y时,可以将一段滑窗内的元素[y,2y]加入一个集合,根据y在这个集合里找能与y异或得到最大值的那个元素。并且,我们发现随着y的移动,这个滑窗的移动也是单调的,说明每个式子进入集合与移出集合都只需要操作一次,时间复杂度是o(N). + +对于求最大XOR pair而言,这样的“集合”必然是用Trie。将符合y条件的数字加入Trie之后,从高到低遍历y的每个bit位:如果y的bit是1,那么我们就选择在Trie里向下走0的分支(如果存在的话),反之我们就在Trie里向下走1的分支。这样走到底之后,你选择的路径所对应的数字就是能与y异或得到最大的结果。 + +PS:在Trie里实时加入一条路径很简单,但是如何移除一条路径呢?显然不能盲目地删除该路径上的所有节点,因为它可能被其他路径共享。技巧是,给每个节点标记一个计数器。加入一条路径时,将沿路的节点的计数器加一;反之删除一个条路径时,将沿路的节点的计数器减一。如果某个节点的计数器为零,意味着该分支已经“虚拟地”从Trie里移出了,就不能再被访问了。 + diff --git a/Trie/2977.Minimum-Cost-to-Convert-String-II/2977.Minimum-Cost-to-Convert-String-II_v1.cpp b/Trie/2977.Minimum-Cost-to-Convert-String-II/2977.Minimum-Cost-to-Convert-String-II_v1.cpp new file mode 100644 index 000000000..f3b2e86c7 --- /dev/null +++ b/Trie/2977.Minimum-Cost-to-Convert-String-II/2977.Minimum-Cost-to-Convert-String-II_v1.cpp @@ -0,0 +1,66 @@ +using LL = long long; +class Solution { +public: + long long minimumCost(string source, string target, vector& original, vector& changed, vector& cost) + { + unordered_setSet; + Set.insert(original.begin(), original.end()); + Set.insert(changed.begin(), changed.end()); + unordered_mapMap; + int idx = 0; + for (string x: Set) + { + Map[x] = idx; + idx++; + } + + int n = Set.size(); + LL d[n][n]; + for (int i=0; idp(m+1); + dp[0] = 0; + + for (int i=1; i<=m; i++) + { + dp[i] = LLONG_MAX/2; + if (source[i]==target[i]) + dp[i] = dp[i-1]; + + string a; + string b; + for (int j=i; j>=1; j--) + { + a = source.substr(j,1) + a; + b = target.substr(j,1) + b; + + if (Map.find(a)!=Map.end() && Map.find(b)!=Map.end()) + dp[i] = min(dp[i], dp[j-1] + d[Map[a]][Map[b]]); + } + } + + if (dp[m]==LLONG_MAX/2) return -1; + + return dp[m]; + } +}; diff --git a/Trie/2977.Minimum-Cost-to-Convert-String-II/2977.Minimum-Cost-to-Convert-String-II_v2.cpp b/Trie/2977.Minimum-Cost-to-Convert-String-II/2977.Minimum-Cost-to-Convert-String-II_v2.cpp new file mode 100644 index 000000000..479d6b19c --- /dev/null +++ b/Trie/2977.Minimum-Cost-to-Convert-String-II/2977.Minimum-Cost-to-Convert-String-II_v2.cpp @@ -0,0 +1,102 @@ +using LL = long long; +class TrieNode +{ + public: + TrieNode* next[26]; + int idx; + TrieNode() + { + for (int i=0; i<26; i++) + next[i] = NULL; + idx = -1; + } +}; + +class Solution { + TrieNode* root = new TrieNode(); +public: + long long minimumCost(string source, string target, vector& original, vector& changed, vector& cost) + { + for (auto& s: original) + reverse(s.begin(), s.end()); + + for (auto& s: changed) + reverse(s.begin(), s.end()); + + unordered_setSet; + Set.insert(original.begin(), original.end()); + Set.insert(changed.begin(), changed.end()); + unordered_mapMap; + int idx = 0; + for (string word: Set) + { + Map[word] = idx; + + TrieNode* node = root; + for (char ch: word) + { + if (node->next[ch-'a']==NULL) + node->next[ch-'a'] = new TrieNode(); + node = node->next[ch-'a']; + } + node->idx = idx; + + idx++; + } + + int n = Set.size(); + LL d[n][n]; + for (int i=0; idp(m+1); + dp[0] = 0; + + for (int i=1; i<=m; i++) + { + dp[i] = LLONG_MAX/2; + if (source[i]==target[i]) + dp[i] = dp[i-1]; + + TrieNode* node1 = root; + TrieNode* node2 = root; + for (int j=i; j>=1; j--) + { + if (node1->next[source[j]-'a']==NULL) + break; + if (node2->next[target[j]-'a']==NULL) + break; + node1 = node1->next[source[j]-'a']; + node2 = node2->next[target[j]-'a']; + + int idx1 = node1->idx; + int idx2 = node2->idx; + if (idx1==-1 || idx2==-1) continue; + + dp[i] = min(dp[i], dp[j-1] + d[idx1][idx2]); + } + } + + if (dp[m]==LLONG_MAX/2) return -1; + + return dp[m]; + } +}; diff --git a/Trie/2977.Minimum-Cost-to-Convert-String-II/Readme.md b/Trie/2977.Minimum-Cost-to-Convert-String-II/Readme.md new file mode 100644 index 000000000..558cd466e --- /dev/null +++ b/Trie/2977.Minimum-Cost-to-Convert-String-II/Readme.md @@ -0,0 +1,21 @@ +### 2977.Minimum-Cost-to-Convert-String-II + +本题和`2976. Minimum Cost to Convert String I`类似的思路,只不过2976里构造的最短路径图的顶点是“字母”,而本题里图的顶点是“字符串”。我们用Floyd可以容易求出“original->changed”里出现过任意两个字符串之间的最小代价,将字符串离散化后(用hash表记录每种字符串的序号),记录在数组d[][]里。 + +然后考虑从source到target的转化,很明显这是一个动态规划。对于前i个字符的前缀而言,成功转化的关键在于i所在的字符串转化是什么?我们需要找到一个位置j stringIndices(vector& wordsContainer, vector& wordsQuery) + { + vector>arr; + for (int i=0; i&a, const pair&b) + { + if (a.first.size() != b.first.size()) + return a.first.size() < b.first.size(); + else + return a.second < b.second; + }); + + for (int i=arr.size()-1; i>=0; i--) + { + TrieNode* node = root; + string s = arr[i].first; + for (int j=s.size()-1; j>=0; j--) + { + if (node->next[s[j]-'a']==NULL) + node->next[s[j]-'a'] = new TrieNode(); + node = node->next[s[j]-'a']; + node->idx = arr[i].second; + } + } + + root->idx = arr[0].second; + vectorrets; + for (auto& query: wordsQuery) + { + TrieNode* node = root; + int ans = -1; + for (int i=query.size()-1; i>=0; i--) + { + if (node->next[query[i]-'a']!=NULL) + node = node->next[query[i]-'a']; + else + { + ans = node->idx; + break; + } + } + if (ans==-1) + ans = node->idx; + + rets.push_back(ans); + + } + + return rets; + } +}; diff --git a/Trie/3093.Longest-Common-Suffix-Queries/Readme.md b/Trie/3093.Longest-Common-Suffix-Queries/Readme.md new file mode 100644 index 000000000..1f07cba79 --- /dev/null +++ b/Trie/3093.Longest-Common-Suffix-Queries/Readme.md @@ -0,0 +1,9 @@ +### 3093.Longest-Common-Suffix-Queries + +在一堆字符串里面高效地寻找一个字符串(或它的前缀/后缀),显然我们会使用字典树的数据结构。 + +本题里面,我们在字典树里游走时,对于所处的节点,它可能被多个wordsContainer里面的字符串共享。那么对于每个节点,它究竟属于哪个字符串呢?根据题意,我们的选择依据是:先看总长度更小、再看出现的序号更小。因此,我们需要依据这个规则,给每个节点标记idx这个属性。所以对wordsQuery的某个后缀在字典树里游走完之后,它停留的节点的idx就是答案。 + +如果高效地给字典树的每个节点赋值idx呢?其实很简单,我们先将总长度更大的字符串加入字典树,再将总长度更小的字符串加入字典树。每次加入字符串时,idx的值就按加入字符串的index来。这样我们就发现,字符串长度小的自动会override每个节点的idx属性。同理,我们将wordsContainer序号更大的字符串先加入字典树,再将序号更小的字符串后加入字典树,这样每个节点的idx就会更新为相对更小的wordsContainer index。 + +综上,我们只需要将wordsContainer排序,按照“先看总长度更小、再看出现的序号更小”的原则排序,然后反序,按照后缀加入字典树。然后将wordsQuery的每个字符串按后缀在字典树里游走,最终停留在哪个节点,该节点的idx属性就是答案。 diff --git a/Trie/3632.Subarrays-with-XOR-at-Least-K/3632.Subarrays-with-XOR-at-Least-K.cpp b/Trie/3632.Subarrays-with-XOR-at-Least-K/3632.Subarrays-with-XOR-at-Least-K.cpp new file mode 100644 index 000000000..3ae7f66b6 --- /dev/null +++ b/Trie/3632.Subarrays-with-XOR-at-Least-K/3632.Subarrays-with-XOR-at-Least-K.cpp @@ -0,0 +1,58 @@ +class TrieNode { + public: + TrieNode* next[2]; + int count; + TrieNode() + { + for (int i=0; i<2; i++) + next[i]=NULL; + count = 0; + } +}; +class Solution { + void add(int x) { + TrieNode* node = root; + for (int i=0; i<31; i++) { + int b = ((x>>(30-i))&1); + if (!node->next[b]) + node->next[b] = new TrieNode(); + node = node->next[b]; + node->count += 1; + } + } + TrieNode* root; +public: + long long countXorSubarrays(vector& nums, int k) { + root = new TrieNode(); + int n = nums.size(); + + long long ret = 0; + int sum = 0; + add(sum); + for (int i=0; i>(30-j))&1); + int bitS = ((sum>>(30-j))&1); + int b = bitK^bitS; + if (bitK==0 && node->next[1-b]) { + ret += node->next[1-b]->count; + } + if (node->next[b]) + node = node->next[b]; + else { + flag = 0; + break; + } + } + if (flag) + ret += node->count; + + add(sum); + } + + return ret; + } +}; diff --git a/Trie/3632.Subarrays-with-XOR-at-Least-K/Readme.md b/Trie/3632.Subarrays-with-XOR-at-Least-K/Readme.md new file mode 100644 index 000000000..213c6e79f --- /dev/null +++ b/Trie/3632.Subarrays-with-XOR-at-Least-K/Readme.md @@ -0,0 +1,8 @@ +### 3632.Subarrays-with-XOR-at-Least-K + +我们考虑这样的操作:将所有前缀的XOR_SUM放入一个字典树。这个字典树有31层,对应这整型数字的31个bit。每个节点有一个count属性,记录该节点的子树有个多少叶子节点(即当前有多少个前缀的XOR_SUM经过这个节点)。 + +我们逐个考察每个前缀:假设[0:i]的前缀异或值转化为二进制数组是bitsS,k转化为二进制数组是bitsK。那么我们从高到低考察每个bit位j。易知始终令`b=bitsS[j]^bitsK[j]`,意味着如果某个前缀异或值的第j位是b的话,那么该前缀与[0:i]前缀之间的subarray的异或值到目前为止恰好紧贴着K。依次类推,我们可以在字典树里从高往低遍历直至叶子节点,判断是有多少个前缀恰好与[0:i]分割出的子区间异或值为k。当然也有可能这条路径不存在,意味着没有这样的前缀。 + +除此之外,在上述的字典树的从跟到叶子节点的路径上,如果在某一层`bitsK[j]=0`,那么意味着如果该位置我们不选择上述的`b=bitsS[j]^bitsK[j]`,而是走另一个分支`1-b`,那么会导致由此访问得到的所有前缀、与[0:i]前缀之间的subarray的异或值在第j位上会变成了1,从而大于预期的`bitsK[j]=0`。于是从该分支到其下所有叶子节点的路径也都应该算入符合条件的计数中去。实际中,对于这种路径,我们直接对结果加上`node->next[1-b]->count`即可,而不用真的去递归遍历到底。 + diff --git a/Trie/3670.Maximum-Product-of-Two-Integers-With-No-Common-Bits/3670.Maximum-Product-of-Two-Integers-With-No-Common-Bits.cpp b/Trie/3670.Maximum-Product-of-Two-Integers-With-No-Common-Bits/3670.Maximum-Product-of-Two-Integers-With-No-Common-Bits.cpp new file mode 100644 index 000000000..793fee837 --- /dev/null +++ b/Trie/3670.Maximum-Product-of-Two-Integers-With-No-Common-Bits/3670.Maximum-Product-of-Two-Integers-With-No-Common-Bits.cpp @@ -0,0 +1,60 @@ +class TrieNode { + public: + TrieNode* next[2]; + TrieNode() + { + for (int i=0; i<2; i++) + next[i]=NULL; + } +}; + +class Solution { + TrieNode* root; +public: + void add(int x) { + TrieNode* node = root; + for (int i=0; i<31; i++) { + int b = ((x>>(30-i))&1); + if (!node->next[b]) + node->next[b] = new TrieNode(); + node = node->next[b]; + } + } + + int dfs(TrieNode* node, int i, int x) { + if (node==NULL) { + return -1; + } + if (i==31) return 0; + + int b = ((x>>(30-i))&1); + if (b==1) { + int ans = dfs(node->next[0], i+1, x); + if (ans!=-1) return ans; + else return -1; + } + else { + int ans1 = dfs(node->next[1], i+1, x); + if (ans1!=-1) + return (1<<(30-i))+ans1; + int ans2 = dfs(node->next[0], i+1, x); + if (ans2!=-1) + return ans2; + return -1; + } + } + + long long maxProduct(vector& nums) { + root = new TrieNode(); + int n = nums.size(); + + long long ret = 0; + for (int x: nums) { + int ans = dfs(root, 0, x); + if (ans!=-1) ret = max(ret, (long long)x*ans); + add(x); + } + + return ret; + } +}; diff --git a/Trie/3670.Maximum-Product-of-Two-Integers-With-No-Common-Bits/3670.Maximum-Product-of-Two-Integers-With-No-Common-Bits_v2.cpp b/Trie/3670.Maximum-Product-of-Two-Integers-With-No-Common-Bits/3670.Maximum-Product-of-Two-Integers-With-No-Common-Bits_v2.cpp new file mode 100644 index 000000000..ec2565411 --- /dev/null +++ b/Trie/3670.Maximum-Product-of-Two-Integers-With-No-Common-Bits/3670.Maximum-Product-of-Two-Integers-With-No-Common-Bits_v2.cpp @@ -0,0 +1,18 @@ +class Solution { +public: + long long maxProduct(vector& nums) { + int max_n = *max_element(begin(nums), end(nums)); + int k = log2(max_n)+1; + vector dp(1< 0) + ret += s.size()-j+1; + + if (s[i]=='a') count1--; + else if (s[i]=='b') count2--; + else if (s[i]=='c') count3--; + } + + return ret; + } +}; diff --git a/Two_Pointers/1358.Number-of-Substrings-Containing-All-Three-Characters/Readme.md b/Two_Pointers/1358.Number-of-Substrings-Containing-All-Three-Characters/Readme.md new file mode 100644 index 000000000..927942064 --- /dev/null +++ b/Two_Pointers/1358.Number-of-Substrings-Containing-All-Three-Characters/Readme.md @@ -0,0 +1,7 @@ +### 1358.Number-of-Substrings-Containing-All-Three-Characters + +我们固定滑窗的左端点i,向右探索右端点j。当我们发现移动到某处的j,使得[i:j]恰好至少包含a,b,c各一个的时候,那么意味着右端点其实可以直至在从j到n-1的任何位置,都满足条件。这样的区间有n-j个。 + +此时我们查看下一个i作为左端点,同样为了满足[i:j]恰好至少包含a,b,c各一个,j必然向右移动。同理,可以计算出以i为左端点、符合条件的区间的个数。 + +最终答案就是以每个i作为左端点时,符合条件的右端点的数目的总和。 diff --git a/Two_Pointers/1838.Frequency-of-the-Most-Frequent-Element/1838.Frequency-of-the-Most-Frequent-Element_v1.cpp b/Two_Pointers/1838.Frequency-of-the-Most-Frequent-Element/1838.Frequency-of-the-Most-Frequent-Element_v1.cpp new file mode 100644 index 000000000..a5744ea37 --- /dev/null +++ b/Two_Pointers/1838.Frequency-of-the-Most-Frequent-Element/1838.Frequency-of-the-Most-Frequent-Element_v1.cpp @@ -0,0 +1,29 @@ +using LL = long long; +class Solution { +public: + int maxFrequency(vector& nums, int k) + { + sort(nums.begin(), nums.end()); + int n = nums.size(); + nums.insert(nums.begin(), 0); + vectorpresum(n+1); + for (int i=1; i<=n; i++) + presum[i] = presum[i-1]+nums[i]; + + int i=1; + int ret = 0; + for (int j=1; j<=n; j++) + { + while (!isOK(nums, presum, i, j, k)) + i++; + ret = max(ret, j-i+1); + } + return ret; + } + + bool isOK(vector&nums, vector&presum, int i, int j, int k) + { + LL detla = (LL)nums[j]*(j-i+1) - (presum[j] - presum[i-1]); + return detla <= k; + } +}; diff --git a/Two_Pointers/1838.Frequency-of-the-Most-Frequent-Element/1838.Frequency-of-the-Most-Frequent-Element.cpp b/Two_Pointers/1838.Frequency-of-the-Most-Frequent-Element/1838.Frequency-of-the-Most-Frequent-Element_v2.cpp similarity index 100% rename from Two_Pointers/1838.Frequency-of-the-Most-Frequent-Element/1838.Frequency-of-the-Most-Frequent-Element.cpp rename to Two_Pointers/1838.Frequency-of-the-Most-Frequent-Element/1838.Frequency-of-the-Most-Frequent-Element_v2.cpp diff --git a/Two_Pointers/1838.Frequency-of-the-Most-Frequent-Element/Readme.md b/Two_Pointers/1838.Frequency-of-the-Most-Frequent-Element/Readme.md index 2e582f017..ccc19819d 100644 --- a/Two_Pointers/1838.Frequency-of-the-Most-Frequent-Element/Readme.md +++ b/Two_Pointers/1838.Frequency-of-the-Most-Frequent-Element/Readme.md @@ -1,8 +1,14 @@ ### 1838.Frequency-of-the-Most-Frequent-Element +#### 解法1: 首先需要明确,我们操作后最终得到的最高频率元素一定是数组中既有的元素。为什么?假设你可以通过操作,得到一个最高频率的元素是x,且x在原数组中没有出现过;那么你必然可以通过更少的一些操作,使得原数组里恰好比x小的元素y,也构造出相同的频次。因此我们不妨将nums按从小到大排序。 -那么这个最高频次的元素是什么呢?显然不一定是数组里既有的最高频次元素。我们必须逐个尝试一遍。假设我们通过不超过k次的操作,使nums[i]的频次最高,那么这些操作必然是作用在紧邻i前面的若干元素,使它们变成nums[i]。我们假设操作的范围是[j:i-1],需要的实际操作数就是```count = sum{nums[i]-nums[k]}, k=j,j+1, ... i-1``` +那么这个最高频次的元素是什么呢?显然不一定是数组里既有的最高频次元素。我们必须逐个尝试一遍。假设我们通过不超过k次的操作,使nums[i]的频次最高,那么这些操作必然是作用在紧邻i前面的若干元素,使它们都变成nums[i]。我们假设操作的范围是[j:i]。那么我们将这段区间内的数字都变成nums[i],所需要的操作就是`nums[i]*(i-j+1) - sum[j:i]`. 显然,如果我们实现准备好前缀和数组的话,那么这个操作数就可以o(1)求出。如果操作数大于k,那么我们必须将j右移减小区间,才有可能符合条件。 + +由此可以见,我们只要唯一个滑窗。对于每个i作为区间的右端点,我们不断移动左指针j使得区间[j:i]恰好符合要求,于是j-i+1就是将nums[i]为最高频次元素的次数。 + +#### 解法2: +接下来解释一种不需要presum的滑窗解法。同上,对于区间[j:i]需要的实际操作数,我们也可以写成```count = sum{nums[i]-nums[k]}, k=j,j+1, ... i-1```。 接下来我们考虑如果最终最高频次的元素是nums[i+1],那么我们如何高效地转移?假设需要操作的数的范围起点不变,即[j:i],那么总操作数的增量就是```count += (nums[i+1]-nums[i])*(i+1-j)```,也就是我们将nums[j:i-1]都变成nums[i]的基础上,再将这(i+1-j)个数都提升一个gap,变成nums[i+1]。此时如果count>k了,那么我们就要优先舍弃最前面的元素j,那么节省的操作数就是nums[i+1]-nums[j]。我们可能会舍弃若干个老元素并右移j,直至是的count<=k,那么此时我们就在题目的限制条件下,可以将nums[j:i]都变成了nums[i+1],即频次就是```i+1-j+1```. diff --git a/Two_Pointers/2730.Find-the-Longest-Semi-Repetitive-Substring/2730.Find-the-Longest-Semi-Repetitive-Substring.cpp b/Two_Pointers/2730.Find-the-Longest-Semi-Repetitive-Substring/2730.Find-the-Longest-Semi-Repetitive-Substring.cpp new file mode 100644 index 000000000..8b4516dde --- /dev/null +++ b/Two_Pointers/2730.Find-the-Longest-Semi-Repetitive-Substring/2730.Find-the-Longest-Semi-Repetitive-Substring.cpp @@ -0,0 +1,24 @@ +class Solution { +public: + int longestSemiRepetitiveSubstring(string s) + { + int n = s.size(); + int ret = 0; + int j = 0; + int count = 0; + for (int i=0; ii && s[j]==s[j-1]) < 2)) + { + count += (j>i && s[j]==s[j-1]); + j++; + } + ret = max(ret, j-i); + + if (i+1 countServers(int n, vector>& logs, int x, vector& queries) + { + for (int i=0; i>q; + for (int i=0; irets(q.size()); + unordered_mapMap; + int i = 0; + int j = 0; + for (auto qq: q) + { + int t = qq[0], idx = qq[1]; + while (j& nums, int k) + { + unordered_map>Map; + for (int i=0; iSet(s.begin(), s.end()); + for (int T = 1; T <= Set.size(); T++) + { + int len = T * k; + vectorfreq(26,0); + int j = 0; + for (int i=0; i+len-1& freq, int k) + { + for (int x: freq) + { + if (x != k && x != 0) + return false; + } + return true; + } +}; diff --git a/Two_Pointers/2953.Count-Complete-Substrings/Readme.md b/Two_Pointers/2953.Count-Complete-Substrings/Readme.md new file mode 100644 index 000000000..19d1b846e --- /dev/null +++ b/Two_Pointers/2953.Count-Complete-Substrings/Readme.md @@ -0,0 +1,5 @@ +### 2953.Count-Complete-Substrings + +很显然,第一步是将原字符串切割成若干个区间,我们只考虑那些“相邻字符大小不超过2”的那些区间. + +接下来,我们需要计算每个通过初筛区间里,再有多少个符合条件的substring,即要求substring里出现的字符的频次都是k。我们注意到字符的种类只有26种,如果只出现一种字符,那长度就是k;如果出现两种字符,那长度就是2k,以此类推,我们发现可以遍历出现字符的种类数目,然后用一个固定长度的滑窗来判定是否存在substring符合条件。滑窗的运动过程中,只要维护一个hash表即可。 diff --git a/Two_Pointers/2958.Length-of-Longest-Subarray-With-at-Most-K-Frequency/2958.Length-of-Longest-Subarray-With-at-Most-K-Frequency.cpp b/Two_Pointers/2958.Length-of-Longest-Subarray-With-at-Most-K-Frequency/2958.Length-of-Longest-Subarray-With-at-Most-K-Frequency.cpp new file mode 100644 index 000000000..04a8e4f61 --- /dev/null +++ b/Two_Pointers/2958.Length-of-Longest-Subarray-With-at-Most-K-Frequency/2958.Length-of-Longest-Subarray-With-at-Most-K-Frequency.cpp @@ -0,0 +1,26 @@ +class Solution { +public: + int maxSubarrayLength(vector& nums, int k) + { + int n = nums.size(); + unordered_mapcount; + int j = 0; + int ret = 0; + for (int i=0; i& nums) + { + LL n = nums.size(); + LL total = n*(n-1)/2+n; + LL half = (total+1)/2; + int left = 1, right = n; + while (left < right) + { + int mid = left + (right-left)/2; + if (atMostK(nums, mid)>=half) + right = mid; + else + left = mid+1; + } + return left; + } + + LL atMostK(vector& A, int K) + { + unordered_mapMap; + LL count=0; + LL i = 0; + + for (LL j=0; jK) + { + Map[A[i]]--; + if (Map[A[i]]==0) + Map.erase(A[i]); + i++; + } + count+= j-i+1; + } + return count; + } +}; diff --git a/Two_Pointers/3134.Find-the-Median-of-the-Uniqueness-Array/Readme.md b/Two_Pointers/3134.Find-the-Median-of-the-Uniqueness-Array/Readme.md new file mode 100644 index 000000000..205179dc2 --- /dev/null +++ b/Two_Pointers/3134.Find-the-Median-of-the-Uniqueness-Array/Readme.md @@ -0,0 +1,7 @@ +### 3134.Find-the-Median-of-the-Uniqueness-Array + +数组的subarray总数有`N = n(n-1)/2+n`个,直接求所有subarray的中位数肯定不现实。此题几乎肯定需要用二分搜值来解。 + +假设一个数字K,怎么判定它是否是所有subarray的distinct number的中位数,也就是第N/2个呢?显然,我们只需要判定`subarray with at most K distinct number`的个数是否有N/2个即可。有的话,我们就往小调整,否则就往大调整。 + +求“subarray with at most K distinct number”个数,可以用滑动窗口来解决。类似于340,992. diff --git a/Two_Pointers/3234.Count-the-Number-of-Substrings-With-Dominant-Ones/3234.Count-the-Number-of-Substrings-With-Dominant-Ones.cpp b/Two_Pointers/3234.Count-the-Number-of-Substrings-With-Dominant-Ones/3234.Count-the-Number-of-Substrings-With-Dominant-Ones.cpp new file mode 100644 index 000000000..1366613b4 --- /dev/null +++ b/Two_Pointers/3234.Count-the-Number-of-Substrings-With-Dominant-Ones/3234.Count-the-Number-of-Substrings-With-Dominant-Ones.cpp @@ -0,0 +1,53 @@ +class Solution { +public: + int numberOfSubstrings(string s) + { + int n = s.size(); + + vectorright = computeRightArray(s); + int ret = 0; + for (int m = 1; m <= 200; m++) + { + int j = 0, count = 0; + for (int i=0; i= count*count) + { + int extra = right[j-1] - max(0, count*count-have); + ret += max(extra+1, 0); + } + + count -= (s[i]=='0'); + } + } + + for (int i=0; i computeRightArray(const std::string& s) + { + int n = s.mgth(); + std::vector right(n, 0); + + for (int i = n-2; i >=0; i--) { + if (s[i + 1] == '1') { + right[i] = right[i + 1] + 1; + } + } + + return right; + } +}; diff --git a/Two_Pointers/3234.Count-the-Number-of-Substrings-With-Dominant-Ones/Readme.md b/Two_Pointers/3234.Count-the-Number-of-Substrings-With-Dominant-Ones/Readme.md new file mode 100644 index 000000000..d8f3da598 --- /dev/null +++ b/Two_Pointers/3234.Count-the-Number-of-Substrings-With-Dominant-Ones/Readme.md @@ -0,0 +1,13 @@ +### 3234.Count-the-Number-of-Substrings-With-Dominant-Ones + +对于substring,我们第一个想法是考虑前缀之差。假设以i为结尾的前缀里有x个1和y个0,那么我们希望找一个已有的前缀位置j(其包含p个1和q个0),需要满足`(x-p)>=(y-q)^2`. 由于这其中包含了平方关系,很难构造hash找出符合条件的j。 + +这是我们再审查这个平方关系。一般情况下,这意味着substring里的1要比0多很多。比如说有10个一,那么就需要有100个零。考虑到s的总长度也不过是4e4,这就意味着其实我们寻找的字符串里最多也就200个零,再配上40000个一就到达极限了。 + +所以此时我们的方案几乎就呼之欲出了,那就是穷举包含零的个数为1到200的substring。我们遍历长度m=1,...,200,对于每个固定的m,通过一遍单调的双指针移动就可以把所有包含零的个数是m的滑窗都找出来。时间复杂度是o(200n),符合题意。 + +假设找到一段滑窗,里面包含了m个零,那么该如何计数以它为基础的符合条件的substring呢?一种想法是,假设确定了最外边的两个0的位置i和j,那么我们可以自由调配i左边的1的个数、以及j右边的1的个数,但要使得区间内的0(个数已经固定)与1的个数符合条件。这样的方法比较复杂。 + +其实有一种更简单的做法,那么穷举substring的左边界i(不管是0还是1),通过上述的滑窗准则,找到对应右边界的j使得[i:j]恰好有m个0,另外有t个1. 那么我们可以知道,至少还需要`m*m-t`个1,这就需要从s[j]右边的若干个连续的1里面取。假设s[j]右边有连续k个1,那么超过`m*m-t`的部分我们可以自由选择,故有`k-(m*m-t)+1`种合法的substring右边界。 + +另外,对于m=0的特殊情况我们要单独处理。即substring里只含有1不含有0. 那么任意以1为左边界的substring,它的右边界可以包含任意数目的连续的1. diff --git a/Two_Pointers/3298.Count-Substrings-That-Can-Be-Rearranged-to-Contain-a-String-II/3298.Count-Substrings-That-Can-Be-Rearranged-to-Contain-a-String-II_v1.cpp b/Two_Pointers/3298.Count-Substrings-That-Can-Be-Rearranged-to-Contain-a-String-II/3298.Count-Substrings-That-Can-Be-Rearranged-to-Contain-a-String-II_v1.cpp new file mode 100644 index 000000000..7113126aa --- /dev/null +++ b/Two_Pointers/3298.Count-Substrings-That-Can-Be-Rearranged-to-Contain-a-String-II/3298.Count-Substrings-That-Can-Be-Rearranged-to-Contain-a-String-II_v1.cpp @@ -0,0 +1,36 @@ +using LL = long long; +class Solution { +public: + long long validSubstringCount(string word1, string word2) + { + vectortarget(26); + for (auto ch: word2) + target[ch-'a']++; + + vectorcount(26); + int j = 0; + LL ret = 0; + + int n = word1.size(); + for (int i=0; i&count, vector&target) + { + for (int i=0; i<26; i++) + if (count[i]target(26); + for (auto ch: word2) + target[ch-'a']++; + int T = 0; + for (int i=0; i<26; i++) + if (target[i]!=0) T++; + + vectorcount(26); + int j = 0; + LL ret = 0; + + int t = 0; + int n = word1.size(); + for (int i=0; iSet({'a','e','i','o','u'}); + unordered_mapMap; + + vectorconsecutive(n); + int c = 0; + for (int i=n-1; i>=0; i--) + { + if (Set.find(word[i])==Set.end()) + c = 0; + else + c++; + consecutive[i] = c; + } + + long long ret = 0; + int j = 0; + for (int i=0; i=k`. 此时两种情况: +1. 如果count1>k,那么我们必然会尝试右移左端点i,因为再右移右端点j的话必然不会满足count1. +2. 如果count1==k,那么我们就找到了一组合法区间[i:j]。那么我们是否还有其他以i为左端点的合法区间呢?事实上,如果j右边有连续的元音出现的话,这些都是可以纳入合法区间的。所以我们可以提前计算一个数组A[k],记录k及k右边连续有多少个元音。这样答案就增加了`1+A[j+1]`个。 + +以上就考虑完了所有以i为左端点的情况。下一步就是将i右移一步,更新count0和count1,然后继续探索右端点即可。 diff --git a/Two_Pointers/3634.Minimum-Removals-to-Balance-Array/3634.Minimum-Removals-to-Balance-Array.cpp b/Two_Pointers/3634.Minimum-Removals-to-Balance-Array/3634.Minimum-Removals-to-Balance-Array.cpp new file mode 100644 index 000000000..bcb70c85f --- /dev/null +++ b/Two_Pointers/3634.Minimum-Removals-to-Balance-Array/3634.Minimum-Removals-to-Balance-Array.cpp @@ -0,0 +1,19 @@ +using ll = long long; +class Solution { +public: + int minRemoval(vector& nums, int k) { + sort(nums.begin(), nums.end()); + int n = nums.size(); + int j = 0; + int ret = n; + for (int i=0; inums[i]*k`,这就意味着必须将从j到n-1的元素都删除才能符合要求。 + +假设我们删除最小值nums[0]但保留次小值nums[1],那么我们发现为了保证最大元素与最小元素的ratio不超过k,那么j的极限位置可以单调地右移。确定了新的j之后,我们发现此时需要删除1+n-j个元素,其中1个是左边删除的小数,n-j个是右边删除的大数。 + +所以我们可以顺次移动左指针i,相应地单调移动右指针j,直至恰好`j==n || nums[j]>nums[i]*k`。此时两种情况: +1. 如果`j==n`,说明ratio还没超过k,右端不需要删除任何数字。只需要删除左边的i个数字即可。 +2. 如果`nums[j]>nums[i]*k`,说明右端需要删除n-j个数字。左边需要删除i个数字。 + +遍历所有的i之后,取全局最小值。 + diff --git a/Two_Pointers/3641.Longest-Semi-Repeating-Subarray/3641.Longest-Semi-Repeating-Subarray.cpp b/Two_Pointers/3641.Longest-Semi-Repeating-Subarray/3641.Longest-Semi-Repeating-Subarray.cpp new file mode 100644 index 000000000..d35ea3ef1 --- /dev/null +++ b/Two_Pointers/3641.Longest-Semi-Repeating-Subarray/3641.Longest-Semi-Repeating-Subarray.cpp @@ -0,0 +1,25 @@ +class Solution { +public: + int longestSubarray(vector& nums, int k) { + unordered_mapMap; + int count = 0; + int n = nums.size(); + int j = -1; + int ret = 0; + for (int i=0; ik) + ret = max(ret, j - i); + else + ret = max(ret, j - i + 1); + Map[nums[i]]--; + if (Map[nums[i]]==1) count--; + } + return ret; + + } +}; diff --git a/Two_Pointers/3641.Longest-Semi-Repeating-Subarray/Readme.md b/Two_Pointers/3641.Longest-Semi-Repeating-Subarray/Readme.md new file mode 100644 index 000000000..af285715f --- /dev/null +++ b/Two_Pointers/3641.Longest-Semi-Repeating-Subarray/Readme.md @@ -0,0 +1,7 @@ +### 3641.Longest-Semi-Repeating-Subarray + +非常明显的双指针,但是滑窗右边界j的控制需要格外小心。 + +对于任意的左边界i,当满足`while (j+1k,那么意味着j超出了合法的范围,合法subarray的长度最多是`j-i`. +2. 如果count<=k,那么意味着j已经到了最后一个元素(即n-1)但是仍未超出合法范围,故此时合法subarray的长度是`j-i+1`. diff --git a/Union_Find/128.Longest-Consecutive-Sequence/128.Longest-Consecutive-Sequence_v2.cpp b/Union_Find/128.Longest-Consecutive-Sequence/128.Longest-Consecutive-Sequence_v2.cpp index 2c9414131..599b8c589 100644 --- a/Union_Find/128.Longest-Consecutive-Sequence/128.Longest-Consecutive-Sequence_v2.cpp +++ b/Union_Find/128.Longest-Consecutive-Sequence/128.Longest-Consecutive-Sequence_v2.cpp @@ -1,22 +1,25 @@ class Solution { -public: - int longestConsecutive(vector& nums) - { - unordered_setSet; - for (auto a:nums) - Set.insert(a); - - int result = 0; - for (int i=0; i& nums) { + unordered_set Set; + for (auto a : nums) + Set.insert(a); + + int result = 0; + unordered_set Visited; + for (int i = 0; i < nums.size(); i++) { + if (Set.find(nums[i] - 1) != Set.end() || + Visited.find(nums[i]) != Visited.end()) + continue; + Visited.insert(nums[i]); + int j = nums[i] + 1; + while (Set.find(j) != Set.end()) { + Visited.insert(j); + j++; + } + + result = max(result, j - nums[i]); + } + return result; } - return result; - } -}; + }; \ No newline at end of file diff --git a/Union_Find/2709.Greatest-Common-Divisor-Traversal/2709.Greatest-Common-Divisor-Traversal.cpp b/Union_Find/2709.Greatest-Common-Divisor-Traversal/2709.Greatest-Common-Divisor-Traversal.cpp new file mode 100644 index 000000000..496887207 --- /dev/null +++ b/Union_Find/2709.Greatest-Common-Divisor-Traversal/2709.Greatest-Common-Divisor-Traversal.cpp @@ -0,0 +1,89 @@ +class Solution { +public: + int Father[2*100005]; + + + int FindFather(int x) + { + if (Father[x]!=x) + Father[x] = FindFather(Father[x]); + return Father[x]; + } + + void Union(int x, int y) + { + x = Father[x]; + y = Father[y]; + if (x>y) Father[y] = x; + else Father[x] = y; + } + + vectorEratosthenes(int n) + { + vectorq(n+1,0); + vectorprimes; + for (int i=2; i<=sqrt(n); i++) + { + if (q[i]==1) continue; + int j=i*2; + while (j<=n) + { + q[j]=1; + j+=i; + } + } + for (int i=2; i<=n; i++) + { + if (q[i]==0) + primes.push_back(i); + } + return primes; + } + + bool canTraverseAllPairs(vector& nums) + { + int MX = *max_element(nums.begin(), nums.end()); + vectorprimes = Eratosthenes(MX); + int M = primes.size(); + + int N = nums.size(); + unordered_mapidx; + for (int j=0; j x) break; + if (p*p > x) + { + if (FindFather(i)!=FindFather(N+idx[x])) + Union(i, N+idx[x]); + break; + } + + if (x%p==0) + { + if (FindFather(i)!=FindFather(N+j)) + Union(i, N+j); + while (x%p==0) + x /= p; + } + } + } + + for (int i=0; i next[100005]; + unordered_setprimes; + LL global = 0; +public: + int FindFather(int x) + { + if (Father[x]!=x) + Father[x] = FindFather(Father[x]); + return Father[x]; + } + + void Union(int x, int y) + { + x = Father[x]; + y = Father[y]; + if (xEratosthenes(int n) + { + vectorq(n+1,0); + unordered_setprimes; + for (int i=2; i<=sqrt(n); i++) + { + if (q[i]==1) continue; + int j=i*2; + while (j<=n) + { + q[j]=1; + j+=i; + } + } + for (int i=2; i<=n; i++) + { + if (q[i]==0) + primes.insert(i); + } + return primes; + } + + bool isPrime(int x) + { + return primes.find(x)!=primes.end(); + } + + long long countPaths(int n, vector>& edges) + { + primes = Eratosthenes(n); + + for (int i=1; i<=n; i++) + Father[i] = i; + + for (auto& edge: edges) + { + int a = edge[0], b = edge[1]; + next[a].push_back(b); + next[b].push_back(a); + if (!isPrime(a) && !isPrime(b)) + { + if (FindFather(a)!=FindFather(b)) + Union(a,b); + } + } + + unordered_mapMap; + for (int i=1; i<=n; i++) + Map[FindFather(i)]+=1; + + for (int p: primes) + { + vectorarr; + for (int nxt: next[p]) + { + if (!isPrime(nxt)) + arr.push_back(Map[FindFather(nxt)]); + } + LL total = accumulate(arr.begin(), arr.end(), 0LL); + LL sum = 0; + for (LL x: arr) + sum += x*(total-x); + global += sum/2 + total; + } + + return global; + } + +}; diff --git a/Union_Find/2867.Count-Valid-Paths-in-a-Tree/Readme.md b/Union_Find/2867.Count-Valid-Paths-in-a-Tree/Readme.md new file mode 100644 index 000000000..4ea6c5780 --- /dev/null +++ b/Union_Find/2867.Count-Valid-Paths-in-a-Tree/Readme.md @@ -0,0 +1,16 @@ +### 2867.Count-Valid-Paths-in-a-Tree + +很显然,因为需要计数的path只有一个prime,我们必然count paths by prime. + +我们考虑每个是质数的节点P,考虑经过它的有效路径。显然,一个最显著的pattern就是:从P某个联通的合数节点(不需要紧邻但是不能被其他质数隔开)开始,经过P,再到P的另一个联通的合数。 + +假设A有M个紧邻的合数节点(显然不会关注紧邻的质数节点),这些合数节点又各自分别于若干个合数节点联通,记这些联通区域里分别有m1,m2,m3...个联通的合数节点。显然,从m1里的任何一个节点,到除m1里的任意节点,都是合法路径。令`m1+m2+m3+...=total`,则有 +```cpp +for (int i=1; i<=M; i++) + count += m_i * (total - m_i); +``` +但是注意,以上的count对于起点、终点互换的路径是重复计算了,所以最终有效的是count/2条路径。 + +另外,有效路径的第二个pattern,就是以P为起点,终点是任意与P联通的合数节点,这样的路径恰好就是total条。 + +最终的答案就是对于每个P,累加`count/2+total`. diff --git a/Union_Find/3600.Maximize-Spanning-Tree-Stability-with-Upgrades/3600.Maximize-Spanning-Tree-Stability-with-Upgrades.cpp b/Union_Find/3600.Maximize-Spanning-Tree-Stability-with-Upgrades/3600.Maximize-Spanning-Tree-Stability-with-Upgrades.cpp new file mode 100644 index 000000000..e530244e8 --- /dev/null +++ b/Union_Find/3600.Maximize-Spanning-Tree-Stability-with-Upgrades/3600.Maximize-Spanning-Tree-Stability-with-Upgrades.cpp @@ -0,0 +1,75 @@ +class Solution { +public: + struct DSU { + vector p, r; + DSU(int n): p(n,-1), r(n,0) { + iota(p.begin(), p.end(), 0); + } + int find(int x) { + return p[x]==x ? x : p[x]=find(p[x]); + } + bool unite(int a, int b) { + a = find(a); b = find(b); + if (a == b) return false; + if (r[a] < r[b]) swap(a,b); + p[b] = a; + if (r[a]==r[b]) ++r[a]; + return true; + } + }; + + bool isOK(int T, int n, vector>& edges, int k) { + DSU dsu(n); + int count = 0; + int upgrade = 0; + + vector>candidates; // upgrade, u,v + for (auto& e: edges) { + int u = e[0], v = e[1], s = e[2], must = e[3]; + if (must) { + if (s=T) + candidates.push_back({0, u, v}); + else if (2*s>=T) + candidates.push_back({1, u, v}); + } + } + + sort(candidates.begin(), candidates.end()); + + for (auto& cand: candidates) { + int u = cand[1], v = cand[2]; + if (count==n-1) break; + if (dsu.find(u)!=dsu.find(v)) { + dsu.unite(u,v); + count++; + upgrade += cand[0]; + if (upgrade > k) return false; + } + } + + for (int i=0; i>& edges, int k) { + int lo = 1, hi = 1e5*2; + while (lo < hi) { + int mid = hi-(hi-lo)/2; + if (isOK(mid, n, edges, k)) + lo = mid; + else + hi = mid-1; + } + if (isOK(lo, n, edges, k)) + return lo; + else + return -1; + } +}; diff --git a/Union_Find/3600.Maximize-Spanning-Tree-Stability-with-Upgrades/Readme.md b/Union_Find/3600.Maximize-Spanning-Tree-Stability-with-Upgrades/Readme.md new file mode 100644 index 000000000..2955612c2 --- /dev/null +++ b/Union_Find/3600.Maximize-Spanning-Tree-Stability-with-Upgrades/Readme.md @@ -0,0 +1,7 @@ +### 3600.Maximize-Spanning-Tree-Stability-with-Upgrades + +突破口是二分搜值。如果猜测全局最小的stability是T的话,尝试能否用最小生成树的方法构造出一棵树来。 + +当固定全局最小的stability是T之后,那么哪些edge能用哪些不能用就一目了然了。对于must的那些边,如果存在stability小于T的,那么显然无解。对于非must的边,如果`2*stability p, r; + DSU(int n): p(n,-1), r(n,0) { + iota(p.begin(), p.end(), 0); + } + int find(int x) { + return p[x]==x ? x : p[x]=find(p[x]); + } + bool unite(int a, int b) { + a = find(a); b = find(b); + if (a == b) return false; + if (r[a] < r[b]) swap(a,b); + p[b] = a; + if (r[a]==r[b]) ++r[a]; + return true; + } +}; + +class Solution { +public: + int maxActivated(vector>& points) { + int n = points.size(); + DSU dsu(n); + unordered_map>MapX; + unordered_map>MapY; + for (int i=0; ileaders; + for (int i=0; itemp; + for (auto [k,v]: leaders) + temp.push_back(v); + + sort(temp.rbegin(),temp.rend()); + if (temp.size()==1) return temp[0]+1; + else return temp[0]+temp[1]+1; + } +}; diff --git a/Union_Find/3873.Maximum-Points-Activated-with-One-Addition/Readme.md b/Union_Find/3873.Maximum-Points-Activated-with-One-Addition/Readme.md new file mode 100644 index 000000000..c77437e3b --- /dev/null +++ b/Union_Find/3873.Maximum-Points-Activated-with-One-Addition/Readme.md @@ -0,0 +1,3 @@ +### 3873.Maximum-Points-Activated-with-One-Addition + +非常明显的Union Find。我们将所有属于同一个x坐标的点union起来,再将所有属于同一个y坐标的点union起来。这样所有的点就被聚类成了若干个group。此时我们只需要用额外的一个点,就可以将最大的两个group再union起来。具体的做法:在group A里取一个点(xa,ya), 在group B里取一个点(xb,yb),那么我们构造(xa,yb)或者(xb,ya)就可以将这两个点连同各自的group连接起来, diff --git a/Union_Find/3887.Incremental-Even-Weighted-Cycle-Queries/3887.Incremental-Even-Weighted-Cycle-Queries.cpp b/Union_Find/3887.Incremental-Even-Weighted-Cycle-Queries/3887.Incremental-Even-Weighted-Cycle-Queries.cpp new file mode 100644 index 000000000..67250d207 --- /dev/null +++ b/Union_Find/3887.Incremental-Even-Weighted-Cycle-Queries/3887.Incremental-Even-Weighted-Cycle-Queries.cpp @@ -0,0 +1,42 @@ +struct DSU { + vector p, r, d; + DSU(int n): p(n,-1), r(n,0), d(n,0) { + iota(p.begin(), p.end(), 0); + } + pair find(int x) { + if (p[x]==x) + return {x, 0}; + auto [root, dist] = find(p[x]); + p[x] = root; + d[x] = (dist+d[x])%2; + return {p[x], d[x]}; + } + void unite(int a, int b, int w) { + auto [ra, da] = find(a); + auto [rb, db] = find(b); + p[ra] = rb; + d[ra] = (da+w+db)%2; + } +}; + +class Solution { +public: + + int numberOfEdgesAdded(int n, vector>& edges) { + DSU dsu(n); + int ret = 0; + for (auto &e:edges) { + int u = e[0], v = e[1], w = e[2]; + auto [ru, du] = dsu.find(u); + auto [rv, dv] = dsu.find(v); + if (ru==rv) { + ret += ((du+dv+w)%2==0); + } + else { + dsu.unite(u,v,w); + ret += 1; + } + } + return ret; + } +}; diff --git a/Union_Find/3887.Incremental-Even-Weighted-Cycle-Queries/Readme.md b/Union_Find/3887.Incremental-Even-Weighted-Cycle-Queries/Readme.md new file mode 100644 index 000000000..6b1d93682 --- /dev/null +++ b/Union_Find/3887.Incremental-Even-Weighted-Cycle-Queries/Readme.md @@ -0,0 +1,29 @@ +### 3887.Incremental-Even-Weighted-Cycle-Queries + +关于当前考察的edge(两个端点记做A和B)是否能引入一个环,很明显等价于这两个点是否已经连通。于是此题肯定需要用到并查集。 + +如果考察的edge的两个端点A与B还没连通,那么根据题意,我们必然会引入这个edge。 + +如果考察的edge的两个端点A与B已经连通,我们假定该连通区域的共同族长节点是R。我们可以发现,路径R->A与路径R->B进行异或的结果,其实就是A->B路径进行异或的结果。所以这个edge所构成的环,可以看成是三部分的异或:R->A,R->B, edge(即A->B).所以我们在设计并查集的数据结构的时候,除了记录每个节点的族长节点,需要再多考虑一个量,即从该点到族长的距离。 + +我们在DSU的结果里,定义p[x]表示x点在联通区域里的族长,d[x]表示x点到其族长的距离。 + +对于find(x)而言,本质依然还是递归,只不过递归函数的输出量是一个pair,包含了root和dist两个分量: +```cpp + pair find(int x) { + if (p[x]==x) + return {x, 0}; + auto [root, dist] = find(p[x]); + p[x] = root; + d[x] = (dist+d[x])%2; + return {p[x], d[x]}; + } +``` + +对于unite而言情况就复杂一些。 + +1. 如果edge的两个端点A,B已经联通,无论是否添加这个edge(取决于RA,RB,edge的异或结果),那么我们都不需要在DSU的数据结构里做unite的操作。因为无论是否添加edge,都不会改变A和B的族长,或者它们各自距离族长的距离。 + +2. 如果edge的两个端点A和B未联通,根据题意我们必然会添加这个edge,同时也必然需要做unite的操作。此时不仅仅是简单地需要将p[a]指向b。事实上,edge的添加会产生巨大的影响:整个联通区域a需要重新定位朝向b的族长(记作pb)。怎么处理最方便呢?其实我们只要将原来的族长pa点,让其“带头通过a点”指向pb点即可,即`p[pa]=pb, d[pa]=d[a]+e+d[b]`。 + +由此我们就有了带权重的并查集。每次考察edge,先判断两个端点是否已经联通。如果已经联通,那么da,db和edge的异或本质就是一个cycle的异或。如果两个端点未被联通,我们需要将区域A的族长“通过这条edge”指向区域B,更新p[pa]和d[pa].