Neo's Blog

不抽象就无法深入思考
不还原就看不到本来面目!

0%

0,1,2

分为三段:0段、未判定段、2段,用三个指针(s0, cur, s2)指向每一段的当前位置。

s0并不是指向0段的最后一个元素,而是指向最后一个元素的下一个元素。s2类似。

如果A[cur]=0,则s0(其实指向1),cur互换位置; s0++; cur++;
如果A[cur]=1,则cur++;
如果A[cur]=2,则s2,cur互换位置; s2—; cur不变;

有字符串,将所有连续的ac跟单独的b去掉后的字符串:如bbbacccccb->ccc; aacceacdby->edy

时间复杂度O(n) 空间复杂度O(n) —> 时间复杂度O(n) 空间复杂度O(1)

这是本题较好的一种解法,设两个指针cur和loc分别从头开始出发,cur每次移动一格,另一个指针loc保留当前的操作位置,如果cur指向的字符是c且loc指向的是a,则将loc回移一位(ac抵消了),如果遇到其他非b的字符,则将loc处的字符置为cur处的字符(++location),一直进行直到到cur到达字符串尾部,此时取字符串开头到loc指针之间的子串即为本题的解。这种解法妙就妙在loc处的字符是即时更新的,一些边界条件都自动消除了。

参考:
https://www.cnblogs.com/SteelArm/p/12773014.html

输入n个整数,输出出现次数大于等于数组长度一半的数。
输入描述:

每个测试输入包含n个空格分割的n个整数,n不超过100,其中有一个整数出现次数大于等于n/2。
输出描述:

输出出现次数大于等于n/2的数。

解题思路

摩尔投票法的核心就是一一抵消,删除不同的数。

因为要找过半的数,用一个变量count记录读取每个变量变化的次数,一个变量temp记录可能过半的数。

先让count = 1,然后让temp = vec[0],然后往后遍历一遍。
碰到和temp相同的数就给count++,否则就count—,如果count变成0,就让temp=vec[i] (v数组遍历过程中的当前值),并让count = 1。

如此遍历一遍,因为有一个数过半,所以temp最后肯定存储的是过半的数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int moreThanHalfNum_Solution(vector<int>& nums) {
int cnt = 0;
int val = -1;
for (int i = 0; i < nums.size(); ++i) {
if (!cnt) {val = nums[i];}

if (nums[i] == val) {
cnt++;
}
else {
cnt--;
}
}

return val;
}
};

参考:
https://www.zhihu.com/question/284969980/answer/440979325

给定一个包含了一些 0 和 1 的非空二维数组 grid 。

一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。

找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)

解题思路:

DFS

图的表示

稠密图,邻接矩阵;

稀疏图,邻接表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

(1) 邻接矩阵:g[a][b] 存储边a->b; a, b是定点的序号,特别浪费空间,适合稠密图。

//稀疏图
(2) 邻接表:

// 对于每个点k,开一个单链表,存储k所有可以走到的点。h[k]存储这个单链表的头结点
int h[N], e[N], ne[N], idx;

// 初始化
idx = 0;
memset(h, -1, sizeof h);

// 添加一条边a->b
void add(int a, int b)
{
//把b当作value, 插入到链表头
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

DFS && BFS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
树与图的遍历
时间复杂度 O(n+m)O(n+m), nn 表示点数,mm 表示边数
(1) 深度优先遍历 —— 模板题 AcWing 846. 树的重心

int dfs(int u)
{
st[u] = true; // st[u] 表示点u已经被遍历过
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j]) dfs(j);
}
}

(2) 宽度优先遍历 —— 模板题 AcWing 847. 图中点的层次

queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);

while (q.size())
{
int t = q.front();
q.pop();

for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j])
{
st[j] = true; // 表示点j已经被遍历过
q.push(j);
}
}
}

拓扑排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
拓扑排序 —— 模板题 AcWing 848. 有向图的拓扑序列
时间复杂度 O(n+m)O(n+m), nn 表示点数,mm 表示边数
bool topsort()
{
int hh = 0, tt = -1;

// d[i] 存储点i的入度
for (int i = 1; i <= n; i ++ )
if (!d[i])
q[ ++ tt] = i;

while (hh <= tt)
{
int t = q[hh ++ ];

for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (-- d[j] == 0)
q[ ++ tt] = j;
}
}

// 如果所有点都入队了,说明存在拓扑序列;否则不存在拓扑序列。
return tt == n - 1;
}

最短路问题的解法

算法 解决的问题 算法原理 时间复杂度
dijkstra 单源最短路,稠密图, 正权 找到最小距离,用找到的最小距离更新剩余距离 $O(N^2)$ N为图点的数量
堆优化版dijkstra 单源最短路,稀疏图,正权 借助堆来优化dijkstra算法中的查找最小值操作 $O(N^2)$N为图点的数量
bellman-ford 单源最短路,负边权,限制步数的情形 迭代k次,每次找最小距离 $O(N^2)$ N为图点的数量
SPFA 单源最短路,负边权,判定有没有负环 队列优化 $O(N^2)$ N为图点的数量
floyd 多源汇最短路 动态规划思想,三角不等式 $O(N^3)$ N为图点的数量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214

朴素dijkstra算法 —— 模板题 AcWing 849. Dijkstra求最短路 I
时间复杂是 O(n2+m)O(n2+m), nn 表示点数,mm 表示边数
int g[N][N]; // 存储每条边
int dist[N]; // 存储1号点到每个点的最短距离
bool st[N]; // 存储每个点的最短路是否已经确定

// 求1号点到n号点的最短路,如果不存在则返回-1
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;

for (int i = 0; i < n - 1; i ++ )
{
int t = -1; // 在还未确定最短路的点中,寻找距离最小的点
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;

// 用t更新其他点的距离
for (int j = 1; j <= n; j ++ )
dist[j] = min(dist[j], dist[t] + g[t][j]);

st[t] = true;
}

if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}

堆优化版dijkstra —— 模板题 AcWing 850. Dijkstra求最短路 II
时间复杂度 O(mlogn)O(mlogn), nn 表示点数,mm 表示边数
typedef pair<int, int> PII;

int n; // 点的数量
int h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边
int dist[N]; // 存储所有点到1号点的距离
bool st[N]; // 存储每个点的最短距离是否已确定

// 求1号点到n号点的最短距离,如果不存在,则返回-1
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, 1}); // first存储距离,second存储节点编号

while (heap.size())
{
auto t = heap.top();
heap.pop();

int ver = t.second, distance = t.first;

if (st[ver]) continue;
st[ver] = true;

for (int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > distance + w[i])
{
dist[j] = distance + w[i];
heap.push({dist[j], j});
}
}
}

if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}

Bellman-Ford算法 —— 模板题 AcWing 853. 有边数限制的最短路
时间复杂度 O(nm)O(nm), nn 表示点数,mm 表示边数
注意在模板题中需要对下面的模板稍作修改,加上备份数组,详情见模板题。

int n, m; // n表示点数,m表示边数
int dist[N]; // dist[x]存储1到x的最短路距离

struct Edge // 边,a表示出点,b表示入点,w表示边的权重
{
int a, b, w;
}edges[M];

// 求1到n的最短路距离,如果无法从1走到n,则返回-1。
int bellman_ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;

// 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。
for (int i = 0; i < n; i ++ )
{
for (int j = 0; j < m; j ++ )
{
int a = edges[j].a, b = edges[j].b, w = edges[j].w;
if (dist[b] > dist[a] + w)
dist[b] = dist[a] + w;
}
}

if (dist[n] > 0x3f3f3f3f / 2) return -1;
return dist[n];
}

spfa 算法(队列优化的Bellman-Ford算法) —— 模板题 AcWing 851. spfa求最短路
时间复杂度 平均情况下 O(m)O(m),最坏情况下 O(nm)O(nm), nn 表示点数,mm 表示边数
int n; // 总点数
int h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边
int dist[N]; // 存储每个点到1号点的最短距离
bool st[N]; // 存储每个点是否在队列中

// 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
int spfa()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;

queue<int> q;
q.push(1);
st[1] = true;

while (q.size())
{
auto t = q.front();
q.pop();

st[t] = false;

for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
if (!st[j]) // 如果队列中已存在j,则不需要将j重复插入
{
q.push(j);
st[j] = true;
}
}
}
}

if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}

spfa判断图中是否存在负环 —— 模板题 AcWing 852. spfa判断负环
时间复杂度是 O(nm)O(nm), nn 表示点数,mm 表示边数
int n; // 总点数
int h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边
int dist[N], cnt[N]; // dist[x]存储1号点到x的最短距离,cnt[x]存储1到x的最短路中经过的点数
bool st[N]; // 存储每个点是否在队列中

// 如果存在负环,则返回true,否则返回false。
bool spfa()
{
// 不需要初始化dist数组
// 原理:如果某条最短路径上有n个点(除了自己),那么加上自己之后一共有n+1个点,由抽屉原理一定有两个点相同,所以存在环。

queue<int> q;
for (int i = 1; i <= n; i ++ )
{
q.push(i);
st[i] = true;
}

while (q.size())
{
auto t = q.front();
q.pop();

st[t] = false;

for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1;
if (cnt[j] >= n) return true; // 如果从1号点到x的最短路中包含至少n个点(不包括自己),则说明存在环
if (!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}

return false;
}

floyd算法 —— 模板题 AcWing 854. Floyd求最短路
时间复杂度是 O(n3)O(n3), nn 表示点数
初始化:
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (i == j) d[i][j] = 0;
else d[i][j] = INF;

// 算法结束后,d[a][b]表示a到b的最短距离
void floyd()
{
for (int k = 1; k <= n; k ++ )
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )

d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}

最小生成树问题的解法

算法 解决的问题 算法原理 时间复杂度
prim算法 单源最短路,稠密图, 正权 找到最小距离,用找到的最小距离更新剩余距离 $O(N^2)$ N为图点的数量
kraskal算法 单源最短路,稀疏图,正权 并查集 $O(N^2)$ N为图点的数量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

朴素版prim算法 —— 模板题 AcWing 858. Prim算法求最小生成树
时间复杂度是 O(n2+m)O(n2+m), nn 表示点数,mm 表示边数
int n; // n表示点数
int g[N][N]; // 稠密图 邻接矩阵,存储所有边
int dist[N]; // 存储其他点到当前最小生成树的距离
bool st[N]; // 存储每个点是否已经在生成树中

稠密图,朴素版prim N^2
稀疏图,heap优化版 mlgn 很少用,用kruakal替代

kruskal mlgm
// 如果图不连通,则返回INF(值是0x3f3f3f3f), 否则返回最小生成树的树边权重之和
int prim()
{
memset(dist, 0x3f, sizeof dist);

int res = 0;
for (int i = 0; i < n; i ++ )
{
int t = -1;
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;

if (i && dist[t] == INF) return INF;

if (i) res += dist[t];
st[t] = true;

for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);
}

return res;
}

Kruskal算法 —— 模板题 AcWing 859. Kruskal算法求最小生成树
时间复杂度是 O(mlogm)O(mlogm), nn 表示点数,mm 表示边数
int n, m; // n是点数,m是边数
int p[N]; // 并查集的父节点数组

struct Edge // 存储边
{
int a, b, w;

bool operator< (const Edge &W)const
{
return w < W.w;
}
}edges[M];

int find(int x) // 并查集核心操作
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}

int kruskal()
{
sort(edges, edges + m);

for (int i = 1; i <= n; i ++ ) p[i] = i; // 初始化并查集

int res = 0, cnt = 0;
for (int i = 0; i < m; i ++ )
{
int a = edges[i].a, b = edges[i].b, w = edges[i].w;

a = find(a), b = find(b);
if (a != b) // 如果两个连通块不连通,则将这两个连通块合并
{
p[a] = b;
res += w;
cnt ++ ;
}
}

if (cnt < n - 1) return INF;
return res;
}

如何判定图是不是二分图:DFS遍历,奇数环

二分图匹配:匈牙利算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
染色法判别二分图 —— 模板题 AcWing 860. 染色法判定二分图
时间复杂度是 O(n+m)O(n+m), nn 表示点数,mm 表示边数
int n; // n表示点数
int h[N], e[M], ne[M], idx; // 邻接表存储图
int color[N]; // 表示每个点的颜色,-1表示未染色,0表示白色,1表示黑色

// 参数:u表示当前节点,c表示当前点的颜色
bool dfs(int u, int c)
{
color[u] = c;
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (color[j] == -1)
{
if (!dfs(j, !c)) return false;
}
else if (color[j] == c) return false;
}

return true;
}

bool check()
{
memset(color, -1, sizeof color);
bool flag = true;
for (int i = 1; i <= n; i ++ )
if (color[i] == -1)
if (!dfs(i, 0))
{
flag = false;
break;
}
return flag;
}

匈牙利算法 —— 模板题 AcWing 861. 二分图的最大匹配
时间复杂度是 O(nm)O(nm), nn 表示点数,mm 表示边数
int n1, n2; // n1表示第一个集合中的点数,n2表示第二个集合中的点数
int h[N], e[M], ne[M], idx; // 邻接表存储所有边,匈牙利算法中只会用到从第一个集合指向第二个集合的边,所以这里只用存一个方向的边
int match[N]; // 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个
bool st[N]; // 表示第二个集合中的每个点是否已经被遍历过

bool find(int x)
{
for (int i = h[x]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j])
{
st[j] = true;
if (match[j] == 0 || find(match[j]))
{
match[j] = x;
return true;
}
}
}

return false;
}

// 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点
int res = 0;
for (int i = 1; i <= n1; i ++ )
{
memset(st, false, sizeof st);
if (find(i)) res ++ ;
}

Maximum sum such that no two elements are adjacent
Given an array of positive numbers, find the maximum sum of a subsequence with the constraint that no 2 numbers in the sequence should be adjacent in the array. So 3 2 7 10 should return 13 (sum of 3 and 10) or 3 2 5 10 7 should return 15 (sum of 3, 5 and 7).Answer the question in most efficient way.
Examples :
Input : arr[] = {5, 5, 10, 100, 10, 5}
Output : 110

Input : arr[] = {1, 2, 3}
Output : 4

Input : arr[] = {1, 20, 3}
Output : 20

不相邻序列最大和—思路

遍历array 中的所有元素,设置两个变量:

excl[i]: 不包含i元素的最大和

incl[i]: 包含i元素的最大和

更新当前元素的 excl 和 incl:

不包含当前元素的最大和 excl[i] = max(incl[i-1], excl[i-1])

包含当前元素的最大和 incl = excl[i-1]+A[i] (元素不能相邻)

因为只与前一项有关,所以可以用空间压缩。

我们把只包含质因子2、3和5的数称作丑数(Ugly Number)。

例如6、8都是丑数,但14不是,因为它包含质因子7。

求第n个丑数的值。

样例
输入:5

输出:5
注意:习惯上我们把1当做第一个丑数。

解答思路

第二种:创建数组保存已经找到丑数,用空间换时间的解法。
根据丑数的定义, 丑数应该是另一个丑数乘以 2、3 或者 5 的结果(1 除外)。因此我们可以创建一个数组,里面的数字是排好序的丑数,每一个丑数都是前面的丑数乘以 2、3 或者 5 得到的。

这种思路的关键在于怎样确保数组里面的丑数是排好序的。假设数组中已经有若干个丑数排好序后存放在数组中,并且把己有最大的丑数记做M,我们接下来分析如何生成下一个丑数。该丑数肯定是前面某一个丑数乘以 2、3 或者 5 的结果, 所以我们首先考虑把已有的每个丑数乘以 2。在乘以 2 的时钝能得到若干个小于或等于 M 的结果。由于是按照顺序生成的,小于或者等于 M 肯定己经在数组中了,我们不需再次考虑:还会得到若干个大于 M 的结果,但我们只需要第一个大于 M 的结果,因为我们希望丑数是按从小到大的顺序生成的,其他更大的结果以后再说。我们把得到的第一个乘以 2 后大于 M 的结果记为 M2,同样,我们把已有的每一个丑数乘以 3 和 5,能得到第一个大于 M 的结果 M3 和 M,那么下一个丑数应该是 M2、M3 和 M5 这 3 个数的最小者。

前面分析的时候,提到把已有的每个丑数分别都乘以 2、3 和 5。事实上这不是必须的,因为已有的丑数是按顺序存放在数组中的。对乘以 2 而言, 肯定存在某一个丑数 T2,排在它之前的每一个丑数乘以 2 得到的结果都会小于已有最大的丑数,在它之后的每一个丑数乘以 2 得到的结果都会太大。我们只需记下这个丑数的位置, 同时每次生成新的丑数的时候,去更新这个 T2。对乘以 3 和 5 而言, 也存在着同样的 T3 和 T5。

T2的更新是需要靠遍历来完成的,但是需要明确的是:T2不需要每次都从0开始,他是越来越大的。

求解一个矩阵中找一条最长的递增路径?

可能解法:有向图DFS和记忆化搜索处理

dp[i][j]表示以(i,j)出发的最长路径。

该题目用常规的DP很难完成,因为他没有base condition,不知道从何处开始计算。

给你一根长度为 n 绳子,请把绳子剪成 m 段(m、n 都是整数,2≤n≤58 并且 m≥2)。

每段的绳子的长度记为k[0]、k[1]、……、k[m]。k[0]k[1] … k[m] 可能的最大乘积是多少?

例如当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到最大的乘积18。

样例
输入:8

输出:18

自上而下 + 备忘录解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution {
public:
vector<int> dp;

int dfs(int n) {
if (dp[n] > 0) {
return dp[n];
}

if (n <= 3) {
dp[n] = n - 1;
return n - 1;
}
if (n == 4) {
dp[n] = 4;
return dp[n];
}
if (n == 5) {
dp[n] = 6;
return dp[n];
}
int max_score = 0;
for (int i = 2; i <= n - 2; ++i) {
//分为两种情况:第一刀减在i处;剩下的那一部分可以有两种选择:不剪,或者继续剪
max_score = max(max_score, max(i * (n - i), i * dfs(n - i)));
}
dp[n] = max_score;
return dp[n];
}

int maxProductAfterCutting(int length) {
dp = vector<int>(length + 5, 0);
return dfs(length);
}
};

在一个m×n的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于0)。

你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格直到到达棋盘的右下角。

给定一个棋盘及其上面的礼物,请计算你最多能拿到多少价值的礼物?

注意:

m,n>0
样例:

输入:
[
[2,3,1],
[1,7,1],
[4,6,1]
]

输出:19

解释:沿着路径 2→3→7→6→1 可以得到拿到最大价值礼物。

解题思路:

  1. 自上而下 + 记忆化搜索
  2. 动态规划
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class Solution {
public:
typedef pair<int, int> PII;
int res;
int m;
int n;
vector<vector<int>> grid;
vector<vector<int>> dp;

/*
void dfs(int x, int y, int sum) {
if (x == m - 1 && y == n -1) {
res = max(res, sum);
return;
}

int dx[] = {0, 1};
int dy[] = {1, 0};
for (int k = 0; k < 2; ++k) {
int nx = x + dx[k], ny = y + dy[k];
if (nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] >= 0) {
int b = grid[nxa][ny];
grid[nx][ny] = -1;
dfs(nx, ny, sum + b);
grid[nx][ny] = b;
}
}
}

int getMaxValue(vector<vector<int>>& _grid) {
grid = _grid;
m = grid.size();
n = grid[0].size();
dfs(0, 0, grid[0][0]);
return res;
}
*/


int dfs(int i, int j) {
if (dp[i][i] >= 0) {
return dp[i][j];
}

if (i == 0 && j == 0) {
return dp[i][j] = grid[i][j];
}

dp[i][j] = grid[i][j] + max(
j - 1 >= 0 ? dfs(i, j - 1) : INT_MIN,
i - 1 >= 0 ? dfs(i - 1, j) : INT_MIN);
return dp[i][j];
}

int getMaxValue(vector<vector<int>>& _grid) {
grid = _grid;
m = grid.size();
n = grid[0].size();
dp = vector<vector<int>>(m, vector<int>(n, -1));
dfs(m - 1, n - 1);
return dp[m - 1][n - 1];
}
};