Leetcode301. Remove Invalid Parentheses

Remove the minimum number of invalid parentheses in order to make the input string valid. Return all possible results.

Note: The input string may contain letters other than the parentheses ( and ).

Example 1:

1
2
Input: "()())()"
Output: ["()()()", "(())()"]

Example 2:

1
2
Input: "(a)())()"
Output: ["(a)()()", "(a())()"]

Example 3:

1
2
Input: ")("
Output: [""]

这道题让移除最少的括号使得给定字符串为一个合法的含有括号的字符串,我们从小数学里就有括号,所以应该对合法的含有括号的字符串并不陌生,字符串中的左右括号数应该相同,而且每个右括号左边一定有其对应的左括号,而且题目中给的例子也说明了去除方法不唯一,需要找出所有合法的取法。参考了网上大神的解法,这道题首先可以用 BFS 来解,我把给定字符串排入队中,然后取出检测其是否合法,若合法直接返回,不合法的话,对其进行遍历,对于遇到的左右括号的字符,去掉括号字符生成一个新的字符串,如果这个字符串之前没有遇到过,将其排入队中,用 HashSet 记录一个字符串是否出现过。对队列中的每个元素都进行相同的操作,直到队列为空还没找到合法的字符串的话,那就返回空集,参见代码如下:

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
class Solution {
public:
vector<string> removeInvalidParentheses(string s) {
vector<string> res;
unordered_set<string> visited{{s}};
queue<string> q{{s}};
bool found = false;
while (!q.empty()) {
string t = q.front(); q.pop();
if (isValid(t)) {
res.push_back(t);
found = true;
}
if (found) continue;
for (int i = 0; i < t.size(); ++i) {
if (t[i] != '(' && t[i] != ')') continue;
string str = t.substr(0, i) + t.substr(i + 1);
if (!visited.count(str)) {
q.push(str);
visited.insert(str);
}
}
}
return res;
}
bool isValid(string t) {
int cnt = 0;
for (int i = 0; i < t.size(); ++i) {
if (t[i] == '(') ++cnt;
else if (t[i] == ')' && --cnt < 0) return false;
}
return cnt == 0;
}
};

下面来看一种递归解法,这种解法首先统计了多余的半括号的数量,用 cnt1 表示多余的左括号,cnt2 表示多余的右括号,因为给定字符串左右括号要么一样多,要么左括号多,要么右括号多,也可能左右括号都多,比如 “)(“。所以 cnt1 和 cnt2 要么都为0,要么都大于0,要么一个为0,另一个大于0。好,下面进入递归函数,首先判断,如果当 cnt1 和 cnt2 都为0时,说明此时左右括号个数相等了,调用 isValid 子函数来判断是否正确,正确的话加入结果 res 中并返回即可。否则从 start 开始遍历,这里的变量 start 表示当前递归开始的位置,不需要每次都从头开始,会有大量重复计算。而且对于多个相同的半括号在一起,只删除第一个,比如 “())”,这里有两个右括号,不管删第一个还是删第二个右括号都会得到 “()”,没有区别,所以只用算一次就行了,通过和上一个字符比较,如果不相同,说明是第一个右括号,如果相同则直接跳过。此时来看如果 cnt1 大于0,说明此时左括号多,而如果当前字符正好是左括号的时候,可以删掉当前左括号,继续调用递归,此时 cnt1 的值就应该减1,因为已经删掉了一个左括号。同理,如果 cnt2 大于0,说明此时右括号多,而如果当前字符正好是右括号的时候,可以删掉当前右括号,继续调用递归,此时 cnt2 的值就应该减1,因为已经删掉了一个右括号,参见代码如下:

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
class Solution {
public:
vector<string> removeInvalidParentheses(string s) {
vector<string> res;
int cnt1 = 0, cnt2 = 0;
for (char c : s) {
cnt1 += (c == '(');
if (cnt1 == 0) cnt2 += (c == ')');
else cnt1 -= (c == ')');
}
helper(s, 0, cnt1, cnt2, res);
return res;
}
void helper(string s, int start, int cnt1, int cnt2, vector<string>& res) {
if (cnt1 == 0 && cnt2 == 0) {
if (isValid(s)) res.push_back(s);
return;
}
for (int i = start; i < s.size(); ++i) {
if (i != start && s[i] == s[i - 1]) continue;
if (cnt1 > 0 && s[i] == '(') {
helper(s.substr(0, i) + s.substr(i + 1), i, cnt1 - 1, cnt2, res);
}
if (cnt2 > 0 && s[i] == ')') {
helper(s.substr(0, i) + s.substr(i + 1), i, cnt1, cnt2 - 1, res);
}
}
}
bool isValid(string t) {
int cnt = 0;
for (int i = 0; i < t.size(); ++i) {
if (t[i] == '(') ++cnt;
else if (t[i] == ')' && --cnt < 0) return false;
}
return cnt == 0;
}
};

下面这种解法由热心网友 fvglty 提供,应该算是一种暴力搜索的方法,并没有太多的技巧在里面,但是思路直接了当,可以作为为面试中最先提出的解法。思路是先将s放到一个 HashSet 中,然后进行该集合 cur 不为空的 while 循环,此时新建另一个集合 next,遍历之前的集合 cur,若某个字符串是合法的括号,直接加到结果 res 中,并且看若 res 不为空,则直接跳过。跳过的部分实际上是去除括号的操作,由于不知道该去掉哪个半括号,所以只要遇到半括号就都去掉,然后加入另一个集合 next 中,这里实际上保存的是下一层的候选者。当前的 cur 遍历完成后,若 res 不为空,则直接返回,因为这是当前层的合法括号,一定是移除数最少的。若 res 为空,则将 next 赋值给 cur,继续循环,参见代码如下:

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
class Solution {
public:
vector<string> removeInvalidParentheses(string s) {
vector<string> res;
unordered_set<string> cur{{s}};
while (!cur.empty()) {
unordered_set<string> next;
for (auto &a : cur) {
if (isValid(a)) res.push_back(a);
if (!res.empty()) continue;
for (int i = 0; i < a.size(); ++i) {
if (a[i] != '(' && a[i] != ')') continue;
next.insert(a.substr(0, i) + a.substr(i + 1));
}
}
if (!res.empty()) return res;
cur = next;
}
return res;
}
bool isValid(string t) {
int cnt = 0;
for (int i = 0; i < t.size(); ++i) {
if (t[i] == '(') ++cnt;
else if (t[i] == ')' && --cnt < 0) return false;
}
return cnt == 0;
}
};

这种解法的思路是先找到合法字符串的长度,对每段相同字符的序列进行统计,因为每段相同字符序列一定有0个、1个或者多个会被留下来,遍历这些序列,搜索去掉一个序列中的字符是否合法:

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
class Solution {
public:

vector<pair<char, int> > data;
vector<string> ret;
int maxlength;

string gen(vector<pair<char, int>> ans) {
string str;
for (const auto& a : ans)
str += string(a.second, a.first);
return str;
}

// cur是当前要枚举哪一块字符串,s是字符串的和,左括号+1,右括号-1
// len是当前选中的字符数量,len==maxlength
// s最后应该为0才合法
void dfs(int cur, int s, int len, vector<pair<char, int>>& ans) {
if (cur == data.size()) {
if (!s && len == maxlength)
ret.push_back(gen(ans));
return;
}

if (data[cur].first != '(' && data[cur].first != ')') {
if (len + data[cur].second > maxlength)
return;
ans.push_back(data[cur]);
dfs(cur+1, s, len+data[cur].second, ans);
ans.pop_back();
return;
}

ans.push_back(data[cur]);
for (int i = 0; i <= data[cur].second; i ++) {
if (data[cur].first == '(') {
if (len + i <= maxlength) {
ans[cur].second = i; // ans
dfs(cur+1, s+i, len+i, ans);
}
}
else { // ')'
if (s >= i && len + i <= maxlength) {
ans[cur].second = i;
dfs(cur+1, s-i, len+i, ans);
}
}
}

ans.pop_back();
}

vector<string> removeInvalidParentheses(string str) {
int cnt = 0, s = 0, r = 0;
for (int i = 0; i < str.length(); i ++) {
if (!i || str[i] == str[i-1])
cnt ++;
else {
data.push_back(make_pair(str[i-1], cnt));
cnt = 1;
}

if (str[i] == '(')
s ++;
else if (str[i] == ')') {
if (s) s --;
else r ++;
}
}
data.push_back(make_pair(str.back(), cnt));
maxlength = str.length() - (s+r);

vector<pair<char, int> > ans;
dfs(0, 0, 0, ans);

return ret;
}
};

Leetcode303. Range Sum Query - Immutable

Given an integer array nums, find the sum of the elements between indices i and j (i ≤ j), inclusive.

Example:

1
2
3
4
5
Given nums = [-2, 0, 3, -5, 2, -1]

sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class NumArray {
public:
vector<int> prefix;

NumArray(vector<int>& nums) {
prefix.push_back(0);
for(int i=0;i<nums.size();i++)
{
prefix.push_back(prefix[i]+nums[i]);
}
}

int sumRange(int i, int j) {
return prefix[j+1]-prefix[i];
}
};

Leetcode304. Range Sum Query 2D - Immutable

Given a 2D matrix matrix, handle multiple queries of the following type:

Calculate the sum of the elements of matrix inside the rectangle defined by its upper left corner (row1, col1) and lower right corner (row2, col2).
Implement the NumMatrix class:

NumMatrix(int[][] matrix) Initializes the object with the integer matrix matrix.
int sumRegion(int row1, int col1, int row2, int col2) Returns the sum of the elements of matrix inside the rectangle defined by its upper left corner (row1, col1) and lower right corner (row2, col2).

Example 1:

1
2
3
4
5
Input
["NumMatrix", "sumRegion", "sumRegion", "sumRegion"]
[[[[3, 0, 1, 4, 2], [5, 6, 3, 2, 1], [1, 2, 0, 1, 5], [4, 1, 0, 1, 7], [1, 0, 3, 0, 5]]], [2, 1, 4, 3], [1, 1, 2, 2], [1, 2, 2, 4]]
Output
[null, 8, 11, 12]

Explanation

1
2
3
4
NumMatrix numMatrix = new NumMatrix([[3, 0, 1, 4, 2], [5, 6, 3, 2, 1], [1, 2, 0, 1, 5], [4, 1, 0, 1, 7], [1, 0, 3, 0, 5]]);
numMatrix.sumRegion(2, 1, 4, 3); // return 8 (i.e sum of the red rectangle)
numMatrix.sumRegion(1, 1, 2, 2); // return 11 (i.e sum of the green rectangle)
numMatrix.sumRegion(1, 2, 2, 4); // return 12 (i.e sum of the blue rectangle)

求二维数组中指定左上角和右下角的长方形内所有数字的和。给定的二维数组是不会变的,每次变得是求和的范围。

解题方法:预先求和

这个题肯定是用先把所有的和求出来,然后查找的时候直接计算就行了。我们使用的这个求和矩阵保存的是每个位置到整个矩阵的左上角元素这个矩形的所有元素和。为了方便起见,利用了和DP类似的添加边界的方法,也就是在最左边和最上边添加了全是0的列和行,这样能保证在求和的时候,每个位置的和是左边的和+上边的和+自身-左上元素的和。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class NumMatrix {
public:
vector<vector<int> > dp;
NumMatrix(vector<vector<int>>& matrix) {
int width = matrix.size();
if (width == 0)
return;
int height = matrix[0].size();

dp.resize(width+1);
for (int i = 0; i < dp.size(); i++)
dp[i].resize(height+1, 0);

for (int i = 1; i <= width; i ++)
for (int j = 1; j <= height; j ++)
dp[i][j] = dp[i][j-1] + dp[i-1][j] - dp[i-1][j-1] + matrix[i-1][j-1];
}

int sumRegion(int row1, int col1, int row2, int col2) {
return dp[row2+1][col2+1] + dp[row1+1][col1+1] - dp[row1-1][col2] - dp[row2][col1-1];
}
};

Leetcode305. Number of Islands II

A 2d grid map of m rows and n columns is initially filled with water. We may perform an addLand operation which turns the water at position (row, col) into a land. Given a list of positions to operate, count the number of islands after each addLand operation. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.

Example:

1
2
Input: m = 3, n = 3, positions = [[0,0], [0,1], [1,2], [2,1]]
Output: [1,1,2,3]

Explanation:

Initially, the 2d grid grid is filled with water. (Assume 0 represents water and 1 represents land).

1
2
3
0 0 0
0 0 0
0 0 0

Operation 1: addLand(0, 0) turns the water at grid[0][0] into a land.

1
2
3
1 0 0
0 0 0 Number of islands = 1
0 0 0

Operation 2: addLand(0, 1) turns the water at grid[0][1] into a land.

1
2
3
1 1 0
0 0 0 Number of islands = 1
0 0 0

Operation 3: addLand(1, 2) turns the water at grid[1][2] into a land.

1
2
3
1 1 0
0 0 1 Number of islands = 2
0 0 0

Operation 4: addLand(2, 1) turns the water at grid[2][1] into a land.

1
2
3
1 1 0
0 0 1 Number of islands = 3
0 1 0

这道题是之前那道 Number of Islands 的拓展,难度增加了不少,因为这次是一个点一个点的增加,每增加一个点,都要统一一下现在总共的岛屿个数,最开始初始化时没有陆地,如下:

1
2
3
0 0 0
0 0 0
0 0 0

假如在(0, 0)的位置增加一个陆地,那么此时岛屿数量为1:

1
2
3
1 0 0
0 0 0
0 0 0

假如再在(0, 2)的位置增加一个陆地,那么此时岛屿数量为2:

1
2
3
1 0 1
0 0 0
0 0 0

假如再在(0, 1)的位置增加一个陆地,那么此时岛屿数量却又变为1:

1
2
3
1 1 1
0 0 0
0 0 0

假如再在(1, 1)的位置增加一个陆地,那么此时岛屿数量仍为1:

1
2
3
1 1 1
0 1 0
0 0 0

为了解决这种陆地之间会合并的情况,最好能够将每个陆地都标记出其属于哪个岛屿,这样就会方便统计岛屿个数。这种群组类问题,很适合使用联合查找 Union Find 来做,又叫并查集 Disjoint Set,一般来说,UF 算法的思路是每个个体先初始化为不同的群组,然后遍历有关联的两个个体,如果发现其 getRoot 函数的返回值不同,则手动将二者加入一个群组,然后总群组数自减1。这里就要分别说一下 root 数组,和 getRoot 函数。两个同群组的个体,通过 getRoot 函数一定会返回相同的值,但是其在 root 数组中的值不一定相同,可以类比成 getRoot 函数返回的是祖先,如果两个人的祖先相同,那么其是属于一个家族的(这里不是指人类共同的祖先哈)。root 可以用数组或者 HashMap 来表示,如果个体是数字的话,那么数组就 OK,如果个体是字符串的话,可能就需要用 HashMap 了。root 数组的初始化可以有两种,可以均初始化为 -1,或者都初始化为不同的数字,博主一般喜欢初始化为不同的数字。getRoot 函数的写法也可用递归或者迭代的方式。

那么具体来看这道题吧,此题跟经典的 UF 使用场景有一点点的区别,因为一般的场景中两个个体之间只有两种关系,属于一个群组或者不属于同一个群组,而这道题里面由于 water 的存在,就多了一种情况,只需要事先检测一下当前位置是不是岛屿就行了,总之问题不大。一般来说 root 数组都是使用一维数组,方便一些,那么这里就可以将二维数组 encode 为一维的,于是需要一个长度为 m*n 的一维数组来标记各个位置属于哪个岛屿,假设每个位置都是一个单独岛屿,岛屿编号可以用其坐标位置表示,但是初始化时将其都赋为 -1,这样方便知道哪些位置尚未变成岛屿。然后开始遍历陆地数组,若某个岛屿位置编码的 root 值不为 -1,说明这是一个重复出现的位置,不需要重新计算了,直接将 cnt 加入结果 res 中。否则将其岛屿编号设置为其坐标位置,然后岛屿计数加1,此时开始遍历其上下左右的位置,遇到越界或者岛屿标号为 -1 的情况直接跳过,现在知道初始化为 -1 的好处了吧,遇到是 water 的地方直接跳过。否则用 getRoot 来查找邻居位置的岛屿编号,同时也用 getRoot 来查找当前点的编号,这一步就是经典的 UF 算法的操作了,因为当前这两个 land 是相邻的,它们是属于一个岛屿,所以其 getRoot 函数的返回值 suppose 应该是相等的,但是如果返回值不同,说明需要合并岛屿,将两个返回值建立关联,并将岛屿计数 cnt 减1。当遍历完当前点的所有邻居时,该合并的都合并完了,将此时的岛屿计数 cnt 存入结果中,参见代码如下:

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
class Solution {
public:
vector<int> numIslands2(int m, int n, vector<vector<int>>& positions) {
vector<int> res;
int cnt = 0;
vector<int> roots(m * n, -1);
vector<vector<int>> dirs{{0, -1}, {-1, 0}, {0, 1}, {1, 0}};
for (auto &pos : positions) {
int id = n * pos[0] + pos[1];
if (roots[id] != -1) {
res.push_back(cnt);
continue;
}
roots[id] = id;
++cnt;
for (auto dir : dirs) {
int x = pos[0] + dir[0], y = pos[1] + dir[1], cur_id = n * x + y;
if (x < 0 || x >= m || y < 0 || y >= n || roots[cur_id] == -1) continue;
int p = findRoot(roots, cur_id), q = findRoot(roots, id);
if (p != q) {
roots[p] = q;
--cnt;
}
}
res.push_back(cnt);
}
return res;
}
int findRoot(vector<int>& roots, int id) {
return (id == roots[id]) ? id : findRoot(roots, roots[id]);
}
};

Leetcode306. Additive Number

Additive number is a string whose digits can form additive sequence.

A valid additive sequence should contain at least three numbers. Except for the first two numbers, each subsequent number in the sequence must be the sum of the preceding two.

Given a string containing only digits ‘0’-‘9’, write a function to determine if it’s an additive number.

Note: Numbers in the additive sequence cannot have leading zeros, so sequence 1, 2, 03 or 1, 02, 3is invalid.

Example 1:

1
2
3
4
Input: "112358"
Output: true
Explanation: The digits can form an additive sequence: 1, 1, 2, 3, 5, 8.
1 + 1 = 2, 1 + 2 = 3, 2 + 3 = 5, 3 + 5 = 8

Example 2:

1
2
3
4
Input: "199100199"
Output: true
Explanation: The additive sequence is: 1, 99, 100, 199.
1 + 99 = 100, 99 + 100 = 199

这道题定义了一种加法数,就是至少含有三个数字,除去前两个数外,每个数字都是前面两个数字的和,题目中给了许多例子,也限定了一些不合法的情况,比如两位数以上不能以0开头等等,让我们来判断一个数是否是加法数。

其实这题可用Brute Force的思想来解,我们让第一个数字先从一位开始,第二个数字从一位,两位,往高位开始搜索,前两个数字确定了,相加得到第三位数字,三个数组排列起来形成一个字符串,和原字符串长度相比,如果小于原长度,那么取出上一次计算的第二个和第三个数,当做新一次计算的前两个数,用相同的方法得到第三个数,再加入当前字符串,再和原字符串长度相比,以此类推,直到当前字符串长度不小于原字符串长度,比较两者是否相同,相同返回true,不相同则继续循环。如果所有情况都遍历完了还是没有返回true,则说明不是Additive Number,返回false,参见代码如下:

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
class Solution {
public:
bool isAdditiveNumber(string num) {
for (int i = 1; i < num.size(); ++i) {
string s1 = num.substr(0, i);
if (s1.size() > 1 && s1[0] == '0') break;
for (int j = i + 1; j < num.size(); ++j) {
string s2 = num.substr(i, j - i);
long d1 = stol(s1), d2 = stol(s2);
if ((s2.size() > 1 && s2[0] == '0')) break;
long next = d1 + d2;
string nextStr = to_string(next);
if (nextStr != num.substr(j, nextStr.length())) continue; // optimization here
string allStr = s1 + s2 + nextStr;
while (allStr.size() < num.size()) {
d1 = d2;
d2 = next;
next = d1 + d2;
nextStr = to_string(next);
allStr += nextStr;
}
if (allStr == num) return true;
}
}
return false;
}
};

Leetcode307. Range Sum Query - Mutable

Given an integer array nums , find the sum of the elements between indices i and j ( i ≤ j ), inclusive.

The update(i, val) function modifies nums by updating the element at index i to val.

Example:

1
2
3
4
5
Given nums = [1, 3, 5]

sumRange(0, 2) -> 9
update(1, 2)
sumRange(0, 2) -> 8

这道题是之前那道 Range Sum Query - Immutable 的延伸,之前那道题由于数组的内容不会改变,所以我们只需要建立一个累计数组就可以支持快速的计算区间值了,而这道题说数组的内容会改变,如果我们还是用之前的方法建立累计和数组,那么每改变一个数字,之后所有位置的数字都要改变,这样如果有很多更新操作的话,就会十分不高效,估计很难通过吧。But,被 OJ 分分钟打脸, brute force 完全没有问题啊,这年头,装个比不容易啊。直接就用个数组 data 接住 nums,然后要更新就更新,要求区域和,就遍历求区域和,就这样 naive 的方法还能 beat 百分之二十多啊,这不科学啊,参见代码如下:

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class NumArray {
public:
NumArray(vector<int> nums) {
data = nums;
}

void update(int i, int val) {
data[i] = val;
}

int sumRange(int i, int j) {
int sum = 0;
for (int k = i; k <= j; ++k) {
sum += data[k];
}
return sum;
}

private:
vector<int> data;
};

上面的方法最大的问题,就是求区域和不高效,如果数组很大很大,每次求一个巨型的区间的和,都要一个一个的遍历去累加,累啊~但是一般的累加数组又无法应对这里的 update 操作,随便修改一个数字的话,那么其之后的所有累加和都会发生改变。所以解决方案就是二者折中一下,分块累加,各不干预。就是将原数组分为若干块,怎么分呢,这里就让每个 block 有 sqrt(n) 个数字就可以了,这个基本是让 block 的个数跟每个 blcok 中数字的个数尽可能相同的分割方法。然后我们就需要一个大小跟 block 个数相同的数组,来保存每个 block 的数字之和。在需要更新的时候,我们就先确定要更新的位置在哪个 block 里,然后只更新该 block 的和。而对于求区域和操作,我们还是要分别确定i和j分别属于哪个 block,若属于同一个 block,那么直接遍历累加即可,若属于不同的,则先从i累加到该 blcok 的末尾,然后中间横跨的那些 block 可以直接将和累加,对于j所在的 blcok,则从该 block 的开头遍历累加到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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class NumArray {
public:
NumArray(vector<int> nums) {
if (nums.empty()) return;
data = nums;
double root = sqrt(data.size());
len = ceil(data.size() / root);
block.resize(len);
for (int i = 0; i < data.size(); ++i) {
block[i / len] += data[i];
}
}

void update(int i, int val) {
int idx = i / len;
block[idx] += val - data[i];
data[i] = val;
}

int sumRange(int i, int j) {
int sum = 0;
int start = i / len, end = j / len;
if (start == end) {
for (int k = i; k <= j; ++k) {
sum += data[k];
}
return sum;
}
for (int k = i; k < (start + 1) * len; ++k) {
sum += data[k];
}
for (int k = start + 1; k < end; ++k) {
sum += block[k];
}
for (int k = end * len; k <= j; ++k) {
sum += data[k];
}
return sum;
}

private:
int len;
vector<int> data, block;
};

Leetcode309. Best Time to Buy and Sell Stock with Cooldown

Say you have an array for which the i th element is the price of a given stock on day i.

Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times) with the following restrictions:

You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).After you sell your stock, you cannot buy stock on next day. (ie, cooldown 1 day)

Example:

1
2
3
prices = [1, 2, 3, 0, 2]
maxProfit = 3
transactions = [buy, sell, cooldown, buy, sell]

这道题加入了一个冷冻期Cooldown之说,就是如果某天卖了股票,那么第二天不能买股票,有一天的冷冻期。此题需要维护三个一维数组buy, sell,和rest。其中:

  • buy[i]表示在第i天之前最后一个操作是买,此时的最大收益。
  • sell[i]表示在第i天之前最后一个操作是卖,此时的最大收益。
  • rest[i]表示在第i天之前最后一个操作是冷冻期,此时的最大收益。

我们写出递推式为:

1
2
3
buy[i]  = max(rest[i-1] - price, buy[i-1]) 
sell[i] = max(buy[i-1] + price, sell[i-1])
rest[i] = max(sell[i-1], buy[i-1], rest[i-1])

上述递推式很好的表示了在买之前有冷冻期,买之前要卖掉之前的股票。一个小技巧是如何保证[buy, rest, buy]的情况不会出现,这是由于buy[i] <= rest[i], 即rest[i] = max(sell[i-1], rest[i-1]),这保证了[buy, rest, buy]不会出现。

另外,由于冷冻期的存在,我们可以得出rest[i] = sell[i-1],这样,我们可以将上面三个递推式精简到两个:

1
2
buy[i]  = max(sell[i-2] - price, buy[i-1]) 
sell[i] = max(buy[i-1] + price, sell[i-1])

我们还可以做进一步优化,由于i只依赖于i-1和i-2,所以我们可以在O(1)的空间复杂度完成算法,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int maxProfit(vector<int>& prices) {
int buy = INT_MIN, pre_buy = 0, sell = 0, pre_sell = 0;
for (int price : prices) {
pre_buy = buy;
buy = max(pre_sell - price, pre_buy);
pre_sell = sell;
sell = max(pre_buy + price, pre_sell);
}
return sell;
}
};

我自己的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
if (len == 0)
return 0;
vector<int> buy(len+1, 0), sell(len+1, 0);
buy[1] = -prices[0];
for (int i = 2; i < len+1; i ++) {
buy[i] = max(sell[i-2]-prices[i-1], buy[i-1]);
sell[i] = max(sell[i-1], buy[i-1]+prices[i-1]);
}
return sell[len];
}
};

Leetcode310. Minimum Height Trees

A tree is an undirected graph in which any two vertices are connected by exactly one path. In other words, any connected graph without simple cycles is a tree.

Given a tree of n nodes labelled from 0 to n - 1, and an array of n - 1 edges where edges[i] = [ai, bi] indicates that there is an undirected edge between the two nodes ai and bi in the tree, you can choose any node of the tree as the root. When you select a node x as the root, the result tree has height h. Among all possible rooted trees, those with minimum height (i.e. min(h)) are called minimum height trees (MHTs).

Return a list of all MHTs’ root labels. You can return the answer in any order.

The height of a rooted tree is the number of edges on the longest downward path between the root and a leaf.

Example 1:

1
2
3
Input: n = 4, edges = [[1,0],[1,2],[1,3]]
Output: [1]
Explanation: As shown, the height of the tree is 1 when the root is the node with label 1 which is the only MHT.

Example 2:

1
2
Input: n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]]
Output: [3,4]

Example 3:

1
2
Input: n = 1, edges = []
Output: [0]

Example 4:

1
2
Input: n = 2, edges = [[0,1]]
Output: [0,1]

大家推崇的方法是一个类似剥洋葱的方法,就是一层一层的褪去叶节点,最后剩下的一个或两个节点就是我们要求的最小高度树的根节点,这种思路非常的巧妙,而且实现起来也不难,跟之前那到课程清单的题一样,我们需要建立一个图g,是一个二维数组,其中g[i]是一个一维数组,保存了i节点可以到达的所有节点。我们开始将所有只有一个连接边的节点(叶节点)都存入到一个队列queue中,然后我们遍历每一个叶节点,通过图来找到和其相连的节点,并且在其相连节点的集合中将该叶节点删去,如果删完后此节点也也变成一个叶节点了,加入队列中,下一轮删除。那么我们删到什么时候呢,当节点数小于等于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
class Solution {
public:
vector<int> findMinHeightTrees(int n, vector<pair<int, int> >& edges) {
if (n == 1) return {0};
vector<int> res;
vector<unordered_set<int>> adj(n);
queue<int> q;
for (auto edge : edges) {
adj[edge.first].insert(edge.second);
adj[edge.second].insert(edge.first);
}
for (int i = 0; i < n; ++i) {
if (adj[i].size() == 1) q.push(i);
}
while (n > 2) {
int size = q.size();
n -= size;
for (int i = 0; i < size; ++i) {
int t = q.front(); q.pop();
for (auto a : adj[t]) {
adj[a].erase(t);
if (adj[a].size() == 1) q.push(a);
}
}
}
while (!q.empty()) {
res.push_back(q.front()); q.pop();
}
return res;
}
};

Leetcode312. Burst Balloons

Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by array nums. You are asked to burst all the balloons. If the you burst balloon iyou will get nums[left] * nums[i] * nums[right]coins. Here left and right are adjacent indices of i. After the burst, the left and right then becomes adjacent.

Find the maximum coins you can collect by bursting the balloons wisely.

Note:

  • You may imagine nums[-1] = nums[n] = 1. They are not real therefore you can not burst them.
  • 0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100

Example:

1
2
3
4
Input: [3,1,5,8]
Output: 167
Explanation: nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167

这道题提出了一种打气球的游戏,每个气球都对应着一个数字,每次打爆一个气球,得到的金币数是被打爆的气球的数字和其两边的气球上的数字相乘,如果旁边没有气球了,则按1算,以此类推,求能得到的最多金币数。参见题目中给的例子,题意并不难理解。那么大家拿到题后,总是会习惯的先去想一下暴力破解法吧,这道题的暴力搜索将相当的复杂,因为每打爆一个气球,断开的地方又重新挨上,所有剩下的气球又要重新遍历,这使得分治法不能 work,整个的时间复杂度会相当的高,不要指望可以通过 OJ。而对于像这种求极值问题,一般都要考虑用动态规划 Dynamic Programming 来做,维护一个二维动态数组 dp,其中 dp[i][j] 表示打爆区间 [i,j] 中的所有气球能得到的最多金币。题目中说明了边界情况,当气球周围没有气球的时候,旁边的数字按1算,这样可以在原数组两边各填充一个1,方便于计算。这道题的最难点就是找状态转移方程,还是从定义式来看,假如区间只有一个数,比如 dp[i][i],那么计算起来就很简单,直接乘以周围两个数字即可更新。如果区间里有两个数字,就要算两次了,先打破第一个再打破了第二个,或者先打破第二个再打破第一个,比较两种情况,其中较大值就是该区间的 dp 值。假如区间有三个数呢,比如 dp[1][3],怎么更新呢?如果先打破第一个,剩下两个怎么办呢,难道还要分别再遍历算一下吗?这样跟暴力搜索的方法有啥区别呢,还要 dp 数组有啥意思。所谓的状态转移,就是假设已知了其他状态,来推导现在的状态,现在是想知道 dp[1][3] 的值,那么如果先打破了气球1,剩下了气球2和3,若之前已经计算了 dp[2][3] 的话,就可以使用其来更新 dp[1][3] 了,就是打破气球1的得分加上 dp[2][3]。那假如先打破气球2呢,只要之前计算了 dp[1][1] 和 dp[3][3],那么三者加起来就可以更新 dp[1][3]。同理,先打破气球3,就用其得分加上 dp[1][2] 来更新 dp[1][3]。

那么对于有很多数的区间 [i, j],如何来更新呢?现在是想知道 dp[i][j] 的值,这个区间可能比较大,但是如果知道了所有的小区间的 dp 值,然后聚沙成塔,逐步的就能推出大区间的 dp 值了。还是要遍历这个区间内的每个气球,就用k来遍历吧,k在区间 [i, j] 中,假如第k个气球最后被打爆,那么此时区间 [i, j] 被分成了三部分,[i, k-1],[k],和 [k+1, j],只要之前更新过了 [i, k-1] 和 [k+1, j] 这两个子区间的 dp 值,可以直接用 dp[i][k-1] 和 dp[k+1][j],那么最后被打爆的第k个气球的得分该怎么算呢,你可能会下意识的说,就乘以周围两个气球被 nums[k-1] * nums[k] * nums[k+1],但其实这样是错误的,为啥呢?dp[i][k-1] 的意义是什么呢,是打爆区间 [i, k-1] 内所有的气球后的最大得分,此时第 k-1 个气球已经不能用了,同理,第 k+1 个气球也不能用了,相当于区间 [i, j] 中除了第k个气球,其他的已经爆了,那么周围的气球只能是第 i-1 个,和第 j+1 个了,所以得分应为 nums[i-1] * nums[k] * nums[j+1],分析到这里,状态转移方程应该已经跃然纸上了吧,如下所示:

 dp[i][j] = max(dp[i][j], nums[i - 1] * nums[k] * nums[j + 1] + dp[i][k - 1] + dp[k + 1][j])                 ( i ≤ k ≤ j )

有了状态转移方程了,就可以写代码,下面就遇到本题的第二大难点了,区间的遍历顺序。一般来说,遍历所有子区间的顺序都是i从0到n,然后j从i到n,然后得到的 [i, j] 就是子区间。但是这道题用这种遍历顺序就不对,在前面的分析中已经说了,这里需要先更新完所有的小区间,然后才能去更新大区间,而用这种一般的遍历子区间的顺序,会在更新完所有小区间之前就更新了大区间,从而不一定能算出正确的dp值,比如拿题目中的那个例子 [3, 1, 5, 8] 来说,一般的遍历顺序是:

[3] -> [3, 1] -> [3, 1, 5] -> [3, 1, 5, 8] -> [1] -> [1, 5] -> [1, 5, 8] -> [5] -> [5, 8] -> [8] 

显然不是我们需要的遍历顺序,正确的顺序应该是先遍历完所有长度为1的区间,再是长度为2的区间,再依次累加长度,直到最后才遍历整个区间:

[3] -> [1] -> [5] -> [8] -> [3, 1] -> [1, 5] -> [5, 8] -> [3, 1, 5] -> [1, 5, 8] -> [3, 1, 5, 8]

这里其实只是更新了 dp 数组的右上三角区域,最终要返回的值存在 dp[1][n] 中,其中n是两端添加1之前数组 nums 的个数。参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int maxCoins(vector<int>& nums) {
int n = nums.size();
nums.insert(nums.begin(), 1);
nums.push_back(1);
vector<vector<int>> dp(n + 2, vector<int>(n + 2, 0));
for (int len = 1; len <= n; ++len) {
for (int i = 1; i <= n - len + 1; ++i) {
int j = i + len - 1;
for (int k = i; k <= j; ++k) {
dp[i][j] = max(dp[i][j], nums[i - 1] * nums[k] * nums[j + 1] + dp[i][k - 1] + dp[k + 1][j]);
}
}
}
return dp[1][n];
}
};

对于题目中的例子[3, 1, 5, 8],得到的dp数组如下:

1
2
3
4
5
6
0    0    0    0    0    0
0 3 30 159 167 0
0 0 15 135 159 0
0 0 0 40 48 0
0 0 0 0 40 0
0 0 0 0 0 0

这题还有递归解法,思路都一样,就是写法略有不同,参见代码如下:

解法二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int maxCoins(vector<int>& nums) {
int n = nums.size();
nums.insert(nums.begin(), 1);
nums.push_back(1);
vector<vector<int>> dp(n + 2, vector<int>(n + 2, 0));
return burst(nums, dp, 1 , n);
}
int burst(vector<int>& nums, vector<vector<int>>& dp, int i, int j) {
if (i > j) return 0;
if (dp[i][j] > 0) return dp[i][j];
int res = 0;
for (int k = i; k <= j; ++k) {
res = max(res, nums[i - 1] * nums[k] * nums[j + 1] + burst(nums, dp, i, k - 1) + burst(nums, dp, k + 1, j));
}
dp[i][j] = res;
return res;
}
};

Leetcode313. Super Ugly Number

Write a program to find the nth super ugly number.

Super ugly numbers are positive numbers whose all prime factors are in the given prime list primes of sizek. For example, [1, 2, 4, 7, 8, 13, 14, 16, 19, 26, 28, 32] is the sequence of the first 12 super ugly numbers given primes = [2, 7, 13, 19] of size 4.

Note:

  1. 1 is a super ugly number for any given primes.
  2. The given numbers in primes are in ascending order.
  3. 0 < k ≤ 100, 0 < n ≤ 106, 0 < primes[i] < 1000.

这道题让我们求超级丑陋数,是之前那两道Ugly Number 丑陋数和Ugly Number II 丑陋数之二的延伸,质数集合可以任意给定,这就增加了难度。但是本质上和Ugly Number II 丑陋数之二没有什么区别,由于我们不知道质数的个数,我们可以用一个idx数组来保存当前的位置,然后我们从每个子链中取出一个数,找出其中最小值,然后更新idx数组对应位置,注意有可能最小值不止一个,要更新所有最小值的位置,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
int nthSuperUglyNumber(int n, vector<int>& primes) {
vector<int> res(1, 1), idx(primes.size(), 0);
while (res.size() < n) {
vector<int> tmp;
int mn = INT_MAX;
for (int i = 0; i < primes.size(); ++i) {
tmp.push_back(res[idx[i]] * primes[i]);
}
for (int i = 0; i < primes.size(); ++i) {
mn = min(mn, tmp[i]);
}
for (int i = 0; i < primes.size(); ++i) {
if (mn == tmp[i]) ++idx[i];
}
res.push_back(mn);
}
return res.back();
}
};

Leetcode315. Count of Smaller Numbers After Self

You are given an integer array nums and you have to return a new counts array. The counts array has the property where counts[i] is the number of smaller elements to the right of nums[i].

Example:

1
2
3
4
5
6
7
Input: [5,2,6,1]
Output: [2,1,1,0]
Explanation:
To the right of 5 there are 2 smaller elements (2 and 1).
To the right of 2 there is only 1 smaller element (1).
To the right of 6 there is 1 smaller element (1).
To the right of 1 there is 0 smaller element.

这道题给定了一个数组,让我们计算每个数字右边所有小于这个数字的个数,目测不能用 brute force,OJ 肯定不答应,那么为了提高运算效率,首先可以使用用二分搜索法,思路是将给定数组从最后一个开始,用二分法插入到一个新的数组,这样新数组就是有序的,那么此时该数字在新数组中的坐标就是原数组中其右边所有较小数字的个数,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Binary Search
class Solution {
public:
vector<int> countSmaller(vector<int>& nums) {
vector<int> t, res(nums.size());
for (int i = nums.size() - 1; i >= 0; --i) {
int left = 0, right = t.size();
while (left < right) {
int mid = left + (right - left) / 2;
if (t[mid] >= nums[i]) right = mid;
else left = mid + 1;
}
res[i] = right;
t.insert(t.begin() + right, nums[i]);
}
return res;
}
};

上面使用二分搜索法是一种插入排序的做法,我们还可以用 C++ 中的 STL 的一些自带的函数,比如求距离 distance,或是求第一个不小于当前数字的函数 lower_bound(),这里利用这两个函数代替了上一种方法中的二分搜索的部分,两种方法的核心思想都是相同的,构造有序数组,找出新加进来的数组在有序数组中对应的位置存入结果中即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Insert Sort
class Solution {
public:
vector<int> countSmaller(vector<int>& nums) {
vector<int> t, res(nums.size());
for (int i = nums.size() - 1; i >= 0; --i) {
int d = distance(t.begin(), lower_bound(t.begin(), t.end(), nums[i]));
res[i] = d;
t.insert(t.begin() + d, nums[i]);
}
return res;
}
};

再来看一种利用二分搜索树来解的方法,构造一棵二分搜索树,稍有不同的地方是需要加一个变量 smaller 来记录比当前结点值小的所有结点的个数,每插入一个结点,会判断其和根结点的大小,如果新的结点值小于根结点值,则其会插入到左子树中,此时要增加根结点的 smaller,并继续递归调用左子结点的 insert。如果结点值大于根结点值,则需要递归调用右子结点的 insert 并加上根结点的 smaller,并加1,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Binary Search Tree
class Solution {
public:
struct Node {
int val, smaller;
Node *left, *right;
Node(int v, int s) : val(v), smaller(s), left(NULL), right(NULL) {}
};
int insert(Node*& root, int val) {
if (!root) return (root = new Node(val, 0)), 0;
if (root->val > val) return root->smaller++, insert(root->left, val);
return insert(root->right, val) + root->smaller + (root->val < val ? 1 : 0);
}
vector<int> countSmaller(vector<int>& nums) {
vector<int> res(nums.size());
Node *root = NULL;
for (int i = nums.size() - 1; i >= 0; --i) {
res[i] = insert(root, nums[i]);
}
return res;
}
};

我们通过一个实例来看看。假设我们有两个已排序的序列等待合并,分别是L = { 8, 12, 16, 22, 100 }R = { 7, 26, 55, 64, 91 }。一开始我们用指针 lPtr = 0 指向 LL 的头部,rPtr = 0 指向 RR 的头部。记已经合并好的部分为 MM。

1
2
3
L = [8, 12, 16, 22, 100]   R = [7, 26, 55, 64, 91]  M = []
| |
lPtr rPtr

我们发现 lPtr 指向的元素大于 rPtr 指向的元素,于是把 rPtr 指向的元素放入答案,并把 rPtr 后移一位。

1
2
3
L = [8, 12, 16, 22, 100]   R = [7, 26, 55, 64, 91]  M = [7]
| |
lPtr rPtr

接着我们继续合并:

1
2
3
L = [8, 12, 16, 22, 100]   R = [7, 26, 55, 64, 91]  M = [8, 9]
| |
lPtr rPtr

此时 lPtr 比 rPtr 小,把 lPtr 对应的数加入答案。如果我们要统计 88 的右边比 88 小的元素,这里 77 对它做了一次贡献。如果带合并的序列L={8,12,16,22,100}R={7,7,7,26,55,64,91},那么一定有一个时刻,lPtr 和 rPtr 分别指向这些对应的位置:

1
2
3
L = [8, 12, 16, 22, 100]   R = [7, 7, 7, 26, 55, 64, 91]  M = [7, 7, 7]
| |
lPtr rPtr

下一步我们就是把 88 加入 MM 中,此时三个 77 对 88 的右边比 88 小的元素的贡献为 33。以此类推,我们可以一边合并一边计算 RR 的头部到 rPtr 前一个数字对当前 lPtr 指向的数字的贡献。

我们发现用这种「算贡献」的思想在合并的过程中计算逆序对的数量的时候,只在 lPtr 右移的时候计算,是基于这样的事实:当前 lPtr 指向的数字比 rPtr 小,但是比 RR 中 [0 … rPtr - 1] 的其他数字大,[0 … rPtr - 1] 的数字是在 lPtr 右边但是比 lPtr 对应数小的数字,贡献为这些数字的个数。

但是我们又遇到了新的问题,在「并」的过程中 88 的位置一直在发生改变,我们应该把计算的贡献保存到哪里呢?这个时候我们引入一个新的数组,来记录每个数字对应的原数组中的下标,例如:

1
2
    a = [8, 9, 1, 5, 2]
index = [0, 1, 2, 3, 4]

排序的时候原数组和这个下标数组同时变化,则排序后我们得到这样的两个数组:

1
2
    a = [1, 2, 5, 8, 9]
index = [2, 4, 3, 0, 1]

我们用一个数组 ans 来记录贡献。我们对某个元素计算贡献的时候,如果它对应的下标为 p,我们只需要在 ans[p] 上加上贡献即可。

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
class Solution {
public:
vector<int> res;
vector<int> idx;

void merge(vector<int>& nums, int l, int r) {
if (l >= r)
return;
int mid = (l + r) >> 1;
merge(nums, l, mid);
merge(nums, mid+1, r);

vector<int> tmp(r - l + 1);
int k = 0, i = l, j = mid+1;
while(i <= mid && j <= r) {
if (nums[idx[i]] <= nums[idx[j]]) {
res[idx[i]] += (j - 1 - mid);
tmp[k++] = idx[i ++];
}
else
tmp[k++] = idx[j ++];
}

while(i <= mid) {
res[idx[i]] += r - mid;
tmp[k++] = idx[i++];
}

while(j <= r)
tmp[k++] = idx[j++];

for (int i = 0; i < tmp.size(); i ++)
idx[l+i] = tmp[i];
}

vector<int> countSmaller(vector<int>& nums) {
res.assign(nums.size(), 0);
idx.assign(nums.size(), 0);
for (int i = 0; i < nums.size(); i ++)
idx[i] = i;
merge(nums, 0, nums.size()-1);

return res;
}
};

Leetcode316. Remove Duplicate Letters

Given a string s, remove duplicate letters so that every letter appears once and only once. You must make sure your result is the smallest in lexicographical order among all possible results.

Example 1:

1
2
Input: s = "bcabc"
Output: "abc"

Example 2:

1
2
Input: s = "cbacdcbc"
Output: "acdb"

从一组字符串中取字符,使得生成结果中每个字符必须出现一次而且只出现一次,并且要求所得结果是字符串顺序最小的。

这个题的难点在于使得结果是字符串顺序最小。解题思路也是围绕这个展开。

先顺一下思路,首先,每个字符都必须要出现一次,那么当这个字符只有一次机会的时候,必须添加到结果字符串结尾中去,反之,如果这个字符的次数没有降为0,即后面还有机会,那么可以先把优先级高的放进来,把这个字符放到后面再处理。所以,我们可以使用一个栈,有点类似单调递增栈的意思,但其实并不是单调栈。我们的思路就是把还可以放到后面的字符弹出栈,留着以后处理,字符序小的插入到对应的位置。

首先,为了知道每个字符出现了多少次,必须做一次次数统计,这个步骤大家都是知道的。

然后,需要借助一个栈来实现字符串构造的操作。具体操作如下:

从输入字符串中逐个读取字符c,并把c的字符统计减一。

  1. 如果当前字符c已经在栈里面出现,那么跳过。
  2. 如果当前字符c在栈里面,那么:
    1. 如果当前字符c小于栈顶,并且栈顶元素有剩余(后面还能再添加进来),则出栈栈顶,标记栈顶不在栈中。重复该操作直到栈顶元素不满足条件或者栈为空。
    2. 入栈字符c,并且标记c已经在栈中。
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
class Solution {
public:
string removeDuplicateLetters(string s) {
string res = "";
int size = s.length();
unordered_map<char, int> m;
unordered_map<char, bool> visited;
for (int i = 0; i < size; i ++) {
m[s[i]] ++;
visited[s[i]] = false;
}

for (int i = 0; i < size; i ++) {
m[s[i]] --;
if (visited[s[i]])
continue;
while(!res.empty() && m[res.back()] > 0 && s[i] < res.back()) {
visited[res.back()] = false;
res.pop_back();
}
res += s[i];
visited[s[i]] = true;
}
return res;
}
};

Leetcode318. Maximum Product of Word Lengths

Given a string array words, find the maximum value of length(word[i]) * length(word[j]) where the two words do not share common letters. You may assume that each word will contain only lower case letters. If no such two words exist, return 0.

Example 1:

1
2
3
Given ["abcw", "baz", "foo", "bar", "xtfn", "abcdef"]
Return 16
The two words can be "abcw", "xtfn".

Example 2:

1
2
3
Given ["a", "ab", "abc", "d", "cd", "bcd", "abcd"]
Return 4
The two words can be "ab", "cd".

Example 3:

1
2
3
Given ["a", "aa", "aaa", "aaaa"]
Return 0
No such pair of words.

这道题给我们了一个单词数组,让我们求两个没有相同字母的单词的长度之积的最大值。我开始想的方法是每两个单词先比较,如果没有相同字母,则计算其长度之积,然后每次更新结果就能找到最大值。因为题目中说都是小写字母,那么只有26位,一个整型数int有32位,我们可以用后26位来对应26个字母,若为1,说明该对应位置的字母出现过,那么每个单词的都可由一个int数字表示,两个单词没有共同字母的条件是这两个int数与为0,用这个判断方法可以通过OJ,参见代码如下。注意移位运算符的优先级很低。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
int maxProduct(vector<string>& words) {
int len = words.size();
vector<int> wordss, lennn;
for (int i = 0; i < len; i ++) {
int t = 0;
for (int j = 0; j < words[i].length(); j ++) {
t = t | (1 << (words[i][j]-'a'));
}
wordss.push_back(t);
lennn.push_back(words[i].length());
}

int res = -1;
for (int i = 0; i < len; i ++)
for (int j = i + 1; j < len; j ++)
if ((wordss[i] & wordss[j]) == 0)
res = max(res, int(lennn[i] * lennn[j]));
return res;
}
};

Leetcode319. Bulb Switcher

There are n bulbs that are initially off. You first turn on all the bulbs. Then, you turn off every second bulb. On the third round, you toggle every third bulb (turning on if it’s off or turning off if it’s on). For the n th round, you only toggle the last bulb. Find how many bulbs are on after n rounds.

Example:

1
2
3
4
5
6
7
8
Given _n_ = 3.   

At first, the three bulbs are [off, off, off].
After first round, the three bulbs are [on, on, on].
After second round, the three bulbs are [on, off, on].
After third round, the three bulbs are [on, off, off].

So you should return 1, because there is only one bulb is on.

这道题给了我们n个灯泡,第一次打开所有的灯泡,第二次每两个更改灯泡的状态,第三次每三个更改灯泡的状态,以此类推,第n次每n个更改灯泡的状态。让我们求n次后,所有亮的灯泡的个数。此题是CareerCup 6.6 Toggle Lockers 切换锁的状态。

那么我们来看这道题吧,还是先枚举个小例子来分析下,比如只有5个灯泡的情况,’X’表示灭,‘√’表示亮,如下所示:

初始状态: X X X X X

第一次: √ √ √ √ √

第二次: √ X √ X √

第三次: √ X X X √

第四次: √ X X √ √

第五次: √ X X √ X

那么最后我们发现五次遍历后,只有1号和4号灯泡是亮的,而且很巧的是它们都是平方数,是巧合吗,还是其中有什么玄机。我们仔细想想,对于第n个灯泡,只有当次数是n的因子的之后,才能改变灯泡的状态,即n能被当前次数整除,比如当n为36时,它的因数有(1,36), (2,18), (3,12), (4,9), (6,6), 可以看到前四个括号里成对出现的因数各不相同,括号中前面的数改变了灯泡状态,后面的数又变回去了,等于灯泡的状态没有发生变化,只有最后那个(6,6),在次数6的时候改变了一次状态,没有对应其它的状态能将其变回去了,所以灯泡就一直是点亮状态的。所以所有平方数都有这么一个相等的因数对,即所有平方数的灯泡都将会是点亮的状态。

那么问题就简化为了求1到n之间完全平方数的个数,我们可以用force brute来比较从1开始的完全平方数和n的大小,参见代码如下:

1
2
3
4
5
6
7
8
class Solution {
public:
int bulbSwitch(int n) {
int res = 1;
while (res * res <= n) ++res;
return res - 1;
}
};

还有一种方法更简单,我们直接对n开方,在C++里的sqrt函数返回的是一个整型数,这个整型数的平方最接近于n,即为n包含的所有完全平方数的个数,参见代码如下:

1
2
3
4
5
6
class Solution {
public:
int bulbSwitch(int n) {
return sqrt(n);
}
};

Leetcode322. Coin Change

You are given coins of different denominations and a total amount of money amount. Write a function to compute the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.

Example 1:

1
2
3
Input: coins = [1, 2, 5], amount = 11
Output: 3
Explanation: 11 = 5 + 5 + 1

Example 2:

1
2
Input: coins = [2], amount = 3
Output: -1

Note:
You may assume that you have an infinite number of each kind of coin.

我们希望既避免重复地计算,又避免无意义的计算(没有答案的子问题)。
生成所有可能的金钱总量就可以避免无意义的计算。
dp[i]表示金钱i对应的最少硬币数。

  • 初始的金钱总量为0,硬币面值为 coins = [1, 2, 5]。
  • 只有一个硬币可以组成的金钱分别为[1, 2, 5],dp[1]=dp[2]=dp[5]=1
  • 在金钱为1的基础上继续生成[2, 3, 6],即dp[3]=dp[6]=2,而dp[2]=min(dp[2],dp[1]+1)=1
  • 在金钱为2的基础上生成[3, 4, 7],即dp[4]=dp[7]=dp[2]+1=2
  • 在金钱为3的基础上生成[4, 5, 8],即dp[8]=2
  • 依次更新,直到计算到以金钱amount为基础时,结束。

当以某个金钱为基础生成接下来的金钱时,这个金钱对应的最少硬币数已经得到。
通过这种递推的方式可以生成所有的小于amount的有解金钱总量,反证法证明之

  • 假设某个金钱m是有解的,但是并没有被上述的递推过程生成
  • m一定是由{ m-coins[i] | 0 <= i < coins.size() }中某个金钱生成的,这些金钱中一定有某几个(或一个)是有解的,但是也没有被递推过程生成,这样反向推理肯定可以到初始金钱数为0
  • 既然能反推到初始金钱数,那么m一定是有解的。

解题的思想有点类似有向图的宽度优先搜索找最短路径

  • 所有的小于amount的有解金钱总量对应于有向图中的结点
  • m < n ,结点m和n之前有边m -> n当且仅当 m + coins[i] = n
  • 每条边的权值为1,对应于每次硬币数加1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
sort(coins.begin(), coins.end());
vector<int> dp(amount+1,INT_MAX);
dp[0]=0;
for(int i=0;i<amount;i++){
if(dp[i]==INT_MAX)
continue;
for(auto& num: coins){
if(i+(long long)num>amount)
break;
dp[i+num] = min(dp[i]+1,dp[i+num]);
}
}
return dp[amount]==INT_MAX?-1:dp[amount];
}
};

我们维护一个一维动态数组 dp,其中 dp[i] 表示钱数为i时的最小硬币数的找零,注意由于数组是从0开始的,所以要多申请一位,数组大小为 amount+1,这样最终结果就可以保存在 dp[amount] 中了。初始化 dp[0] = 0,因为目标值若为0时,就不需要硬币了。其他值可以初始化是 amount+1,为啥呢?因为最小的硬币是1,所以 amount 最多需要 amount 个硬币,amount+1 也就相当于当前的最大值了,注意这里不能用整型最大值来初始化,因为在后面的状态转移方程有加1的操作,有可能会溢出,除非你先减个1,这样还不如直接用 amount+1 舒服呢。好,接下来就是要找状态转移方程了,没思路?不要紧!回归例子1,假设我取了一个值为5的硬币,那么由于目标值是 11,所以是不是假如我们知道 dp[6],那么就知道了组成 11 的 dp 值了?所以更新 dp[i] 的方法就是遍历每个硬币,如果遍历到的硬币值小于i值(比如不能用值为5的硬币去更新 dp[3])时,用 dp[i - coins[j]] + 1 来更新 dp[i],所以状态转移方程为:

dp[i] = min(dp[i], dp[i - coins[j]] + 1);
其中 coins[j] 为第j个硬币,而 i - coins[j] 为钱数i减去其中一个硬币的值,剩余的钱数在 dp 数组中找到值,然后加1和当前 dp 数组中的值做比较,取较小的那个更新 dp 数组。先来看迭代的写法如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
vector<int> dp(amount + 1, amount + 1);
dp[0] = 0;
for (int i = 1; i <= amount; ++i) {
for (int j = 0; j < coins.size(); ++j) {
if (coins[j] <= i) {
dp[i] = min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return (dp[amount] > amount) ? -1 : dp[amount];
}
};

Leetcode326. Power of Three

Given an integer, write a function to determine if it is a power of three.

Example 1:

1
2
Input: 27
Output: true

Example 2:

1
2
Input: 0
Output: false

Example 3:

1
2
Input: 9
Output: true

Example 4:

1
2
Input: 45
Output: false

这道题让我们判断一个数是不是3的次方数,3的次方数没有显著的特点,最直接的方法就是不停地除以3,看最后的余数是否为1,要注意考虑输入是负数和0的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
bool isPowerOfThree(int n) {
if(n == 0)
return false;
while(n != 1) {
if(n % 3 != 0)
return false;
n /= 3;
}
return true;
}
};
1
2
3
4
5
6
7
8
9
class Solution {
public:
bool isPowerOfThree(int n) {
while (n && n % 3 == 0) {
n /= 3;
}
return n == 1;
}
};

题目中的Follow up让我们不用循环,那么有一个投机取巧的方法,由于输入是int,正数范围是0-231,在此范围中允许的最大的3的次方数为319=1162261467,那么我们只要看这个数能否被n整除即可,参见代码如下:

1
2
3
4
5
6
class Solution {
public:
bool isPowerOfThree(int n) {
return (n > 0 && 1162261467 % n == 0);
}
};

最后还有一种巧妙的方法,利用对数的换底公式来做,高中学过的换底公式为logab = logcb / logca,那么如果n是3的倍数,则log3n一定是整数,我们利用换底公式可以写为log3n = log10n / log103,注意这里一定要用10为底数,不能用自然数或者2为底数,否则当n=243时会出错,原因请看这个帖子。现在问题就变成了判断log10n / log103是否为整数,在c++中判断数字a是否为整数,我们可以用 a - int(a) == 0 来判断,参见代码如下:

1
2
3
4
5
6
class Solution {
public:
bool isPowerOfThree(int n) {
return (n > 0 && int(log10(n) / log10(3)) - log10(n) / log10(3) == 0);
}
};

Leetcode327. Count of Range Sum

Given an integer array nums, return the number of range sums that lie in [lower, upper] inclusive.
Range sum S(i, j) is defined as the sum of the elements in nums between indices i and j (i ≤ j), inclusive.

Note: A naive algorithm of O ( n 2) is trivial. You MUST do better than that.

Example:

1
2
3
Input: _nums_ = [-2,5,-1], _lower_ = -2, _upper_ = 2,
Output: 3
Explanation: The three ranges are : [0,0], [2,2], [0,2] and their respective sums are: -2, -1, 2.

这道题给了我们一个数组,又给了一个下限和一个上限,让求有多少个不同的区间使得每个区间的和在给定的上下限之间。类似的区间和的问题一定是要计算累积和数组 sums 的,其中 sum[i] = nums[0] + nums[1] + … + nums[i],对于某个i来说,只有那些满足 lower <= sum[i] - sum[j] <= upper 的j能形成一个区间 [j, i] 满足题意,目标就是来找到有多少个这样的 j (0 =< j < i) 满足 sum[i] - upper =< sum[j] <= sum[i] - lower,可以用 C++ 中由红黑树实现的 multiset 数据结构可以对其中数据排序,然后用 upperbound 和 lowerbound 来找临界值。lower_bound 是找数组中第一个不小于给定值的数(包括等于情况),而 upper_bound 是找数组中第一个大于给定值的数,那么两者相减,就是j的个数,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int countRangeSum(vector<int>& nums, int lower, int upper) {
int res = 0;
long long sum = 0;
multiset<long long> sums;
sums.insert(0);
for (int i = 0; i < nums.size(); ++i) {
sum += nums[i];
res += distance(sums.lower_bound(sum - upper), sums.upper_bound(sum - lower));
sums.insert(sum);
}
return res;
}
};

我们再来看一种方法,这种方法的思路和前一种一样,只是没有 STL 的 multiset 和 lower_bound 和 upper_bound 函数,而是使用了 Merge Sort 来解,在混合的过程中,已经给左半边 [start, mid) 和右半边 [mid, end) 排序了。当遍历左半边,对于每个i,需要在右半边找出k和j,使其满足:

j是第一个满足 sums[j] - sums[i] > upper 的下标

k是第一个满足 sums[k] - sums[i] >= lower 的下标

那么在 [lower, upper] 之间的区间的个数是 j - k,同时也需要另一个下标t,用来拷贝所有满足 sums[t] < sums[i] 到一个寄存器 Cache 中以完成混合排序的过程,这个步骤是混合排序的精髓所在,实际上这个寄存器的作用就是将 [start, end) 范围内的数字排好序先存到寄存器中,然后再覆盖原数组对应的位置即可,(注意这里 sums 可能会整型溢出,使用长整型 long 代替),参见代码如下:

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
class Solution {
public:
int countRangeSum(vector<int>& nums, int lower, int upper) {
vector<long> sums(nums.size() + 1, 0);
for (int i = 0; i < nums.size(); ++i) {
sums[i + 1] = sums[i] + nums[i];
}
return countAndMergeSort(sums, 0, sums.size(), lower, upper);
}
int countAndMergeSort(vector<long>& sums, int start, int end, int lower, int upper) {
if (end - start <= 1) return 0;
int mid = start + (end - start) / 2;
int cnt = countAndMergeSort(sums, start, mid, lower, upper) + countAndMergeSort(sums, mid, end, lower, upper);
int j = mid, k = mid, t = mid;
vector<int> cache(end - start, 0);
for (int i = start, r = 0; i < mid; ++i, ++r) {
while (k < end && sums[k] - sums[i] < lower) ++k;
while (j < end && sums[j] - sums[i] <= upper) ++j;
while (t < end && sums[t] < sums[i]) cache[r++] = sums[t++];
cache[r] = sums[i];
cnt += j - k;
}
copy(cache.begin(), cache.begin() + t - start, sums.begin() + start);
return cnt;
}
};

Leetcode328. Odd Even Linked List

Given the head of a singly linked list, group all the nodes with odd indices together followed by the nodes with even indices, and return the reordered list.

The first node is considered odd, and the second node is even, and so on.

Note that the relative order inside both the even and odd groups should remain as it was in the input.

You must solve the problem in O(1) extra space complexity and O(n) time complexity.

Example 1:

1
2
Given 1->2->3->4->5->NULL,
return 1->3->5->2->4->NULL.

这道题给了我们一个链表,让我们分开奇偶节点,所有奇节点在前,偶节点在后。我们可以使用两个指针来做,pre指向奇节点,cur指向偶节点,然后把偶节点cur后面的那个奇节点提前到pre的后面,然后pre和cur各自前进一步,此时cur又指向偶节点,pre指向当前奇节点的末尾,以此类推直至把所有的偶节点都提前了即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
ListNode* oddEvenList(ListNode* head) {
if (head == NULL || head->next == NULL)
return head;
ListNode *odd = head, *even = head->next, *p = even;
while(odd->next && even->next) {
odd->next = even->next;
even->next = even->next->next;
odd = odd->next;
even = even->next;
}
odd->next = p;
return head;
}
};

Leetcode329. Longest Increasing Path in a Matrix

Given an integer matrix, find the length of the longest increasing path.

From each cell, you can either move to four directions: left, right, up or down. You may NOT move diagonally or move outside of the boundary (i.e. wrap-around is not allowed).

Example 1:

1
2
3
4
5
6
7
8
Input: nums = 
[
[9,9,4],
[6,6,8],
[2,1,1]
]
Output: 4
Explanation: The longest increasing path is [1, 2, 6, 9].

Example 2:

1
2
3
4
5
6
7
8
Input: nums = 
[
[3,4,5],
[3,2,6],
[2,2,1]
]
Output: 4
Explanation: The longest increasing path is [3, 4, 5, 6]. Moving diagonally is not allowed.

这道题给我们一个二维数组,让我们求矩阵中最长的递增路径,规定我们只能上下左右行走,不能走斜线或者是超过了边界。那么这道题的解法要用递归和DP来解,用DP的原因是为了提高效率,避免重复运算。我们需要维护一个二维动态数组dp,其中dp[i][j]表示数组中以(i,j)为起点的最长递增路径的长度,初始将dp数组都赋为0,当我们用递归调用时,遇到某个位置(x, y), 如果dp[x][y]不为0的话,我们直接返回dp[x][y]即可,不需要重复计算。我们需要以数组中每个位置都为起点调用递归来做,比较找出最大值。在以一个位置为起点用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
class Solution {
public:
int dirs[4][2] = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}};
int** dp;
int longestIncreasingPath(vector<vector<int>>& matrix) {
if (matrix.empty() || matrix[0].empty())
return 0;
int res = 1, m = matrix.size(), n = matrix[0].size();
dp = (int**)malloc(sizeof(int*)*m);
for(int i=0;i<m;i++){
dp[i] = (int*)malloc(sizeof(int)*n);
memset(dp[i],0,n*sizeof(int));
}

for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
res = max(res, dfs(matrix, i, j));
}
}
return res;
}

int dfs(vector<vector<int>>& matrix,int i,int j){
if (dp[i][j])
return dp[i][j];
int mx = 1, m = matrix.size(), n = matrix[0].size();
for(int ii=0;ii<4;ii++){
int x = i + dirs[ii][0], y = j + dirs[ii][1];
if (x < 0 || x >= m || y < 0 || y >= n || matrix[x][y] <= matrix[i][j])
continue;
int len = 1 + dfs(matrix, x, y);
mx = max(mx, len);
}
dp[i][j]=mx;
return mx;
}
};

Leetcode331. Verify Preorder Serialization of a Binary Tree

One way to serialize a binary tree is to use preorder traversal. When we encounter a non-null node, we record the node’s value. If it is a null node, we record using a sentinel value such as #.

For example, the above binary tree can be serialized to the string “9,3,4,#,#,1,#,#,2,#,6,#,#“, where # represents a null node.

Given a string of comma-separated values preorder, return true if it is a correct preorder traversal serialization of a binary tree.

It is guaranteed that each comma-separated value in the string must be either an integer or a character # representing null pointer.

You may assume that the input format is always valid.

For example, it could never contain two consecutive commas, such as “1,,3”.
Note: You are not allowed to reconstruct the tree.

Example 1:

1
2
Input: preorder = "9,3,4,#,#,1,#,#,2,#,6,#,#"
Output: true

Example 2:

1
2
Input: preorder = "1,#"
Output: false

Example 3:

1
2
Input: preorder = "9,#,#,1"
Output: false

通过二叉树的性质,所有二叉树中Null指针的个数=节点个数+1。因为一棵树要增加一个节点,必然是在null指针的地方增加一个叶子结点,也就是毁掉一个null指针的同时带来两个null指针,意味着每增加一个节点,增加一个null指针。然而最开始一颗空树本来就有一个null指针,因此二叉树中null指针的个数等于节点数+1。从头开始扫描这个字串,如果途中#的个数超了,或者字符串扫描完不满足等式则返回false。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
bool isValidSerialization(string preorder) {
int len = preorder.length();
int i = 0, cnt = 0;
while(i < len-1) {
if (preorder[i] == '#') {
if (cnt == 0)
return false;
cnt --;
i ++;
}
else {
for(; i < len && preorder[i] != ','; i ++) ;
cnt ++;
}
i ++;
}
return cnt == 0 && preorder[len-1] == '#';
}
};

Leetcode334. Increasing Triplet Subsequence

Given an integer array nums, return true if there exists a triple of indices (i, j, k) such that i < j < k and nums[i] < nums[j] < nums[k]. If no such indices exists, return false.

Example 1:

1
2
3
Input: nums = [1,2,3,4,5]
Output: true
Explanation: Any triplet where i < j < k is valid.

Example 2:

1
2
3
Input: nums = [5,4,3,2,1]
Output: false
Explanation: No triplet exists.

Example 3:

1
2
3
Input: nums = [2,1,5,0,4,6]
Output: true
Explanation: The triplet (3, 4, 5) is valid because nums[3] == 0 < nums[4] == 4 < nums[5] == 6.

dp数组,分别记录当前位置的最大和最小数。时间复杂度O(n)。空间复杂度O(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
class Solution {
public boolean increasingTriplet(int[] nums) {
if(nums.length<3)
return false;
int mindp[]=new int[nums.length];
mindp[0]=nums[0];
int tempmin=nums[0];
for(int i=1;i<nums.length;i++){
tempmin=Math.min(tempmin,nums[i-1]);
mindp[i]=tempmin;
}
int maxdp[]=new int[nums.length];
maxdp[nums.length-1]=nums[nums.length-1];
int tempmax=nums[nums.length-1];
for(int i=nums.length-2;i>=0;i--){
tempmax=Math.max(tempmax,nums[i+1]);
maxdp[i]=tempmax;
}
for(int i=1;i<nums.length-1;i++){
if(nums[i]>mindp[i] && nums[i]<maxdp[i])
return true;
}
return false;
}
}

找出两个数,分别记录为次小和最小,当一个数 比这两个数都大时,返回true.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public boolean increasingTriplet(int[] nums) {
int One=Integer.MAX_VALUE,Tow=Integer.MAX_VALUE;
for(int i=0;i< nums.length;i++){
if(nums[i]<=One)
One=nums[i];
else if(nums[i]<=Tow)
Tow=nums[i];
else
return true;
}
return false;
}
}

Leetcode337. House Robber III

The thief has found himself a new place for his thievery again. There is only one entrance to this area, called root.

Besides the root, each house has one and only one parent house. After a tour, the smart thief realized that all houses in this place form a binary tree. It will automatically contact the police if two directly-linked houses were broken into on the same night.

Given the root of the binary tree, return the maximum amount of money the thief can rob without alerting the police.

Example 1:

1
2
3
Input: root = [3,2,3,null,3,null,1]
Output: 7
Explanation: Maximum amount of money the thief can rob = 3 + 3 + 1 = 7.

Example 2:

1
2
3
Input: root = [3,4,5,1,3,null,1]
Output: 9
Explanation: Maximum amount of money the thief can rob = 4 + 5 = 9.

这道题是之前那两道 House Robber II 和 House Robber 的拓展,这个小偷又偷出新花样了,沿着二叉树开始偷,碉堡了,题目中给的例子看似好像是要每隔一个偷一次,但实际上不一定只隔一个,比如如下这个例子:

1
2
3
4
5
6
7
      4
/
1
/
2
/
3

如果隔一个偷,那么是 4+2=6,其实最优解应为 4+3=7,隔了两个,所以说纯粹是怎么多怎么来,那么这种问题是很典型的递归问题,可以利用回溯法来做,因为当前的计算需要依赖之前的结果,那么对于某一个节点,如果其左子节点存在,通过递归调用函数,算出不包含左子节点返回的值,同理,如果右子节点存在,算出不包含右子节点返回的值,那么此节点的最大值可能有两种情况,一种是该节点值加上不包含左子节点和右子节点的返回值之和,另一种是左右子节点返回值之和不包含当期节点值,取两者的较大值返回即可,但是这种方法无法通过 OJ,超时了,所以必须优化这种方法,这种方法重复计算了很多地方,比如要完成一个节点的计算,就得一直找左右子节点计算,可以把已经算过的节点用 HashMap 保存起来,以后递归调用的时候,现在 HashMap 里找,如果存在直接返回,如果不存在,等计算出来后,保存到 HashMap 中再返回,这样方便以后再调用,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
int rob(TreeNode* root) {
unordered_map<TreeNode*, int> m;
return dfs(root, m);
}
int dfs(TreeNode *root, unordered_map<TreeNode*, int> &m) {
if (!root) return 0;
if (m.count(root)) return m[root];
int val = 0;
if (root->left) {
val += dfs(root->left->left, m) + dfs(root->left->right, m);
}
if (root->right) {
val += dfs(root->right->left, m) + dfs(root->right->right, m);
}
val = max(val + root->val, dfs(root->left, m) + dfs(root->right, m));
m[root] = val;
return val;
}
};

下面再来看一种方法,这种方法的递归函数返回一个大小为2的一维数组 res,其中 res[0] 表示不包含当前节点值的最大值,res[1] 表示包含当前值的最大值,那么在遍历某个节点时,首先对其左右子节点调用递归函数,分别得到包含与不包含左子节点值的最大值,和包含于不包含右子节点值的最大值,则当前节点的 res[0] 就是左子节点两种情况的较大值加上右子节点两种情况的较大值,res[1] 就是不包含左子节点值的最大值加上不包含右子节点值的最大值,和当前节点值之和,返回即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int rob(TreeNode* root) {
vector<int> res = dfs(root);
return max(res[0], res[1]);
}
vector<int> dfs(TreeNode *root) {
if (!root) return vector<int>(2, 0);
vector<int> left = dfs(root->left);
vector<int> right = dfs(root->right);
vector<int> res(2, 0);
res[0] = max(left[0], left[1]) + max(right[0], right[1]);
res[1] = left[0] + right[0] + root->val;
return res;
}
};

下面这种解法思路和解法二有些类似。这里的 helper 函数返回当前结点为根结点的最大 rob 的钱数,里面的两个参数l和r表示分别从左子结点和右子结点开始 rob,分别能获得的最大钱数。在递归函数里面,如果当前结点不存在,直接返回0。否则对左右子结点分别调用递归函数,得到l和r。另外还得到四个变量,ll和lr表示左子结点的左右子结点的最大 rob 钱数,rl 和 rr 表示右子结点的最大 rob 钱数。那么最后返回的值其实是两部分的值比较,其中一部分的值是当前的结点值加上 ll, lr, rl, 和 rr 这四个值,这不难理解,因为抢了当前的房屋,则左右两个子结点就不能再抢了,但是再下一层的四个子结点都是可以抢的;另一部分是不抢当前房屋,而是抢其左右两个子结点,即 l+r 的值,返回两个部分的值中的较大值即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
int rob(TreeNode* root) {
int l = 0, r = 0;
return helper(root, l, r);
}
int helper(TreeNode* node, int& l, int& r) {
if (!node) return 0;
int ll = 0, lr = 0, rl = 0, rr = 0;
l = helper(node->left, ll, lr);
r = helper(node->right, rl, rr);
return max(node->val + ll + lr + rl + rr, l + r);
}
};

Leetcode338. Counting Bits

Given a non negative integer number num. For every numbers i in the range 0 ≤ i ≤ num calculate the number of 1’s in their binary representation and return them as an array.

Example 1:

1
2
Input: 2
Output: [0,1,1]

Example 2:

1
2
Input: 5
Output: [0,1,1,2,1,2]

Follow up: It is very easy to come up with a solution with run time O(n*sizeof(integer)). But can you do it in linear time O(n) /possibly in a single pass? Space complexity should be O(n). Can you do it like a boss? Do it without using any builtin function like __builtin_popcount in c++ or in any other language.

从低位入手。‘1’的个数等于除了最低位之外的‘1’的个数加上最低位‘1’的个数,即ret[n] = ret[n>>1] + n%2,具体代码:

1
2
3
4
5
6
7
8
9
class Solution {
public:
vector<int> countBits(int num) {
vector<int> ret(num+1, 0);
for(int i=1; i<=num; ++i)
ret[i] = ret[i>>1] + i%2;
return ret;
}
};

Leetcode 339. Nested List Weight Sum

Given a nested list of integers, return the sum of all integers in the list weighted by their depth. Each element is either an integer, or a list – whose elements may also be integers or other lists.

Example 1:

1
2
3
Input: [[1,1],2,[1,1]]
Output: 10
Explanation: Four 1's at depth 2, one 2 at depth 1.

Example 2:

1
2
3
Input: [1,[4,[6]]]
Output: 27
Explanation: One 1 at depth 1, one 4 at depth 2, and one 6 at depth 3; 1 + 4*2 + 6*3 = 27.

这道题定义了一种嵌套链表的结构,链表可以无限往里嵌套,规定每嵌套一层,深度加1,让我们求权重之和,就是每个数字乘以其权重,再求总和。那么我们考虑,由于嵌套层数可以很大,所以我们用深度优先搜索DFS会很简单,每次遇到嵌套的,递归调用函数,一层一层往里算就可以了,我最先想的方法是遍历给的嵌套链表的数组,对于每个嵌套链表的对象,调用getSum函数,并赋深度值1,累加起来返回。在getSum函数中,首先判断其是否为整数,如果是,则返回当前深度乘以整数,如果不是,那么我们再遍历嵌套数组,对每个嵌套链表再调用递归函数,将返回值累加起来返回即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int depthSum(vector<NestedInteger>& nestedList) {
int res = 0;
for (auto a : nestedList) {
res += getSum(a, 1);
}
return res;
}
int getSum(NestedInteger ni, int level) {
int res = 0;
if (ni.isInteger()) return level * ni.getInteger();
for (auto a : ni.getList()) {
res += getSum(a, level + 1);
}
return res;
}
};

但其实上面的方法可以优化,我们可以把给的那个嵌套链表的一维数组直接当做一个嵌套链表的对象,然后调用递归函数,递归函数的处理方法跟上面一样,只不过用了个三元处理使其看起来更加简洁了一些:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int depthSum(vector<NestedInteger>& nestedList) {
return helper(nestedList, 1);
}
int helper(vector<NestedInteger>& nl, int depth) {
int res = 0;
for (auto a : nl) {
res += a.isInteger() ? a.getInteger() * depth : helper(a.getList(), depth + 1);
}
return res;
}
};

Leetcode342. Power of Four

Given an integer (signed 32 bits), write a function to check whether it is a power of 4.

Example 1:

1
2
Input: 16
Output: true

Example 2:

1
2
Input: 5
Output: false

这道题让我们判断一个数是否为4的次方数,那么最直接的方法就是不停的除以4,看最终结果是否为1,参见代码如下:

1
2
3
4
5
6
7
8
9
class Solution {
public:
bool isPowerOfFour(int num) {
while (num && (num % 4 == 0)) {
num /= 4;
}
return num == 1;
}
};

还有一种方法是跟 Power of Three 中的解法三一样,使用换底公式来做

1
2
3
4
5
6
class Solution {
public:
bool isPowerOfFour(int num) {
return num > 0 && int(log10(num) / log10(4)) - log10(num) / log10(4) == 0;
}
};

下面这种方法是网上比较流行的一种解法,思路很巧妙,首先根据 Power of Two 中的解法,我们知道 num & (num - 1) 可以用来判断一个数是否为2的次方数,更进一步说,就是二进制表示下,只有最高位是1,那么由于是2的次方数,不一定是4的次方数,比如8,所以我们还要其他的限定条件,我们仔细观察可以发现,4的次方数的最高位的1都是奇数位,那么我们只需与上一个数 (0x55555555) <==> 1010101010101010101010101010101,如果得到的数还是其本身,则可以肯定其为4的次方数:

1
2
3
4
5
6
class Solution {
public:
bool isPowerOfFour(int num) {
return num > 0 && !(num & (num - 1)) && (num & 0x55555555) == num;
}
};

Leetcode343. Integer Break

Given an integer n, break it into the sum of k positive integers, where k >= 2, and maximize the product of those integers.

Return the maximum product you can get.

Example 1:

1
2
3
Input: n = 2
Output: 1
Explanation: 2 = 1 + 1, 1 × 1 = 1.

Example 2:

1
2
3
Input: n = 10
Output: 36
Explanation: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36.

这道题给了我们一个正整数n,让拆分成至少两个正整数之和,使其乘积最大。当前的拆分方法需要用到之前的拆分值,这种重现关系就很适合动态规划 Dynamic Programming 来做,我们使用一个一维数组 dp,其中 dp[i] 表示数字i拆分为至少两个正整数之和的最大乘积,数组大小为 n+1,值均初始化为1,因为正整数的乘积不会小于1。可以从3开始遍历,因为n是从2开始的,而2只能拆分为两个1,乘积还是1。i从3遍历到n,对于每个i,需要遍历所有小于i的数字,因为这些都是潜在的拆分情况,对于任意小于i的数字j,首先计算拆分为两个数字的乘积,即j乘以 i-j,然后是拆分为多个数字的情况,这里就要用到 dp[i-j] 了,这个值表示数字 i-j 任意拆分可得到的最大乘积,再乘以j就是数字i可拆分得到的乘积,取二者的较大值来更新 dp[i],最后返回 dp[n] 即可,

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
int integerBreak(int n) {
vector<int> dp(n+1, 1);
for (int i = 3; i <= n; i ++) {
for (int j = 1; j < i; j ++)
dp[i] = max(dp[i], max(j*(i-j), dp[i-j] *j));
}
return dp[n];
}
};

题目提示中让用 O(n) 的时间复杂度来解题,而且告诉我们找7到 10 之间的规律,那么我们一点一点的来分析:

正整数从1开始,但是1不能拆分成两个正整数之和,所以不能当输入。

那么:

  • 2只能拆成 1+1,所以乘积也为1。
  • 数字3可以拆分成 2+1 或 1+1+1,显然第一种拆分方法乘积大为2。
  • 数字4拆成 2+2,乘积最大,为4。
  • 数字5拆成 3+2,乘积最大,为6。
  • 数字6拆成 3+3,乘积最大,为9。
  • 数字7拆为 3+4,乘积最大,为 12。
  • 数字8拆为 3+3+2,乘积最大,为 18。
  • 数字9拆为 3+3+3,乘积最大,为 27。
  • 数字10拆为 3+3+4,乘积最大,为 36。

….

那么通过观察上面的规律,我们可以看出从5开始,数字都需要先拆出所有的3,一直拆到剩下一个数为2或者4,因为剩4就不用再拆了,拆成两个2和不拆没有意义,而且4不能拆出一个3剩一个1,这样会比拆成 2+2 的乘积小。这样我们就可以写代码了,先预处理n为2和3的情况,然后先将结果 res 初始化为1,然后当n大于4开始循环,结果 res 自乘3,n自减3,根据之前的分析,当跳出循环时,n只能是2或者4,再乘以 res 返回即可:

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int integerBreak(int n) {
if (n == 2 || n == 3) return n - 1;
int res = 1;
while (n > 4) {
res *= 3;
n -= 3;
}
return res * n;
}
};

直接分别算出能拆出3的个数和最后剩下的余数2或者4,然后直接相乘得到结果,参见代码如下:

1
2
3
4
5
6
7
8
9
class Solution {
public:
int integerBreak(int n) {
if (n == 2 || n == 3) return n - 1;
if (n == 4) return 4;
n -= 5;
return (int)pow(3, (n / 3 + 1)) * (n % 3 + 2);
}
};

Leetcode344. Reverse String

Write a function that reverses a string. The input string is given as an array of characters char[].

Do not allocate extra space for another array, you must do this by modifying the input array in-place with O(1) extra memory. You may assume all the characters consist of printable ascii characters.

Example 1:

1
2
Input: ["h","e","l","l","o"]
Output: ["o","l","l","e","h"]

Example 2:

1
2
Input: ["H","a","n","n","a","h"]
Output: ["h","a","n","n","a","H"]

我的解法:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
void reverseString(vector<char>& s) {
int l = 0, r = s.size() - 1;
while(l < r) {
int temp = s[l];
s[l] = s[r];
s[r] = temp;
l ++;
r --;
}
}
};

Leetcode345. Reverse Vowels of a String

Write a function that takes a string as input and reverse only the vowels of a string.

Example 1:

1
2
Input: "hello"
Output: "holle"

Example 2:

1
2
Input: "leetcode"
Output: "leotcede"

用首尾两个指针,当前后两个字符都是元音字母时交换,并且首指针向后移动,尾指针向前移动,如果只有首指针指的是元音字母而尾指针指的是非元音字母,则尾指针向前移动,否则首指针向后移动。有一些特殊样例,比如“.,”,导致用while会错。

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
class Solution {
public:

bool isv(char c) {
return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' ||
c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U';
}

string reverseVowels(string s) {
int l = 0, r = s.length() - 1;
char temp;
while(l < r) {
if(isv(s[l]) && isv(s[r])) {
temp = s[l];
s[l] = s[r];
s[r] = temp;
l ++;
r --;
}
if(!isv(s[l])) l ++;
if(!isv(s[r])) r --;
}
return s;
}
};

LeetCode346. Moving Average from Data Stream

Given a stream of integers and a window size, calculate the moving average of all integers in the sliding window.

Example:

1
2
3
4
5
MovingAverage m = new MovingAverage(3);
m.next(1) = 1
m.next(10) = (1 + 10) / 2
m.next(3) = (1 + 10 + 3) / 3
m.next(5) = (10 + 3 + 5) / 3

这道题定义了一个MovingAverage类,里面可以存固定个数字,然后我们每次读入一个数字,如果加上这个数字后总个数大于限制的个数,那么我们移除最早进入的数字,然后返回更新后的平均数,这种先进先出的特性最适合使用队列queue来做,而且我们还需要一个double型的变量sum来记录当前所有数字之和,这样有新数字进入后,如果没有超出限制个数,则sum加上这个数字,如果超出了,那么sum先减去最早的数字,再加上这个数字,然后返回sum除以queue的个数即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class MovingAverage {
public:
MovingAverage(int size) {
this->size = size;
sum = 0;
}

double next(int val) {
if (q.size() >= size) {
sum -= q.front(); q.pop();
}
q.push(val);
sum += val;
return sum / q.size();
}

private:
queue<int> q;
int size;
double sum;
};

Leetcode347. Top K Frequent Elements

Given an integer array nums and an integer k, return the k most frequent elements. You may return the answer in any order.

Example 1:

1
2
Input: nums = [1,1,1,2,2,3], k = 2
Output: [1,2]

Example 2:

1
2
Input: nums = [1], k = 1
Output: [1]

这道题给了我们一个数组,让统计前k个高频的数字,那么对于这类的统计数字的问题,首先应该考虑用 HashMap 来做,建立数字和其出现次数的映射,然后再按照出现次数进行排序。可以用堆排序来做,使用一个最大堆来按照映射次数从大到小排列,在 C++ 中使用 priority_queue 来实现,默认是最大堆。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> m;
priority_queue<pair<int, int> > q;
for (int i : nums)
m[i] ++;
for(pair<int, int> p : m)
q.push({p.second, p.first});

vector<int> res;
for(int i = 0; i < k; i ++) {
res.push_back(q.top().second);
q.pop();
}
return res;
}
};

Leetcode349. Intersection of Two Arrays

Given two arrays, write a function to compute their intersection.

Example 1:

1
2
Input: nums1 = [1,2,2,1], nums2 = [2,2]
Output: [2]

Example 2:

1
2
Input: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
Output: [9,4]

Note:

  • Each element in the result must be unique.
  • The result can be in any order.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
int l1 = nums1.size(), l2 = nums2.size(), mark = 0;
vector<int> res;
sort(nums1.begin(), nums1.end());
sort(nums2.begin(), nums2.end());
for(int i = 0, j = 0; i < l1 && j < l2;) {
if(nums1[i] < nums2[j])
i ++;
else if(nums1[i] > nums2[j])
j ++;
else {
if(res.size() == 0 || mark != nums1[i]) {
res.push_back(nums1[i]);
mark = nums1[i];
}
i ++;
j ++;
}
}
return res;
}
};

Leetcode350. Intersection of Two Arrays II

Given two arrays, write a function to compute their intersection.

Example 1:

1
2
Input: nums1 = [1,2,2,1], nums2 = [2,2]
Output: [2,2]

Example 2:

1
2
Input: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
Output: [4,9]

Note:

  • Each element in the result should appear as many times as it shows in both arrays.
  • The result can be in any order.

跟上一个题一样,只是可以加上重复的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
int l1 = nums1.size(), l2 = nums2.size();
vector<int> res;
sort(nums1.begin(), nums1.end());
sort(nums2.begin(), nums2.end());
for(int i = 0, j = 0; i < l1 && j < l2;) {
if(nums1[i] < nums2[j])
i ++;
else if(nums1[i] > nums2[j])
j ++;
else {
res.push_back(nums1[i]);
i ++;
j ++;
}
}
return res;
}
};

Leetcode354. Russian Doll Envelopes

You have a number of envelopes with widths and heights given as a pair of integers (w, h). One envelope can fit into another if and only if both the width and height of one envelope is greater than the width and height of the other envelope.

What is the maximum number of envelopes can you Russian doll? (put one inside other)

Example:

1
Given envelopes = [[5,4],[6,4],[6,7],[2,3]], the maximum number of envelopes you can Russian doll is 3 ([2,3] => [5,4] => [6,7]).

这道题给了我们一堆大小不一的信封,让我们像套俄罗斯娃娃那样把这些信封都给套起来,这道题实际上是之前那道Longest Increasing Subsequence的具体应用,而且难度增加了,从一维变成了两维,但是万变不离其宗,解法还是一样的,首先来看DP的解法,这是一种brute force的解法,首先要给所有的信封按从小到大排序,首先根据宽度从小到大排,如果宽度相同,那么高度小的再前面,这是STL里面sort的默认排法,所以我们不用写其他的comparator,直接排就可以了,然后我们开始遍历,对于每一个信封,我们都遍历其前面所有的信封,如果当前信封的长和宽都比前面那个信封的大,那么我们更新dp数组,通过dp[i] = max(dp[i], dp[j] + 1)。然后我们每遍历完一个信封,都更新一下结果res,参见代码如下;

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int maxEnvelopes(vector<pair<int, int>>& envelopes) {
int res = 0, n = envelopes.size();
vector<int> dp(n, 1);
sort(envelopes.begin(), envelopes.end());
for (int i = 0; i < n; ++i) {
for (int j = 0; j < i; ++j) {
if (envelopes[i].first > envelopes[j].first && envelopes[i].second > envelopes[j].second) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
res = max(res, dp[i]);
}
return res;
}
};

Leetcode355. Design Twitter

Design a simplified version of Twitter where users can post tweets, follow/unfollow another user and is able to see the 10 most recent tweets in the user’s news feed. Your design should support the following methods:

  • postTweet(userId, tweetId) : Compose a new tweet.
  • getNewsFeed(userId) : Retrieve the 10 most recent tweet ids in the user’s news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent.
  • follow(followerId, followeeId) : Follower follows a followee.
  • unfollow(followerId, followeeId) : Follower unfollows a followee.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Twitter twitter = new Twitter();

// User 1 posts a new tweet (id = 5).
twitter.postTweet(1, 5);

// User 1's news feed should return a list with 1 tweet id -> [5].
twitter.getNewsFeed(1);

// User 1 follows user 2.
twitter.follow(1, 2);

// User 2 posts a new tweet (id = 6).
twitter.postTweet(2, 6);

// User 1's news feed should return a list with 2 tweet ids -> [6, 5].
// Tweet id 6 should precede tweet id 5 because it is posted after tweet id 5.
twitter.getNewsFeed(1);

// User 1 unfollows user 2.
twitter.unfollow(1, 2);

// User 1's news feed should return a list with 1 tweet id -> [5],
// since user 1 is no longer following user 2.
twitter.getNewsFeed(1);

这道题让我们设计个简单的推特,具有发布消息,获得新鲜事,添加关注和取消关注等功能。我们需要用两个哈希表来做,第一个是建立用户和其所有好友之间的映射,另一个是建立用户和其所有消息之间的映射。由于获得新鲜事是需要按时间顺序排列的,那么我们可以用一个整型变量cnt来模拟时间点,每发一个消息,cnt自增1,那么我们就知道cnt大的是最近发的。那么我们在建立用户和其所有消息之间的映射时,还需要建立每个消息和其时间点cnt之间的映射。这道题的主要难点在于实现getNewsFeed()函数,这个函数获取自己和好友的最近10条消息,我们的做法是用户也添加到自己的好友列表中,然后遍历该用户的所有好友,遍历每个好友的所有消息,维护一个大小为10的哈希表,如果新遍历到的消息比哈希表中最早的消息要晚,那么将这个消息加入,然后删除掉最早的那个消息,这样我们就可以找出最近10条消息了,参见代码如下:

解法一:

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
class Twitter {
public:
Twitter() {
time = 0;
}

void postTweet(int userId, int tweetId) {
follow(userId, userId);
tweets[userId].insert({time++, tweetId});
}

vector<int> getNewsFeed(int userId) {
vector<int> res;
map<int, int> top10;
for (auto id : friends[userId]) {
for (auto a : tweets[id]) {
top10.insert({a.first, a.second});
if (top10.size() > 10) top10.erase(top10.begin());
}
}
for (auto a : top10) {
res.insert(res.begin(), a.second);
}
return res;
}

void follow(int followerId, int followeeId) {
friends[followerId].insert(followeeId);
}

void unfollow(int followerId, int followeeId) {
if (followerId != followeeId) {
friends[followerId].erase(followeeId);
}
}

private:
int time;
unordered_map<int, unordered_set<int>> friends;
unordered_map<int, map<int, int>> tweets;
};

Leetcode357. Count Numbers with Unique Digits

Given an integer n, return the count of all numbers with unique digits, x, where 0 <= x < 10n.

Example 1:

1
2
3
Input: n = 2
Output: 91
Explanation: The answer should be the total numbers in the range of 0 ≤ x < 100, excluding 11,22,33,44,55,66,77,88,99

Example 2:

1
2
Input: n = 0
Output: 1

题目要求: 找出0到10的n次方中所有unique的数(既该数的所有位数无重复数组),如123为为unique,122不为unique,因为后两位2重复出现。

其实这个题的思路还是蛮简单的,回想下最简单的排列组合的知识。如找出所有十位unique digits number, 那么我们在十位可取1-9这九个数字,在个位能取除十位的数字外剩下的数字,由于个位可取0,所以个位还是可以取9个数字,则十位数的unique number为9*9 = 81。同理, 所有百位数unique digits number为9*9*8=648,以此类推。

由于此题目要求返回0到10的n次方中所有的unique number个数,因此我们可以分别求出个,十,百…每个位数上分别对应的unique number个数,然后加起来就好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int countNumbersWithUniqueDigits(int n) {
if (n == 0)
return 1;
int res = 10;
for (int i = 2; i <= n; i ++) {
int idx = 9;
int mul = 9;
int count = i;
while(count > 1) {
mul *= idx;
idx --;
count --;
}
res += mul;
}
return res;
}
};

Leetcode359. Logger Rate Limiter

Design a logger system that receive stream of messages along with its timestamps, each message should be printed if and only if it is not printed in the last 10 seconds.

Given a message and a timestamp (in seconds granularity), return true if the message should be printed in the given timestamp, otherwise returns false.

It is possible that several messages arrive roughly at the same time.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Logger logger = new Logger();

// logging string "foo" at timestamp 1
logger.shouldPrintMessage(1, "foo"); returns true;

// logging string "bar" at timestamp 2
logger.shouldPrintMessage(2,"bar"); returns true;

// logging string "foo" at timestamp 3
logger.shouldPrintMessage(3,"foo"); returns false;

// logging string "bar" at timestamp 8
logger.shouldPrintMessage(8,"bar"); returns false;

// logging string "foo" at timestamp 10
logger.shouldPrintMessage(10,"foo"); returns false;

// logging string "foo" at timestamp 11
logger.shouldPrintMessage(11,"foo"); returns true;

这道题让我们设计一个记录系统每次接受信息并保存时间戳,然后让我们打印出该消息,前提是最近10秒内没有打印出这个消息。这不是一道难题,我们可以用哈希表来做,建立消息和时间戳之间的映射,如果某个消息不再哈希表表,我们建立其和时间戳的映射,并返回true。如果应经在哈希表里了,我们看当前时间戳是否比哈希表中保存的时间戳大10,如果是,更新哈希表,并返回true,反之返回false,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Logger {
public:
Logger() {}

bool shouldPrintMessage(int timestamp, string message) {
if (!m.count(message)) {
m[message] = timestamp;
return true;
}
if (timestamp - m[message] >= 10) {
m[message] = timestamp;
return true;
}
return false;
}

private:
unordered_map<string, int> m;
};

Leetcode367. Valid Perfect Square

Given a positive integer num, write a function which returns True if num is a perfect square else False.

Follow up: Do not use any built-in library function such as sqrt.

Example 1:

1
2
Input: num = 16
Output: true

Example 2:

1
2
Input: num = 14
Output: false

给了我们一个数,让我们判断其是否为完全平方数,那么显而易见的是,肯定不能使用 brute force,这样太不高效了,那么最小是能以指数的速度来缩小范围。

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
bool isPerfectSquare(int num) {
if(num == 1)
return 1;
for(int i = 1; i <= num/i; i ++) {
if(i * i == num)
return true;
}
return false;
}
};

Leetcode368. Largest Divisible Subset

Given a set of distinct positive integers, find the largest subset such that every pair (S i, Sj) of elements in this subset satisfies: Si % Sj = 0 or Sj % Si = 0.

If there are multiple solutions, return any subset is fine.

Example 1:

1
2
3
nums: [1,2,3]

Result: [1,2] (of course, [1,3] will also be ok)

Example 2:

1
2
3
nums: [1,2,4,8]

Result: [1,2,4,8]

这道题给了我们一个数组,让我们求这样一个子集合,集合中的任意两个数相互取余均为0,而且提示中说明了要使用DP来解。那么我们考虑,较小数对较大数取余一定不为0,那么问题就变成了看较大数能不能整除这个较小数。那么如果数组是无序的,处理起来就比较麻烦,所以我们首先可以先给数组排序,这样我们每次就只要看后面的数字能否整除前面的数字。定义一个动态数组dp,其中dp[i]表示到数字nums[i]位置最大可整除的子集合的长度,还需要一个一维数组parent,来保存上一个能整除的数字的位置,两个整型变量mx和mx_idx分别表示最大子集合的长度和起始数字的位置,我们可以从后往前遍历数组,对于某个数字再遍历到末尾,在这个过程中,如果nums[j]能整除nums[i], 且dp[i] < dp[j] + 1的话,更新dp[i]和parent[i],如果dp[i]大于mx了,还要更新mx和mx_idx,最后循环结束后,我们来填res数字,根据parent数组来找到每一个数字,参见代码如下:

解法一:

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
class Solution {
public:
vector<int> largestDivisibleSubset(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<int> dp(nums.size(), 0), parent(nums.size(), 0), res;
int mx = 0, mx_idx = 0;
for (int i = nums.size() - 1; i >= 0; --i) {
for (int j = i; j < nums.size(); ++j) {
if (nums[j] % nums[i] == 0 && dp[i] < dp[j] + 1) {
dp[i] = dp[j] + 1;
parent[i] = j;
if (mx < dp[i]) {
mx = dp[i];
mx_idx = i;
}
}
}
}
for (int i = 0; i < mx; ++i) {
res.push_back(nums[mx_idx]);
mx_idx = parent[mx_idx];
}
return res;
}
};

Leetcode371. Sum of Two Integers

Calculate the sum of two integers a and b, but you are not allowed to use the operator + and -.

Example 1:

1
2
Input: a = 1, b = 2
Output: 3

Example 2:

1
2
Input: a = -2, b = 3
Output: 1

首先,先对数据a,b进行&(与)运算,原因是下面用^(异或)的方法来进行加法会漏掉进位,所以,先对数据进行&运算得到carry,carry中为1的位是会进行进位的位,接下来对数据进行^运算,结果记为add1,实现伪加法,之所以是伪加法,是因为它漏掉了进位。那漏掉的进位怎么办呢?对carry进行左移得到C,C+add1就是两个数据的真正的和。那怎么实现C+add1呢?将add1赋予a,C赋予b,重复以上的操作,直到b等于0。

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int getSum(int a, int b) {
int c;
while(b !=0 ) {
c = (unsigned)(a&b) << 1;
a = a ^ b;
b = c;
}
return a;
}
};

Leetcode372. Super Pow

Your task is to calculate a b mod 1337 where a is a positive integer and b is an extremely large positive integer given in the form of an array.

Example1:

1
2
3
4
a = 2
b = [3]

Result: 8

Example2:

1
2
3
4
a = 2
b = [1,0]

Result: 1024

这道题让我们求一个数的很大的次方对1337取余的值,开始一直在想这个1337有什么玄机,为啥突然给这么一个数,感觉很突兀,后来想来想去也没想出来为啥,估计就是怕结果太大无法表示,随便找个数取余吧。那么这道题和之前那道Pow(x, n)的解法很类似,我们都得对半缩小,不同的是后面都要加上对1337取余。由于给定的指数b是一个一维数组的表示方法,我们要是折半缩小处理起来肯定十分不方便,所以我们采用按位来处理,比如223 = (22)10 * 23, 所以我们可以从b的最高位开始,算出个结果存入res,然后到下一位是,res的十次方再乘以a的该位次方再对1337取余,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int superPow(int a, vector<int>& b) {
long long res = 1;
for (int i = 0; i < b.size(); ++i) {
res = pow(res, 10) * pow(a, b[i]) % 1337;
}
return res;
}
int pow(int x, int n) {
if (n == 0) return 1;
if (n == 1) return x % 1337;
return pow(x % 1337, n / 2) * pow(x % 1337, n - n / 2) % 1337;
}
};

Leetcode373. Find K Pairs with Smallest Sums

You are given two integer arrays nums1 and nums2 sorted in ascending order and an integer k.

Define a pair (u, v) which consists of one element from the first array and one element from the second array.

Return the k pairs (u1, v1), (u2, v2), …, (uk, vk) with the smallest sums.

Example 1:

1
2
3
Input: nums1 = [1,7,11], nums2 = [2,4,6], k = 3
Output: [[1,2],[1,4],[1,6]]
Explanation: The first 3 pairs are returned from the sequence: [1,2],[1,4],[1,6],[7,2],[7,4],[11,2],[7,6],[11,4],[11,6]

Example 2:

1
2
3
Input: nums1 = [1,1,2], nums2 = [1,2,3], k = 2
Output: [[1,1],[1,1]]
Explanation: The first 2 pairs are returned from the sequence: [1,1],[1,1],[1,2],[2,1],[1,2],[2,2],[1,3],[1,3],[2,3]

Example 3:

1
2
3
Input: nums1 = [1,2], nums2 = [3], k = 3
Output: [[1,3],[2,3]]
Explanation: All possible pairs are returned from the sequence: [1,3],[2,3]

Constraints:

  • 1 <= nums1.length, nums2.length <= 104
  • -109 <= nums1[i], nums2[i] <= 109
  • nums1 and nums2 both are sorted in ascending order.
  • 1 <= k <= 1000

这道题给了我们两个数组,让从每个数组中任意取出一个数字来组成不同的数字对,返回前K个和最小的数字对。那么这道题有多种解法,首先来看 brute force 的解法,这种方法从0循环到数组的个数和k之间的较小值,这样做的好处是如果k远小于数组个数时,不需要计算所有的数字对,而是最多计算 k*k 个数字对,然后将其都保存在 res 里,这时候给 res 排序,用自定义的比较器,就是和的比较,然后把比k多出的数字对删掉即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
vector<pair<int, int>> kSmallestPairs(vector<int>& nums1, vector<int>& nums2, int k) {
vector<pair<int, int>> res;
for (int i = 0; i < min((int)nums1.size(), k); ++i) {
for (int j = 0; j < min((int)nums2.size(), k); ++j) {
res.push_back({nums1[i], nums2[j]});
}
}
sort(res.begin(), res.end(), [](pair<int, int> &a, pair<int, int> &b){return a.first + a.second < b.first + b.second;});
if (res.size() > k) res.erase(res.begin() + k, res.end());
return res;
}
};

Leetcode374. Guess Number Higher or Lower

We are playing the Guess Game. The game is as follows:

  • I pick a number from 1 to n. You have to guess which number I picked.
  • Every time you guess wrong, I’ll tell you whether the number is higher or lower.
  • You call a pre-defined API guess(int num) which returns 3 possible results (-1, 1, or 0):
    • -1 : My number is lower
    • 1 : My number is higher
    • 0 : Congrats! You got it!

Example :

1
2
Input: n = 10, pick = 6
Output: 6

二分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int guessNumber(int n) {
int l = 0, r = n, mid;
while(l <= r) {
mid = l + (r - l) / 2;
if(guess(mid) < 0)
r = mid - 1;
else if(guess(mid) > 0)
l = mid + 1;
else if(0 == guess(mid))
return mid;
}
return -1;
}
};

Leetcode375. Guess Number Higher or Lower II

We are playing the Guessing Game. The game will work as follows:

  1. I pick a number between 1 and n.
  2. You guess a number.
  3. If you guess the right number, you win the game.
  4. If you guess the wrong number, then I will tell you whether the number I picked is higher or lower, and you will continue guessing.
  5. Every time you guess a wrong number x, you will pay x dollars. If you run out of money, you lose the game.
    Given a particular n, return the minimum amount of money you need to guarantee a win regardless of what number I pick.

Example 1:

1
2
Input: n = 10
Output: 16

Explanation: The winning strategy is as follows:

  • The range is [1,10]. Guess 7.
    • If this is my number, your total is $0. Otherwise, you pay $7.
    • If my number is higher, the range is [8,10]. Guess 9.
      • If this is my number, your total is $7. Otherwise, you pay $9.
      • If my number is higher, it must be 10. Guess 10. Your total is $7 + $9 = $16.
      • If my number is lower, it must be 8. Guess 8. Your total is $7 + $9 = $16.
    • If my number is lower, the range is [1,6]. Guess 3.
      • If this is my number, your total is $7. Otherwise, you pay $3.
      • If my number is higher, the range is [4,6]. Guess 5.
        • If this is my number, your total is $7 + $3 = $10. Otherwise, you pay $5.
        • If my number is higher, it must be 6. Guess 6. Your total is $7 + $3 + $5 = $15.
        • If my number is lower, it must be 4. Guess 4. Your total is $7 + $3 + $5 = $15.
      • If my number is lower, the range is [1,2]. Guess 1.
        • If this is my number, your total is $7 + $3 = $10. Otherwise, you pay $1.
        • If my number is higher, it must be 2. Guess 2. Your total is $7 + $3 + $1 = $11.

The worst case in all these scenarios is that you pay $16. Hence, you only need $16 to guarantee a win.

Example 2:

1
2
Input: n = 1
Output: 0

Explanation: There is only one possible number, so you can guess 1 and not have to pay anything.

Example 3:

1
2
Input: n = 2
Output: 1

Explanation: There are two possible numbers, 1 and 2.

  • Guess 1.
    • If this is my number, your total is $0. Otherwise, you pay $1.
    • If my number is higher, it must be 2. Guess 2. Your total is $1.
      The worst case is that you pay $1.

这道题需要用到 Minimax 极小化极大算法,并且题目中还说明了要用 DP 来做,需要建立一个二维的 dp 数组,其中 dp[i][j] 表示从数字i到j之间猜中任意一个数字最少需要花费的钱数,那么需要遍历每一段区间 [j, i],维护一个全局最小值 global_min 变量,然后遍历该区间中的每一个数字,计算局部最大值local_max = k + max(dp[j][k - 1], dp[k + 1][i]),这个正好是将该区间在每一个位置都分为两段,然后取当前位置的花费加上左右两段中较大的花费之和为局部最大值,相当于是猜k这个值时的代价。为啥要取两者之间的较大值呢,因为要 cover 所有的情况,就得取最坏的情况。然后更新全局最小值,最后在更新 dp[j][i] 的时候看j和i是否是相邻的,相邻的话赋为j,否则赋为 global_min。这里为啥又要取较小值呢,因为 dp 数组是求的 [j, i] 范围中的最低 cost,比如只有两个数字1和2,那么肯定是猜1的 cost 低。

如果只有一个数字,那么不用猜,cost 为0。如果有两个数字,比如1和2,猜1,即使不对,cost 也比猜2要低。如果有三个数字 1,2,3,那么就先猜2,根据对方的反馈,就可以确定正确的数字,所以 cost 最低为2。如果有四个数字 1,2,3,4,那么情况就有点复杂了,策略是用k来遍历所有的数字,然后再根据k分成的左右两个区间,取其中的较大 cost 加上k。

  • 当k为1时,左区间为空,所以 cost 为0,而右区间 2,3,4,根据之前的分析应该取3,所以整个 cost 就是 1+3=4。
  • 当k为2时,左区间为1,cost 为0,右区间为 3,4,cost 为3,整个 cost 就是 2+3=5。
  • 当k为3时,左区间为 1,2,cost 为1,右区间为4,cost 为0,整个 cost 就是 3+1=4。
  • 当k为4时,左区间 1,2,3,cost 为2,右区间为空,cost 为0,整个 cost 就是 4+2=6。

综上k的所有情况,此时应该取整体 cost 最小的,即4,为最后的答案,这就是极小化极大算法:

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
class Solution {
public:
int getMoneyAmount(int n) {
vector<vector<int>> dp(n+1, vector<int>(n+1, 0));

for (int i = 0; i < n + 1; i++)
for (int j = 0; j < n + 1; j++)
if (i != j)
dp[i][j] = INT_MAX;

for (int i = 1; i < n; i ++)
dp[i][i+1] = i;

for(int i = 2; i <= n; i ++) {
for (int j = i-1; j >= 1; j --) {
int local, minn = INT_MAX;
for (int k = j+1; k < i; k ++) {
local = max(dp[j][k-1], dp[k+1][i]) + k;
minn = min(minn, local);
}
dp[j][i] = min(minn, dp[j][i]);
}
}
return dp[1][n];
}
};

极小极大的定义

Minimax算法 又名极小化极大算法,是一种找出失败的最大可能性中的最小值的算法(即最小化对手的最大得益)。通常以递归形式来实现。

Minimax算法常用于棋类等由两方较量的游戏和程序。该算法是一个零总和算法,即一方要在可选的选项中选择将其优势最大化的选择,另一方则选择令对手 优势最小化的一个,其输赢的总和为0(有点像能量守恒,就像本身两个玩家都有1点,最后输家要将他的1点给赢家,但整体上还是总共有2点)。很多棋类游戏 可以采取此算法,例如tic-tac-toe。

以TIC-TAC-TOE为例

tic-tac-toe就是井字棋。

一般X的玩家先下。设定X玩家的最大利益为正无穷(+∞),O玩家的最大利益为负无穷(-∞),这样我们称X玩家为MAX(因为他总是追求更大的值),成O玩家为MIN(她总是追求更小的值),各自都为争取自己的最大获益而努力。

现在,让我们站在MAX的立场来分析局势(这是必须的,应为你总不能两边倒吧,你喜欢的话也可以选择MIN)。由于MAX是先下的(习惯上X的玩家先下),于是构建出来的博弈树如下(前面两层):

MAX总是会选择MIN最大获利中的最小值(对MAX最有利),同样MIN也会一样,选择对自己最有利的(即MAX有可能获得的最大值)。有点难理解,其实就是自己得不到也不给你得到这样的意思啦,抢先把对对手有利的位置抢占了。你会看出,这是不断往下深钻的,直到最底层(即叶节点)你才能网上回溯,确定那个是对你最有利的。

具体过程会像是这么一个样子的:

但实际情况下,完全遍历一颗博弈树是不现实的,因为层级的节点数是指数级递增的,完全遍历会很耗时…一般情况下需要限制深钻的层数,在达到限定的层数时就返回一个估算值(通过一个启发式的函数对当前博弈位置进行估值),这样获得的值就不是精确的了(遍历的层数越深越精确,当然和估算函数也有一定关系),但该值依然是足够帮助我们做出决策的。于是,对耗时和精确度需要做一个权衡。一般我们限定其遍历的深度为6(目前多数的象棋游戏也是这么设定的)。

于是,我们站在MAX的角度,评估函数会是这样子的:

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
static   final   int   INFINITY = 100 ;   // 表示无穷的值  
static final int WIN = +INFINITY ; // MAX的最大利益为正无穷
static final int LOSE = -INFINITY ; // MAX的最小得益(即MIN的最大得益)为负无穷
static final int DOUBLE_LINK = INFINITY / 2 ; // 如果同一行、列或对角上连续有两个,赛点
static final int INPROGRESS = 1 ; // 仍可继续下(没有胜出或和局)
static final int DRAW = 0 ; // 和局
static final int [][] WIN_STATUS = {
{ 0, 1, 2 },
{ 3, 4, 5 },
{ 6, 7, 8 },
{ 0, 3, 6 },
{ 1, 4, 7 },
{ 2, 5, 8 },
{ 0, 4, 8 },
{ 2, 4, 6 }
};
/**
* 估值函数,提供一个启发式的值,决定了游戏AI的高低
*/
public int gameState( char [] board ) {
int result = INPROGRESS;
boolean isFull = true ;

// is game over?
for ( int pos = 0; pos < 9; pos++) {
char chess = board[pos];
if ( empty == chess) {
isFull = false ;
break ;
}
}
// is Max win/lose?
for ( int [] status : WIN_STATUS) {
char chess = board[status[0]];
if (chess == empty ) {
break ;
}
int i = 1;
for (; i < status.length; i++) {
if (board[status[i]] != chess) {
break ;
}
}
if (i == status.length) {
result = chess == x ? WIN : LOSE;
break ;
}
}
if (result != WIN & result != LOSE) {
if (isFull) {
// is draw
result = DRAW;
}
else {
// check double link
// finds[0]->'x', finds[1]->'o'
int [] finds = new int [2];
for ( int [] status : WIN_STATUS) {
char chess = empty ;
boolean hasEmpty = false ;
int count = 0;
for ( int i = 0; i < status.length; i++) {
if (board[status[i]] == empty ) {
hasEmpty = true ;
}
else {
if (chess == empty ) {
chess = board[status[i]];
}
if (board[status[i]] == chess) {
count++;
}
}
}
if (hasEmpty && count > 1) {
if (chess == x ) {
finds[0]++;
} else {
finds[1]++;
}
}
}
// check if two in one line
if (finds[1] > 0) {
result = -DOUBLE_LINK;
} else if (finds[0] > 0) {
result = DOUBLE_LINK;
}
}
}
return result;
}

基于这些,一个限定层数的实现是这样的:

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
/** 
* 以'x'的角度来考虑的极小极大算法
*/
public int minimax( char [] board, int depth){
int [] bestMoves = new int [9];
int index = 0;
int bestValue = - INFINITY ;
for ( int pos=0; pos<9; pos++){
if (board[pos]== empty ){
board[pos] = x ;

int value = min(board, depth);
if (value>bestValue){
bestValue = value;
index = 0;
bestMoves[index] = pos;
} else
if (value==bestValue){
index++;
bestMoves[index] = pos;
}

board[pos] = empty ;
}
}
if (index>1){
index = ( new Random (System. currentTimeMillis ()).nextInt()>>>1)%index;
}
return bestMoves[index];
}
/**
* 对于'x',估值越大对其越有利
*/
public int max( char [] board, int depth){
int evalValue = gameState (board);
boolean isGameOver = (evalValue== WIN || evalValue== LOSE || evalValue== DRAW );
if (depth==0 || isGameOver){
return evalValue;
}

int bestValue = - INFINITY ;
for ( int pos=0; pos<9; pos++){
if (board[pos]== empty ){
// try
board[pos] = x ;

// maximixing
bestValue = Math. max (bestValue, min(board, depth-1));
// reset
board[pos] = empty ;
}
}
return evalValue;
}
/**
* 对于'o',估值越小对其越有利
*/
public int min( char [] board, int depth){
int evalValue = gameState (board);
boolean isGameOver = (evalValue== WIN || evalValue== LOSE || evalValue== DRAW );
if (depth==0 || isGameOver){
return evalValue;
}
int bestValue = + INFINITY ;
for ( int pos=0; pos<9; pos++){
if (board[pos]== empty ){
// try
board[pos] = o ;
// minimixing
bestValue = Math.min(bestValue, max(board, depth-1));
// reset
board[pos] = empty ;
}
}
return evalValue;
}

Alpha-beta剪枝

另外,通过结合Alpha-beta剪枝能进一步优化效率。Alpha-beta剪枝顾名思义就是裁剪掉一些不必要的分支,以减少遍历的节点数。实际上是通过传递两个参数alpha和beta到递归的极小极大函数中,alpha表示了MAX的最坏情况,beta表示了MIN的最坏情况,因此他们的初始值为负无穷和正无穷。在递归的过程中,在轮到MAX的回合,如果极小极大的值比alpha大,则更新alpha;在MIN的回合中,如果极小极大值比beta小,则更新beta。当alpha和beta相交时(即alpha>=beta),这时该节点的所有子节点对于MAX和MIN双方都不会带来好的获益,所以可以忽略掉(裁剪掉)以该节点为父节点的整棵子树。

根据这一定义,可以很轻易地在上面程序的基础上进行改进:

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
/** 
* 以'x'的角度来考虑的极小极大算法
*/
public int minimax( char [] board, int depth){
int [] bestMoves = new int [9];
int index = 0;

int bestValue = - INFINITY ;
for ( int pos=0; pos<9; pos++){
if (board[pos]== empty ){
board[pos] = x ;

int value = min(board, depth, - INFINITY , + INFINITY );
if (value>bestValue){
bestValue = value;
index = 0;
bestMoves[index] = pos;
}
else if (value==bestValue){
index++;
bestMoves[index] = pos;
}
board[pos] = empty ;
}
}
if (index>1){
index = ( new Random (System. currentTimeMillis ()).nextInt()>>>1)%index;
}
return bestMoves[index];
}
/**
* 对于'x',估值越大对其越有利
*/
public int max( char [] board, int depth, int alpha, int beta){
int evalValue = gameState (board);
boolean isGameOver = (evalValue== WIN || evalValue== LOSE || evalValue== DRAW );
if (beta<=alpha){
return evalValue;
}
if (depth==0 || isGameOver){
return evalValue;
}
int bestValue = - INFINITY ;
for ( int pos=0; pos<9; pos++){
if (board[pos]== empty ){
// try
board[pos] = x ;
// maximixing
bestValue = Math. max (bestValue, min(board, depth-1, Math. max (bestValue, alpha), beta));
// reset
board[pos] = empty ;
}
}
return evalValue;
}
/**
* 对于'o',估值越小对其越有利
*/
public int min( char [] board, int depth, int alpha, int beta){
int evalValue = gameState (board);
boolean isGameOver = (evalValue== WIN || evalValue== LOSE || evalValue== DRAW );
if (alpha>=beta){
return evalValue;
}
// try
if (depth==0 || isGameOver || alpha>=beta){
return evalValue;
}

int bestValue = + INFINITY ;
for ( int pos=0; pos<9; pos++){
if (board[pos]== empty ){
// try
board[pos] = o ;
// minimixing
bestValue = Math.min(bestValue, max(board, depth-1, alpha, Math.min(bestValue, beta)));
// reset
board[pos] = empty ;
}
}
return evalValue;
}

Leetcode376. Wiggle Subsequence

A wiggle sequence is a sequence where the differences between successive numbers strictly alternate between positive and negative. The first difference (if one exists) may be either positive or negative. A sequence with one element and a sequence with two non-equal elements are trivially wiggle sequences.

For example, [1, 7, 4, 9, 2, 5] is a wiggle sequence because the differences (6, -3, 5, -7, 3) alternate between positive and negative.
In contrast, [1, 4, 7, 2, 5] and [1, 7, 4, 5, 5] are not wiggle sequences. The first is not because its first two differences are positive, and the second is not because its last difference is zero.
A subsequence is obtained by deleting some elements (possibly zero) from the original sequence, leaving the remaining elements in their original order.

Given an integer array nums, return the length of the longest wiggle subsequence of nums.

Example 1:

1
2
3
Input: nums = [1,7,4,9,2,5]
Output: 6
Explanation: The entire sequence is a wiggle sequence with differences (6, -3, 5, -7, 3).

Example 2:

1
2
3
4
Input: nums = [1,17,5,10,13,15,10,5,16,8]
Output: 7
Explanation: There are several subsequences that achieve this length.
One is [1, 17, 10, 13, 10, 16, 8] with differences (16, -7, 3, -3, 6, -8).

Example 3:

1
2
Input: nums = [1,2,3,4,5,6,7,8,9]
Output: 2

一个整数序列,如果两个相邻元素的差恰好正负交替出现,则该序列被称为摇摆序列。一个小于2个元素的序列直接为摇摆序列。给一个随机序列,求这个序列满足摇摆序列定义的最长子序列的长度。

解法:记录序列中前后两个元素的状态。初始状态为begin,如果后一个元素大于前一个元素,则状态为up,反之状态为down。当状态转换时,摇摆序列的长度加1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
if(nums.size() < 2)
return nums.size();

int dir = 0, res = 1;
for (int i = 1; i < nums.size(); i ++) {
if(nums[i] > nums[i-1]) {
if (dir == 0 || dir == -1) {
res ++;
dir = 1;
}
}
else if (nums[i] < nums[i-1]) {
if (dir == 0 || dir == 1) {
res ++;
dir = -1;
}
}
}
return res;
}
};

状态转移方程

  • dp[i][0]=max{dp[j][1]}+1 当nums[i]>nums[j]
  • dp[i][1]=max{dp[j][0]}+1 当nums[i]<nums[j]

其中 0<=j<i

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
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
int len = nums.size();
if(len < 2)
return len;

vector<vector<int> > dp(len+1, vector<int>(2, 0));
dp[0][0] = 1;
dp[0][1] = 1;
for (int i = 1; i < len; i ++) {
int a = INT_MIN;
int b = INT_MIN;
for (int k = i-1; k >= 0; k --) {
if (nums[i] > nums[k])
a = max(a, dp[k][0]);
if (nums[i] < nums[k])
b = max(b, dp[k][1]);
}
dp[i][1] = a > INT_MIN ? a+1 : 1;
dp[i][0] = b > INT_MIN ? b+1 : 1;
}

return max(dp[len-1][0], dp[len-1][1]);
}
};

Leetcode377. Combination Sum IV

Given an array of distinct integers nums and a target integer target, return the number of possible combinations that add up to target.

The answer is guaranteed to fit in a 32-bit integer.

Example 1:

1
2
Input: nums = [1,2,3], target = 4
Output: 7

Explanation:

1
2
3
4
5
6
7
8
The possible combination ways are:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)

Note that different sequences are counted as different combinations.

这里需要一个一维数组 dp,其中 dp[i] 表示目标数为i的解的个数,然后从1遍历到 target,对于每一个数i,遍历 nums 数组,如果 i>=x, dp[i] += dp[i - x]。这个也很好理解,比如说对于 [1,2,3] 4,这个例子,当计算 dp[3] 的时候,3可以拆分为 1+x,而x即为 dp[2],3也可以拆分为 2+x,此时x为 dp[1],3同样可以拆为 3+x,此时x为 dp[0],把所有的情况加起来就是组成3的所有情况了。

如果 target 远大于 nums 数组的个数的话,上面的算法可以做适当的优化,先给 nums 数组排个序,然后从1遍历到 target,对于i小于数组中的数字x时,直接 break 掉,因为后面的数更大,其余地方不变,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
vector<int> dp(target+1, 0);
sort(nums.begin(), nums.end());
dp[0] = 1;
for (int i = 1; i < target+1; i ++) {
for (int ii : nums) {
if (i < ii)
break;
if (long(dp[i]) + dp[i-ii] > INT_MAX)
continue;
else
dp[i] = dp[i] + dp[i - ii];
}
}
return dp[target];
}
};

中间可能会溢出,超过INT_MAX,需要处理一下,如果超过了INT_MAX以后也就不可能用到了,所以直接continue。

Leetcode378. Kth Smallest Element in a Sorted Matrix

Given an n x n matrix where each of the rows and columns are sorted in ascending order, return the kth smallest element in the matrix.

Note that it is the kth smallest element in the sorted order, not the kth distinct element.

Example 1:

1
2
3
Input: matrix = [[1,5,9],[10,11,13],[12,13,15]], k = 8
Output: 13
Explanation: The elements in the matrix are [1,5,9,10,11,12,13,13,15], and the 8th smallest number is 13

Example 2:

1
2
Input: matrix = [[-5]], k = 1
Output: -5

输入矩阵matrix,每一行可以看做是一个排序好的数组,可以将这几个小数组排序好之后取第k个数,即可。排序几个已经排序好的数组,可以参考leetcode23。时间复杂度O(klogn)。

因为同时每一列也是排序号的,考虑用二分查找实现。

二分思路

返回值一定在matrix[0][0]matrix[n-1][n-1]之间。令函数g(x)={matrix中小于等于x的数量}={of(matrix[i][j]<=x)}。g(x)是一个递增的函数,x越大,g(x)越大。返回值是满足g(x)>=k,的最小值。

1
2
3
4
5
6
7
8
9
10
11
12
int kthSmallest(vector<vector<int>>& matrix, int k) {
int len = matrix.size();
int left = matrix[0][0], right = matrix[len-1][len-1];
while(left <= right) {
int middle = left + (right - left) / 2;
if (count(matrix, middle, len) >= k)
right = middle - 1;
else
left = middle + 1;
}
return left;
}

计算小于等于middle值的个数

接下来的问题是如何数出< = middle的数量。可以发现一个性质:任取一个数 mid 满足l <= middle <= r,那么矩阵中不大于 mid 的数,肯定全部分布在矩阵的左上角。

我们可以从左下角开始遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private int countSmallerOrEqual(int[][] matrix, int middle, int n){
int i = n - 1;
int j = 0;
int num = 0;
while(i>=0 && j<n){
if(matrix[i][j]<=middle){
num += i+1;
j++;
}else{
i--;
}
}
return num;
}

时间复杂度O(nlog(r−l))。二分查找进行次数为O(log(r-l)),每次操作时间复杂度为 O(n)。

当然我们也可以每次遍历一个子数组,二分查找个数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private int countSmallerOrEqual(int[][] matrix, int middle, int n){
int num = 0;
for(int i = 0;i<n;i++){
int l = 0, r = n-1;
while(l<=r){
int m = l + ((r-l)>>1);
if(matrix[i][m]>middle){
r = m - 1;
}else{
l = m + 1;
}
}
if(l>=0){
num += l;
}
}
return num;
}

Leetcode382. Linked List Random Node

Given a singly linked list, return a random node’s value from the linked list. Each node must have the same probability of being chosen.

Example 1:

1
2
3
4
5
Input
["Solution", "getRandom", "getRandom", "getRandom", "getRandom", "getRandom"]
[[[1, 2, 3]], [], [], [], [], []]
Output
[null, 1, 3, 2, 2, 3]

Explanation

1
2
3
4
5
6
7
Solution solution = new Solution([1, 2, 3]);
solution.getRandom(); // return 1
solution.getRandom(); // return 3
solution.getRandom(); // return 2
solution.getRandom(); // return 2
solution.getRandom(); // return 3
// getRandom() should return either 1, 2, or 3 randomly. Each element should have equal probability of returning.

这道题给了我们一个链表,让随机返回一个节点,那么最直接的方法就是先统计出链表的长度,然后根据长度随机生成一个位置,然后从开头遍历到这个位置即可。

Leetcode383. Ransom Note

Given an arbitrary ransom note string and another string containing letters from all the magazines, write a function that will return true if the ransom note can be constructed from the magazines ; otherwise, it will return false.

Each letter in the magazine string can only be used once in your ransom note.

Example 1:

1
2
Input: ransomNote = "a", magazine = "b"
Output: false

Example 2:

1
2
Input: ransomNote = "aa", magazine = "ab"
Output: false

Example 3:

1
2
Input: ransomNote = "aa", magazine = "aab"
Output: true

判断给定字符串Ransom 能否由另一字符串magazine中字符中的某些字符组合生成。 显然,统计字符出现次数即可!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
vector<int> re(27, 0), ree(27, 0);
for(int i = 0; i < magazine.length(); i ++)
re[magazine[i]-'a'] ++;
for(int i = 0; i < ransomNote.length(); i ++) {
re[ransomNote[i]-'a'] --;
if(re[ransomNote[i]-'a'] < 0)
return false;
}
return true;
}
};

Leetcode384. Shuffle an Array

Given an integer array nums, design an algorithm to randomly shuffle the array.

Implement the Solution class:

  • Solution(int[] nums) Initializes the object with the integer array nums.
  • int[] reset() Resets the array to its original configuration and returns it.
  • int[] shuffle() Returns a random shuffling of the array.

Example 1:

1
2
3
4
5
Input
["Solution", "shuffle", "reset", "shuffle"]
[[[1, 2, 3]], [], [], []]
Output
[null, [3, 1, 2], [1, 2, 3], [1, 3, 2]]

Explanation

1
2
3
4
Solution solution = new Solution([1, 2, 3]);
solution.shuffle(); // Shuffle the array [1,2,3] and return its result. Any permutation of [1,2,3] must be equally likely to be returned. Example: return [3, 1, 2]
solution.reset(); // Resets the array back to its original configuration [1,2,3]. Return [1, 2, 3]
solution.shuffle(); // Returns the random shuffling of array [1,2,3]. Example: return [1, 3, 2]

我们遍历数组每个位置,每次都随机生成一个坐标位置,然后交换当前遍历位置和随机生成的坐标位置的数字,这样如果数组有n个数字,那么我们也随机交换了n组位置,从而达到了洗牌的目的,这里需要注意的是i + rand() % (res.size() - i)不能写成rand() % res.size(),不是真正的随机分布,应该使用Knuth shuffle算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
Solution(vector<int> nums): v(nums) {}

/** Resets the array to its original configuration and return it. */
vector<int> reset() {
return v;
}

/** Returns a random shuffling of the array. */
vector<int> shuffle() {
vector<int> res = v;
for (int i = 0; i < res.size(); ++i) {
int t = i + rand() % (res.size() - i);
swap(res[i], res[t]);
}
return res;
}

private:
vector<int> v;
};

洗牌的正确姿势-Knuth shuffle算法

怎样用计算机模拟出足够随机的洗牌结果,看似很简单,但其实它比给一副乱糟糟的牌排好序可能还更难一些。洗牌问题的描述很简单:即如何通过打乱顺序,让一副扑克牌变成随机的排列,而且每一种可能的排列有相同机会出现。关键点在于“相同机会”,即各种随机排列是等可能的。下面先简单介绍一个常见的错误做法,然后看看如何改进变成Knuth 洗牌算法。

先看看一个很直接的做法(一副牌在这里用一个数组表示):

对数组从头到尾扫描一遍,扫描过程中,每次都从整个数组随机选一个元素,跟当前扫描到的元素交换位置。

也就是,先拿起第一张牌,把它跟从整副牌里随机挑出的另一张牌(把它叫做随机牌)交换位置(随机牌也可能是第一张牌自己,这个时候就相当于不交换位置);接着拿起第二张牌,也把它跟随机选出的另一张牌交换位置;一直重复直到把最后一张牌跟随机牌交换位置。

用python实现起来也只有几行:

1
2
3
4
5
def shuffleSort(a):
N = len(a)
for i in range(N):
j = random.randint(0, N-1)
a[j], a[i] = a[i], a[j]

这样随机交换之后,每种排列出现的可能性会是等概率的吗?看起来好像会,但事实上,经过这样交换,总有一部分排列出现的概率更高一些,这个洗牌过程并没有很公平。

为什么不够公平?要从直觉上能够理解清楚还不是那么容易。我们用一个简单的例子来看看,假设这副牌只有三张,分别是{A,B, C}.

按照前面说的方法,第一轮把第一张牌A跟随机一张牌进行交换,会产生三个等可能的结果:

1
2
3
no change:      {A, B,  C}
swap with B: {B, A, C}
swap with C: {C, B, A}

第二轮从上述三种排列出发,把第二张牌跟随机的一张牌交换,得到九种(有重复)等可能的排列。第三轮也类似。用树状图表示可以看得直观些。

可以看到,最后产生的27个结果里面,{A, B, C}, {C, A, B}, {C, B, A}都出现了4次,而{A, C, B}, {B, A, C}, {B, C, A}都出现了5次。也就是说有些排序出现的可能性是4/27,有些却是5/27. 而且,随着牌数目的增加,这个概率的不均衡会更加严重。

我们重新看看这个方法。A,B,C三张牌的全排列只有6种,但是在这个方法里,一共产生了27个结果(27个分支),它不是6的倍数,怎么都没法给6种排列平均分嘛。所以,要让结果够公平,一个必要条件就是产生的分支是6的整数倍,也就是N!的整数倍。

Knuth洗牌算法

所以牌该怎么洗呢?在上述方法的基础上,做一处修改,就能剪去一些分支,让分支数是N!的整数倍。这就是Knuth洗牌算法。

1
2
3
4
5
def shuffleSort(a):
N = len(a)
for i in range(N):
j = random.randint(0, i)
a[j], a[i] = a[i], a[j]

唯一修改的就是随机牌j选取的方法,在拿起第i张牌时,只从它前面的牌随机选出j,而不是从整副牌里面随机选取。

Really? 就只是这样吗?

是的。就这么简单。

还是用{A, B, C}这三张牌作为例子看看。

第一轮拿起牌A, 现在随机牌只能是A,经过第一轮之后,其实没有发生变换,还是{A,B,C}; (这一步也可以省略)

第二轮拿起牌B, 从{A,B}里面随机选一张牌跟B交换,会得到两种等可能的结果:

1
2
swap with A: {B, A, C}
no change: {A, B, C}

第三轮从上面两种可能的排列出发,拿起最后一张牌(这里都是C), 再从所有牌里面随机选一张跟它交换。树状图如下:

最终得到的结果只有6个,正好是三张牌的所有6种排列结果,每种出现一次。所以,Knuth洗牌算法是公平的。

Leetcode385. Mini Parser

Given a nested list of integers represented as a string, implement a parser to deserialize it.

Each element is either an integer, or a list – whose elements may also be integers or other lists.

Note: You may assume that the string is well-formed:

  • String is non-empty.
  • String does not contain white spaces.
  • String contains only digits 0-9, [, - ,, ].

Example 1:

1
2
3
Given s = "324",

You should return a NestedInteger object which contains a single integer 324.

Example 2:

1
2
3
4
5
6
7
8
Given s = "[123,[456,[789]]]",

Return a NestedInteger object containing a nested list with 2 elements:
1. An integer containing value 123.
2. A nested list containing two elements:
i. An integer containing value 456.
ii. A nested list with one element:
a. An integer containing value 789.

这道题让我们实现一个迷你解析器用来把一个字符串解析成NestInteger类,关于这个嵌套链表类的题我们之前做过三道,Nested List Weight Sum II,Flatten Nested List Iterator,和Nested List Weight Sum。应该对这个类并不陌生了,我们可以先用递归来做,思路是,首先判断s是否为空,为空直接返回,不为空的话看首字符是否为[,不是的话说明s为一个整数,我们直接返回结果。如果首字符是[,且s长度小于等于2,说明没有内容,直接返回结果。反之如果s长度大于2,我们从i=1开始遍历,我们需要一个变量start来记录某一层的其实位置,用cnt来记录跟其实位置是否为同一深度,cnt=0表示同一深度,由于中间每段都是由逗号隔开,所以当我们判断当cnt为0,且当前字符是逗号或者已经到字符串末尾了,我们把start到当前位置之间的字符串取出来递归调用函数,把返回结果加入res中,然后start更新为i+1。如果遇到[,计数器cnt自增1,若遇到],计数器cnt自减1。参见代码如下:

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
NestedInteger deserialize(string s) {
if (s.empty()) return NestedInteger();
if (s[0] != '[') return NestedInteger(stoi(s));
if (s.size() <= 2) return NestedInteger();
NestedInteger res;
int start = 1, cnt = 0;
for (int i = 1; i < s.size(); ++i) {
if (cnt == 0 && (s[i] == ',' || i == s.size() - 1)) {
res.add(deserialize(s.substr(start, i - start)));
start = i + 1;
} else if (s[i] == '[') ++cnt;
else if (s[i] == ']') --cnt;
}
return res;
}
};

Leetcode386. Lexicographical Numbers

Given an integer n, return all the numbers in the range [1, n] sorted in lexicographical order.

You must write an algorithm that runs in O(n) time and uses O(1) extra space.

Example 1:

1
2
Input: n = 13
Output: [1,10,11,12,13,2,3,4,5,6,7,8,9]

Example 2:

1
2
Input: n = 2
Output: [1,2]

给出一个整数 n ,将 1 到 n 的所有整数按照字典顺序排列。例:给出13,返回[1,10,11,12,13,2,3,4,5,6,7,8,9]。注意:尽量使用少的时间和空间复杂度,输入整数大小可能接近5,000,000。

设数组ans,用来存放最后返回的结果,设temp,表示当前应该存入的数字,初始为1.
按照字典序的规则,尽可能先把位数少的,顺序靠前的先放进去,那么应该先放1,10,100,1000。。。
即if(temp * 10 <= n ) temp *= 10;
然后当超出上限的时候,退回到上一位,开始每次加1,例如:1001,1002,1003。。。。一直到个位数到9为止,即1009,此时加1变成1100,把最右边连续的 0 去除,即回退到11,然后再开始乘10,再循环加个位,再回退。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
vector<int> lexicalOrder(int n) {
vector<int> res(n);
int temp = 1;

for (int i = 0; i < n; i ++) {
res[i] = temp;
if (temp * 10 <= n)
temp = temp * 10;
else {
if (temp >= n)
temp /= 10;
temp ++;
while(temp % 10 == 0)
temp /= 10;
}
}
return res;
}
};

Leetcode387. First Unique Character in a String

Given a string, find the first non-repeating character in it and return it’s index. If it doesn’t exist, return -1.

Examples:

1
2
s = "leetcode"
return 0.
1
2
s = "loveleetcode",
return 2.

用哈希表建立每个字符和其出现次数的映射,然后按顺序遍历字符串,找到第一个出现次数为1的字符,返回其位置即可。

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int firstUniqChar(string s) {
unordered_map<char, int> m;
for (char c : s) ++m[c];
for (int i = 0; i < s.size(); ++i) {
if(m[s[i]] == 1)
return i;
}
return -1;
}
};

Leetcode389. Find the Difference

Given two strings s and t which consist of only lowercase letters. String t is generated by random shuffling string s and then add one more letter at a random position. Find the letter that was added in t.

Example:

1
2
3
4
5
6
Input:
s = "abcd"
t = "abcde"
Output: e
Explanation:
'e' is the letter that was added.

一个hash表可以解决, 甚至可以用两个加起来然后异或

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
char findTheDifference(string s, string t) {
unordered_map<char, int> hash;
char ans;
for(auto ch: s) hash[ch]++;
for(auto ch: t)
if(--hash[ch]<0) ans = ch;
return ans;
}
};

Leetcode392. Is Subsequence

Given a string s and a string t, check if s is subsequence of t.

You may assume that there is only lower case English letters in both s and t. t is potentially a very long (length ~= 500,000) string, and s is a short string (<=100).

A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie, “ace” is a subsequence of “abcde” while “aec” is not).

Example 1:

1
2
s = "abc", t = "ahbgdc"
Return true.

Example 2:

1
2
s = "axc", t = "ahbgdc"
Return false.

Follow up:
If there are lots of incoming S, say S1, S2, … , Sk where k >= 1B, and you want to check one by one to see if T has its subsequence. In this scenario, how would you change your code?

检查一个串是不是另一个串的子串,找两个指针即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
bool isSubsequence(string s, string t) {
int ss,tt;
ss=0;
tt=0;
while(ss<s.length() && tt<t.length()){
if(s[ss]==t[tt]){
ss++;
tt++;
}
else
tt++;
}
if(ss==s.length()){
return true;
}
else
return false;
}
};

我们可以用俩个指针i,j分别指向字符串s,t. 算法描述如下: j指针一直往后走,碰到t[j]==s[i]的时候,说明匹配上了一个,将i++,否则i不加1,当t都走完的时候,这个时候,我们判断一下i指针是否走完了s字符串,如果走完了,说明也匹配上了,如果没有走完,那么就是没有匹配上,中间的过程,i提前等于s.length()的时候,也可以退出!此时的时间复杂度就是扫描一遍t,s串的线性复杂度O(t_len+s_len)。

算法正确性:

我们算法的关键点就在于,当s[i]=t[j]的时候,i和j都需要加1,那么这俩个指针加1的过程是否一定是对的呢?我们可以这样理解,当s=”abc”,t=”ahbgdc”,i=1,j=1的时候,我们只需要判断s后面的子串bc是否是t的子串hbgdc的子集(相当于分解为子问题),如果s的子串bc满足是t的子串hbgdc的子集,我们就返回true,如果不满足,我们就返回false。这样一步一步贪心下去就能保证算法正确性。

下面举一个简单例子走一遍算法帮助理解: s=”abc”, t=“ahbgdc” 首先i,j都为0,分别指向s,t俩个串开头. 第一步,当j=0<t_len=6的时候,进入循环,此时t[0]=a,s[0]=a,俩者相等,那么i,j都1,此时i=1,j=1,i!=(s_len=3),不跳出, j还是小于t_len。 此时t[1]=h ,s[1]=b,它们不相等,那么只有j加1,此时i=1,j=2,i!=3,不跳出. j还是小于t_len=6,此时t[2]=b,s[1]=b,它们相等,那么i,j分别加1,此时i=2,j=3,i!=3不跳出. 那么t[3]=g,s[2]=c,它们不相等,那么之后j加1,此时i=2,j=4,i!=3不跳出. 此时t[4]=d,s[2]=2,它们不相等,此时j加1,i=2,j=5,i!=3不跳出. j还是小于6,t[5]=c,s[2]=c,此时它们相等,i++i=3,此时等于s_lenres=true,跳出while循环,返回结果为true。

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
class Solution {
public:
bool isSubsequence(string s, string t) {
//俩个指针都往前走
if(s.length()==0)
return true; //空字符串是任何字符串的子串
int i = 0,j = 0;
bool res = false;
int s_len = s.length(),t_len = t.length();
while(j < t_len)
{
if(t[j] == s[i])
{
i++;
if(i==s_len)
{
res = true;
break;
}
}
j++; //无论t[j]是否等于s[i],j都要加1
}
return res;
}
};

Leetcode393. UTF-8 Validation

A character in UTF8 can be from 1 to 4 bytes long, subjected to the following rules:

For 1-byte character, the first bit is a 0, followed by its unicode code.
For n-bytes character, the first n-bits are all one’s, the n+1 bit is 0, followed by n-1 bytes with most significant 2 bits being 10.
This is how the UTF-8 encoding would work:

1
2
3
4
5
6
7
Char. number range  |        UTF-8 octet sequence
(hexadecimal) | (binary)
--------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

Given an array of integers representing the data, return whether it is a valid utf-8 encoding.

Note:
The input is an array of integers. Only the least significant 8 bits of each integer is used to store the data. This means each integer represents only 1 byte of data.

Example 1: data = [197, 130, 1], which represents the octet sequence: 11000101 10000010 00000001. Return true.It is a valid utf-8 encoding for a 2-bytes character followed by a 1-byte character.

Example 2: data = [235, 140, 4], which represented the octet sequence: 11101011 10001100 00000100. Return false. The first 3 bits are all one’s and the 4th bit is 0 means it is a 3-bytes character. The next byte is a continuation byte which starts with 10 and that’s correct. But the second continuation byte does not start with 10, so it is invalid.

这道题考察我们 UTF-8 编码,这种互联网所采用的通用的编码格式的产生是为了解决ASCII只能表示英文字符的局限性,和统一 Unicode 的实现方式。下面这段摘自维基百科 UTF-8 编码:

对于 UTF-8 编码中的任意字节B,如果B的第一位为0,则B独立的表示一个字符(ASCII 码);

  • 如果B的第一位为1,第二位为0,则B为一个多字节字符中的一个字节(非 ASCII 字符);
  • 如果B的前两位为1,第三位为0,则B为两个字节表示的字符中的第一个字节;
  • 如果B的前三位为1,第四位为0,则B为三个字节表示的字符中的第一个字节;
  • 如果B的前四位为1,第五位为0,则B为四个字节表示的字符中的第一个字节;

因此,对 UTF-8 编码中的任意字节,根据第一位,可判断是否为 ASCII 字符;根据前二位,可判断该字节是否为一个字符编码的第一个字节;根据前四位(如果前两位均为1),可确定该字节为字符编码的第一个字节,并且可判断对应的字符由几个字节表示;根据前五位(如果前四位为1),可判断编码是否有错误或数据传输过程中是否有错误。

如果是标识字节,先将其向右平移五位,如果得到 110,则说明后面跟了一个字节,否则向右平移四位,如果得到 1110,则说明后面跟了两个字节,否则向右平移三位,如果得到 11110,则说明后面跟了三个字节,否则向右平移七位,如果为1的话,说明是 10000000 这种情况,不能当标识字节,直接返回 false。在非标识字节中,向右平移六位,如果得到的不是 10,则说明不是以 10 开头的,直接返回 false,否则 cnt 自减1,成功完成遍历返回 true。

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
class Solution {
public:
bool validUtf8(vector<int>& data) {
int cnt = 0;
for(int i = 0; i < data.size(); i ++) {
if(cnt == 0) {
if((data[i] >> 7) == 0)
cnt = 0;
else if((data[i] >> 5) == 0b110)
cnt = 1;
else if((data[i] >> 4) == 0b1110)
cnt = 2;
else if((data[i] >> 3) == 0b11110)
cnt = 3;
else
return false;
}
else {
if((data[i] >> 6) != 0b10)
return false;
cnt --;
}
}
return cnt == 0;
}
};

Leetcode394. Decode String

Given an encoded string, return its decoded string.

The encoding rule is: k[encoded_string], where the encoded_string inside the square brackets is being repeated exactly k times. Note that k is guaranteed to be a positive integer.

You may assume that the input string is always valid; there are no extra white spaces, square brackets are well-formed, etc.

Furthermore, you may assume that the original data does not contain any digits and that digits are only for those repeat numbers, k. For example, there will not be input like 3a or 2[4].

Example 1:

1
2
Input: s = "3[a]2[bc]"
Output: "aaabcbc"

Example 2:

1
2
Input: s = "3[a2[c]]"
Output: "accaccacc"

Example 3:

1
2
Input: s = "2[abc]3[cd]ef"
Output: "abcabccdcdcdef"

这道题让我们把一个按一定规则编码后的字符串解码成其原来的模样,编码的方法很简单,就是把重复的字符串放在一个括号里,把重复的次数放在括号的前面,注意括号里面有可能会嵌套括号,这题可以用递归和迭代两种方法来解,我们首先来看递归的解法,把一个括号中的所有内容看做一个整体,一次递归函数返回一对括号中解码后的字符串。给定的编码字符串实际上只有四种字符,数字,字母,左括号,和右括号。那么我们开始用一个变量i从0开始遍历到字符串的末尾,由于左括号都是跟在数字后面,所以首先遇到的字符只能是数字或者字母,如果是字母,直接存入结果中,如果是数字,循环读入所有的数字,并正确转换,那么下一位非数字的字符一定是左括号,指针右移跳过左括号,对之后的内容调用递归函数求解,注意我们循环的停止条件是遍历到末尾和遇到右括号,由于递归调用的函数返回了子括号里解码后的字符串,而我们之前把次数也已经求出来了,那么循环添加到结果中即可,参见代码如下:

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
class Solution {
public:
string decodeString(string s) {
int i = 0;
return decode(s, i);
}
string decode(string s, int& i) {
string res = "";
int n = s.size();
while (i < n && s[i] != ']') {
if (s[i] < '0' || s[i] > '9') {
res += s[i++];
} else {
int cnt = 0;
while (s[i] >= '0' && s[i] <= '9') {
cnt = cnt * 10 + s[i++] - '0';
}
++i;
string t = decode(s, i);
++i;
while (cnt-- > 0) {
res += t;
}
}
}
return res;
}
};

我们也可以用迭代的方法写出来,当然需要用 stack 来辅助运算,我们用两个 stack,一个用来保存个数,一个用来保存字符串,我们遍历输入字符串,如果遇到数字,我们更新计数变量 cnt;如果遇到左括号,我们把当前 cnt 压入数字栈中,把当前t压入字符串栈中;如果遇到右括号时,我们取出数字栈中顶元素,存入变量k,然后给字符串栈的顶元素循环加上k个t字符串,然后取出顶元素存入字符串t中;如果遇到字母,我们直接加入字符串t中即可,参见代码如下:

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
class Solution {
public:
string decodeString(string s) {
string t = "";
stack<int> s_num;
stack<string> s_str;
int cnt = 0;
for (int i = 0; i < s.size(); ++i) {
if (s[i] >= '0' && s[i] <= '9') {
cnt = 10 * cnt + s[i] - '0';
} else if (s[i] == '[') {
s_num.push(cnt);
s_str.push(t);
cnt = 0; t.clear();
} else if (s[i] == ']') {
int k = s_num.top(); s_num.pop();
for (int j = 0; j < k; ++j) s_str.top() += t;
t = s_str.top(); s_str.pop();
} else {
t += s[i];
}
}
return s_str.empty() ? t : s_str.top();
}
};

我自己的代码:

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
class Solution {
public:

bool is_digit(char c) {
return '0' <= c && c <= '9';
}

bool is_char(char c) {
return 'a' <= c && c <= 'z';
}

string decodeString(string s) {
stack<int> st1;
stack<string> st2;
int len = s.length();
for (int i = 0; i < len;) {
if (is_digit(s[i])) {
int t = 0;
while(i < len && is_digit(s[i]))
t = t*10 + s[i++] - '0';
st1.push(t);
}
else if (s[i] == '[') {
int t = 0;
i ++;
while(i+t < len && is_char(s[i+t])) {
t ++;
}
st2.push(s.substr(i, t));
i += t;
}
else if (s[i] == ']') {
string temp;
int t = st1.top(); st1.pop();
string te = st2.top(); st2.pop();
while(t--)
temp = temp + te;
if (!st2.empty()) {te = st2.top(); st2.pop();st2.push(te + temp);}
else
st2.push(temp);
i ++;
}
else {
string te;
int t = 0;
while(i+t < len && is_char(s[i+t])) {
t ++;
}
if (!st2.empty()) {te = st2.top(); st2.pop();st2.push(te + s.substr(i, t));}
else
st2.push(s.substr(i, t));
i += t;
}
}
return st2.top();
}
};

Leetcode395. Longest Substring with At Least K Repeating Characters

Find the length of the longest substring T of a given string (consists of lowercase letters only) such that every character in T appears no less than k times.

Example 1:

1
2
3
4
5
6
7
Input:
s = "aaabb", k = 3

Output:
3

The longest substring is "aaa", as 'a' is repeated 3 times.

Example 2:

1
2
3
4
5
6
7
Input:
s = "ababbc", k = 2

Output:
5

The longest substring is "ababb", as 'a' is repeated 2 times and 'b' is repeated 3 times.

这道题给了我们一个字符串s和一个正整数k,让求一个最大子字符串并且每个字符必须至少出现k次。用 mask 就很好的解决了判断是否出现这个问题,由于字母只有 26 个,而整型 mask 有 32 位,足够用了,每一位代表一个字母,如果为1,表示该字母不够k次,如果为0就表示已经出现了k次。遍历字符串,对于每一个字符,都将其视为起点,然后遍历到末尾,增加 HashMap 中字母的出现次数,如果其小于k,将 mask 的对应位改为1,如果大于等于k,将 mask 对应位改为0。然后看 mask 是否为0,是的话就更新 res 结果,然后把当前满足要求的子字符串的起始位置j保存到 max_idx 中,等内层循环结束后,将外层循环变量i赋值为 max_idx+1,继续循环直至结束,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
int longestSubstring(string s, int k) {
int res = 0, i = 0, n = s.size();
while (i + k <= n) {
int m[26] = {0}, mask = 0, max_idx = i;
for (int j = i; j < n; ++j) {
int t = s[j] - 'a';
++m[t];
if (m[t] < k) mask |= (1 << t);
else mask &= (~(1 << t));
if (mask == 0) {
res = max(res, j - i + 1);
max_idx = j;
}
}
i = max_idx + 1;
}
return res;
}
};

虽然上面的方法很机智的使用了 mask 了标记某个子串的字母是否都超过了k,但仍然不是很高效,因为遍历了所有的子串,使得时间复杂度到达了平方级。来想想如何进行优化,因为题目中限定了字符串中只有字母,这意味着最多不同的字母数只有 26 个,最后满足题意的子串中的不同字母数一定是在 [1, 26] 的范围,这样就可以遍历这个范围,每次只找不同字母个数为 cnt,且每个字母至少重复k次的子串,来更新最终结果 res。这里让 cnt 从1遍历到 26,对于每个 cnt,都新建一个大小为 26 的数组 charCnt 来记录每个字母的出现次数,使用的思想其实还是滑动窗口 Sliding Window,使用两个变量 start 和 i 来分别标记窗口的左右边界,当右边界小于n时,进行 while 循环,需要一个变量 valid 来表示当前子串是否满足题意,初始化为 true,还需要一个变量 uniqueCnt 来记录子串中不同字母的个数。此时若 s[i] 这个字母在 charCnt 中的出现次数为0,说明遇到新字母了,uniqueCnt 自增1,同时把该字母的映射值加1。此时由于 uniqueCnt 变大了,有可能会超过之前限定了 cnt,所以这里用一个 while 循环,条件是当 uniqueCnt 大于 cnt ,此时应该收缩滑动窗口的左边界,那么对应的左边界上的字母的映射值要自减1,若减完后为0了,则 uniqueCnt 自减1,注意这里一会后加,一会先减的操作,不要搞晕了。当 uniqueCnt 没超过 cnt 的时候,此时还要看当前窗口中的每个字母的出现次数是否都大于等于k,遇到小于k的字母,则直接 valid 标记为 false 即可。最终若 valid 还是 true,则表示滑动窗口内的字符串是符合题意的,用其长度来更新结果 res 即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
int longestSubstring(string s, int k) {
int res = 0, n = s.size();
for (int cnt = 1; cnt <= 26; ++cnt) {
int start = 0, i = 0, uniqueCnt = 0;
vector<int> charCnt(26);
while (i < n) {
bool valid = true;
if (charCnt[s[i++] - 'a']++ == 0) ++uniqueCnt;
while (uniqueCnt > cnt) {
if (--charCnt[s[start++] - 'a'] == 0) --uniqueCnt;
}
for (int j = 0; j < 26; ++j) {
if (charCnt[j] > 0 && charCnt[j] < k) valid = false;
}
if (valid) res = max(res, i - start);
}
}
return res;
}
};

下面这种解法用的分治法 Divide and Conquer 的思想,看起来简洁了不少,但是个人感觉比较难想,这里使用了一个变量 max_idx,是用来分割子串的,实现开始统计好了字符串s的每个字母出现的次数,然后再次遍历每个字母,若当前字母的出现次数小于k了,则从开头到前一个字母的范围内的子串可能是满足题意的,还需要对前面的子串进一步调用递归,用返回值来更新当前结果 res,此时变量 ok 标记为 false,表示当前整个字符串s是不符合题意的,因为有字母出现次数小于k,此时 max_idx 更新为 i+1,表示再从新的位置开始找下一个出现次数小于k的字母的位置,可以对新的范围的子串继续调用递归。当 for 循环结束后,若 ok 是 true,说明整个s串都是符合题意的,直接返回n,否则要对 [max_idx, n-1] 范围内的子串再次调用递归,因为这个区间的子串也可能是符合题意的,还是用返回值跟结果 res 比较,谁大就返回谁,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int longestSubstring(string s, int k) {
int n = s.size(), max_idx = 0, res = 0;
int m[128] = {0};
bool ok = true;
for (char c : s) ++m[c];
for (int i = 0; i < n; ++i) {
if (m[s[i]] < k) {
res = max(res, longestSubstring(s.substr(max_idx, i - max_idx), k));
ok = false;
max_idx = i + 1;
}
}
return ok ? n : max(res, longestSubstring(s.substr(max_idx, n - max_idx), k));
}
};

Leetcode397. Integer Replacement

Given a positive integer n and you can do operations as follow:

  • If n is even, replace n with n /2.
  • If n is odd, you can replace n with either n + 1 or n - 1.

What is the minimum number of replacements needed for n to become 1?

Example 1:

1
2
3
4
5
6
7
8
Input:
8

Output:
3

Explanation:
8 -> 4 -> 2 -> 1

Example 2:

1
2
3
4
5
6
7
8
9
10
Input:
7

Output:
4

Explanation:
7 -> 8 -> 4 -> 2 -> 1
or
7 -> 6 -> 3 -> 2 -> 1

这道题给了我们一个整数n,然后让我们通过变换变为1,如果n是偶数,我们变为n/2,如果是奇数,我们可以变为n+1或n-1,让我们求变为1的最少步骤。那么一看道题的要求,就会感觉应该用递归很合适,我们直接按照规则写出递归即可,注意由于有n+1的操作,所以当n为INT_MAX的时候,就有可能溢出,所以我们可以先将n转为长整型,然后再进行运算,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
int integerReplacement(int n) {
if (n == 1) return 0;
if (n % 2 == 0) return 1 + integerReplacement(n / 2);
else {
long long t = n;
return 2 + min(integerReplacement((t + 1) / 2), integerReplacement((t - 1) / 2));
}
}
};

Leetcode398. Random Pick Index

Given an integer array nums with possible duplicates, randomly output the index of a given target number. You can assume that the given target number must exist in the array.

Implement the Solution class:

  • Solution(int[] nums) Initializes the object with the array nums.
  • int pick(int target) Picks a random index i from nums where nums[i] == target. If there are multiple valid i’s, then each index should have an equal probability of returning.

Example 1:

1
2
3
4
5
Input
["Solution", "pick", "pick", "pick"]
[[[1, 2, 3, 3, 3]], [3], [1], [3]]
Output
[null, 4, 0, 2]

Explanation

1
2
3
4
Solution solution = new Solution([1, 2, 3, 3, 3]);
solution.pick(3); // It should return either index 2, 3, or 4 randomly. Each index should have equal probability of returning.
solution.pick(1); // It should return 0. Since in the array only nums[0] is equal to 1.
solution.pick(3); // It should return either index 2, 3, or 4 randomly. Each index should have equal probability of returning.

这道题指明了我们不能用太多的空间,那么省空间的随机方法只有水塘抽样Reservoir Sampling了,LeetCode之前有过两道需要用这种方法的题目Shuffle an Array和Linked List Random Node。那么如果了解了水塘抽样,这道题就不算一道难题了,我们定义两个变量,计数器cnt和返回结果res,我们遍历整个数组,如果数组的值不等于target,直接跳过;如果等于target,计数器加1,然后我们在[0,cnt)范围内随机生成一个数字,如果这个数字是0,我们将res赋值为i即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
Solution(vector<int> nums): v(nums) {}

int pick(int target) {
int cnt = 0, res = -1;
for (int i = 0; i < v.size(); ++i) {
if (v[i] != target) continue;
++cnt;
if (rand() % cnt == 0) res = i;
}
return res;
}
private:
vector<int> v;
};

Leetcode399. Evaluate Division

You are given an array of variable pairs equations and an array of real numbers values, where equations[i] = [Ai, Bi] and values[i] represent the equation Ai / Bi = values[i]. Each Ai or Bi is a string that represents a single variable.

You are also given some queries, where queries[j] = [Cj, Dj] represents the jth query where you must find the answer for Cj / Dj = ?.

Return the answers to all queries. If a single answer cannot be determined, return -1.0.

Note: The input is always valid. You may assume that evaluating the queries will not result in division by zero and that there is no contradiction.

Example 1:

1
2
3
4
5
6
Input: equations = [["a","b"],["b","c"]], values = [2.0,3.0], queries = [["a","c"],["b","a"],["a","e"],["a","a"],["x","x"]]
Output: [6.00000,0.50000,-1.00000,1.00000,-1.00000]
Explanation:
Given: a / b = 2.0, b / c = 3.0
queries are: a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ?
return: [6.0, 0.5, -1.0, 1.0, -1.0 ]

这道题已知条件中给了一些除法等式,然后给了另外一些除法等式,问我们能不能根据已知条件求出结果,不能求出来的用-1表示。问题本身是很简单的数学问题,但是写代码来自动实现就需要我们用正确的数据结构和算法,通过观察题目中的例子,我们可以看出如果需要分析的除法式的除数和被除数如果其中任意一个没有在已知条件中出现过,那么返回结果-1,所以我们在分析已知条件的时候,可以使用set来记录所有出现过的字符串,然后我们在分析其他除法式的时候,可以使用递归来做。通过分析得出,不能直接由已知条件得到的情况主要有下面三种:

  • 已知: a / b = 2, b / c = 3, 求 a / c
  • 已知: a / c = 2, b / c = 3, 求 a / b
  • 已知: a / b = 2, a / c = 3, 求 b / c

虽然有三种情况,但其实后两种情况都可以转换为第一种情况,对于每个已知条件,我们将其翻转一下也存起来,那么对于对于上面美中情况,就有四个已知条件了:

  • 已知: a / b = 2 ,b / a = 1/2, b / c = 3 ,c / b = 1/3,求 a / c
  • 已知: a / c = 2 ,c / a = 1/2,b / c = 3, c / b = 1/3 ,求 a / b
  • 已知: a / b = 2, b / a = 1/2 , a / c = 3 ,c / a = 1/3,求 b / c

我们发现,第二种和第三种情况也能转化为第一种情况,只需将上面加粗的两个条件相乘即可。对于每一个需要解析的表达式,我们需要一个HashSet来记录已经访问过的表达式,然后对其调用递归函数。在递归函数中,我们在HashMap中快速查找该表达式,如果跟某一个已知表达式相等,直接返回结果。如果没有的话,那么就需要间接寻找了,我们在HashMap中遍历跟解析式中分子相同的已知条件,跳过之前已经遍历过的,否则就加入visited数组,然后再对其调用递归函数,如果返回值是正数,则乘以当前已知条件的值返回,就类似上面的情况一,相乘以后b就消掉了。如果已知找不到解,最后就返回-1。

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
class Solution {
public:
vector<double> calcEquation(vector<pair<string, string>> equations, vector<double>& values, vector<pair<string, string>> queries) {
vector<double> res;
for (int i = 0; i < equations.size(); ++i) {
m[equations[i].first][equations[i].second] = values[i];
m[equations[i].second][equations[i].first] = 1.0 / values[i];
}
for (auto query : queries) {
unordered_set<string> visited;
double t = helper(query.first, query.second, visited);
res.push_back((t > 0.0) ? t : -1);
}
return res;
}
double helper(string up, string down, unordered_set<string>& visited) {
if (m[up].count(down)) return m[up][down];
for (auto a : m[up]) {
if (visited.count(a.first)) continue;
visited.insert(a.first);
double t = helper(a.first, down, visited);
if (t > 0.0) return t * a.second;
}
return -1.0;
}

private:
unordered_map<string, unordered_map<string, double>> m;
};

Leetcode400. Nth Digit

Given an integer n, return the nth digit of the infinite integer sequence [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, …].

Example 1:

1
2
Input: n = 3
Output: 3

Example 2:

1
2
3
Input: n = 11
Output: 0
Explanation: The 11th digit of the sequence 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ... is a 0, which is part of the number 10.

自然数序列看成一个长字符串,问我们第N位上的数字是什么。那么这道题的关键就是要找出第N位所在的数字,然后可以把数字转为字符串,这样直接可以访问任何一位。那么我们首先来分析自然数序列和其位数的关系,前九个数都是1位的,然后10到99总共90个数字都是两位的,100到999这900个数都是三位的,那么这就很有规律了,我们可以定义个变量cnt,初始化为9,然后每次循环扩大10倍,再用一个变量len记录当前循环区间数字的位数,另外再需要一个变量start用来记录当前循环区间的第一个数字,我们n每次循环都减去len*cnt (区间总位数),当n落到某一个确定的区间里了,那么(n-1)/len就是目标数字在该区间里的坐标,加上start就是得到了目标数字,然后我们将目标数字start转为字符串,(n-1)%len就是所要求的目标位,最后别忘了考虑int溢出问题,我们干脆把所有变量都申请为长整型的好了,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int findNthDigit(int n) {
long long digit = 1, start = 1, count = 9;
while(n > count) {
n -= count;
digit += 1;
start *= 10;
count = 9 * digit * start;
}
int num = start + (n - 1) / digit;
return (to_string(num))[(n-1)%digit] - '0';
}
};

Leetcode1502. Can Make Arithmetic Progression From Sequence

Given an array of numbers arr. A sequence of numbers is called an arithmetic progression if the difference between any two consecutive elements is the same.

Return true if the array can be rearranged to form an arithmetic progression, otherwise, return false.

Example 1:

1
2
3
Input: arr = [3,5,1]
Output: true
Explanation: We can reorder the elements as [1,3,5] or [5,3,1] with differences 2 and -2 respectively, between each consecutive elements.

Example 2:

1
2
3
Input: arr = [1,2,4]
Output: false
Explanation: There is no way to reorder the elements to obtain an arithmetic progression.

判断是否可以组成等差数列,将数组排序后,比较两两数字差是否一致。

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
bool canMakeArithmeticProgression(vector<int>& arr) {
sort(arr.begin(), arr.end());
int cha = arr[1] - arr[0];
for(int i = 2; i < arr.size(); i ++)
if(arr[i] - arr[i-1] != cha)
return false;
return true;
}
};

Leetcode1507. Reformat Date

Given a date string in the form Day Month Year, where:

  • Day is in the set {“1st”, “2nd”, “3rd”, “4th”, …, “30th”, “31st”}.
  • Month is in the set {“Jan”, “Feb”, “Mar”, “Apr”, “May”, “Jun”, “Jul”, “Aug”, “Sep”, “Oct”, “Nov”, “Dec”}.
  • Year is in the range [1900, 2100].

Convert the date string to the format YYYY-MM-DD, where:

  • YYYY denotes the 4 digit year.
  • MM denotes the 2 digit month.
  • DD denotes the 2 digit day.

Example 1:

1
2
Input: date = "20th Oct 2052"
Output: "2052-10-20"

Example 2:

1
2
Input: date = "6th Jun 1933"
Output: "1933-06-06"

时间格式转换,很麻烦。

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
class Solution {
public:
string reformatDate(string date) {
string res;
int count = 0;
int day, month, year;
vector<string> mon{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
for(int i = 0; i < date.length();) {
if(date[i] == ' ') {
count ++;
i ++;
}
else if(count == 0) {
day = 0;
while('0' <= date[i] && date[i] <= '9') {
day = day * 10 + (date[i] - '0');
i ++;
}
i += 2;
}
else if(count == 1) {
string months = "";
int ii;
while(date[i] != ' ')
months += date[i ++];
for(ii = 0; ii < mon.size(); ii ++)
if(months == mon[ii])
break;
month = ii + 1;
}
else if(count == 2) {
year = 0;
while('0' <= date[i] && date[i] <= '9') {
year = year * 10 + (date[i] - '0');
i ++;
}
}
}
res = to_string(year) + "-" + (month >= 10 ? "" : "0") + to_string(month) + "-" + (day >= 10 ? "" : "0") + to_string(day);
return res;
}
};

Leetcode1512. Number of Good Pairs

Given an array of integers nums. A pair (i,j) is called good if nums[i] == nums[j] and i < j. Return the number of good pairs.

Example 1:

1
2
3
Input: nums = [1,2,3,1,1,3]
Output: 4
Explanation: There are 4 good pairs (0,3), (0,4), (3,4), (2,5) 0-indexed.

Example 2:

1
2
3
Input: nums = [1,1,1,1]
Output: 6
Explanation: Each pair in the array are good.

遍历一遍即可。

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
int numIdenticalPairs(vector<int>& nums) {
int res = 0;
for(int i = 0; i < nums.size(); i ++)
for(int j = i + 1; j < nums.size(); j ++)
if(nums[i] == nums[j])
res ++;
return res;
}
};

Leetcode1530. Number of Good Leaf Nodes Pairs

You are given the root of a binary tree and an integer distance. A pair of two different leaf nodes of a binary tree is said to be good if the length of the shortest path between them is less than or equal to distance.

Return the number of good leaf node pairs in the tree.

Example 1:

1
2
3
Input: root = [1,2,3,null,4], distance = 3
Output: 1
Explanation: The leaf nodes of the tree are 3 and 4 and the length of the shortest path between them is 3. This is the only good pair.

Example 2:

1
2
3
Input: root = [1,2,3,4,5,6,7], distance = 3
Output: 2
Explanation: The good pairs are [4,5] and [6,7] with shortest path = 2. The pair [4,6] is not good because the length of ther shortest path between them is 4.

Example 3:

1
2
3
Input: root = [7,1,4,6,null,5,3,null,null,null,null,null,2], distance = 3
Output: 1
Explanation: The only good pair is [2,5].

Constraints:

  • The number of nodes in the tree is in the range [1, 210].
  • 1 <= Node.val <= 100
  • 1 <= distance <= 10

如果二叉树中两个 叶 节点之间的 最短路径长度 小于或者等于 distance ,那它们就可以构成一组 好叶子节点对。这个题让找好叶子节点对的数量。注意到这个题的数据量很小,可以遍历找到。

因为是找左右子树的数量,所以可以对左右子树分别算一个数组,数组里计算了距离本节点距离为i的节点有几个,例如如果这个点是叶子,那cnt数组中只有cnt[0]为1。在算这两个节点的距离时,如下边的代码所示,要s+1 + t+1,s是node的左子树到node->left的距离,t是node的右子树到node->right的距离。起始条件是0,因为是叶子到node的子节点的距离,是可能为0的;而终止条件应该是d-1,因为两端的叶子的路径是经过node的,不能算上根节点。

计算结果时把左右子树的数组对应值乘起来。

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:
int ret;
int d;

vector<int> dfs(TreeNode* node) {
vector<int> cnt(d);
if (!node)
return cnt;

vector<int> lcnt = dfs(node->left);
vector<int> rcnt = dfs(node->right);

for (int s = 0; s <= d-2; s ++)
for (int t = 0; t <= d-2; t ++) {
if (s + t + 2 > d)
continue;
ret += lcnt[s] * rcnt[t];
}
for (int i = 0; i < d-1; i ++)
cnt[i+1] = lcnt[i] + rcnt[i];

if (!node->left && !node->right)
cnt[0] = 1;
return cnt;
}

int countPairs(TreeNode* root, int distance) {
ret = 0;
d = distance;

dfs(root);
return ret;
}
};

另一种做法:

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
class Solution {
public:
int countPairs(TreeNode* root, int distance) {
numPairs = 0;
targetDistance = distance;
countPairsHelper(root, 1);
return numPairs;
}

private:
int numPairs;
int targetDistance;

vector<int> countPairsHelper(TreeNode* root, int level) {
if (root == NULL)
return {};
if (root->left == NULL && root->right == NULL)
return {level};

vector<int> leftLeaves = countPairsHelper(root->left, level + 1);
vector<int> rightLeaves = countPairsHelper(root->right, level + 1);
for (auto left: leftLeaves) {
for (auto right: rightLeaves) {
if (left + right - 2 * level <= targetDistance)
numPairs++;
}
}

for (auto right: rightLeaves)
leftLeaves.push_back(right);
return leftLeaves;
}
};

Leetcode1672. 最富有客户的资产总量

给你一个 m x n 的整数网格 accounts ,其中 accounts[i][j] 是第 i 位客户在第 j 家银行托管的资产数量。返回最富有客户所拥有的 资产总量 。

客户的 资产总量 就是他们在各家银行托管的资产数量之和。最富有客户就是 资产总量 最大的客户。

示例 1:

1
2
3
4
5
6
输入:accounts = [[1,2,3],[3,2,1]]
输出:6
解释:
第 1 位客户的资产总量 = 1 + 2 + 3 = 6
第 2 位客户的资产总量 = 3 + 2 + 1 = 6
两位客户都是最富有的,资产总量都是 6 ,所以返回 6 。

示例 2:

1
2
3
4
5
6
7
输入:accounts = [[1,5],[7,3],[3,5]]
输出:10
解释:
第 1 位客户的资产总量 = 6
第 2 位客户的资产总量 = 10
第 3 位客户的资产总量 = 8
第 2 位客户是最富有的,资产总量是 10

示例 3:

1
2
输入:accounts = [[2,8,7],[7,1,3],[1,9,5]]
输出:17
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int maximumWealth(vector<vector<int>>& accounts) {
int res = 0;
for (auto& account : accounts) {
int sum = 0;
for (int i : account)
sum += i;
res = max(res, sum);
}
return res;
}
};

Leetcode1403. Minimum Subsequence in Non-Increasing Order

Given the array nums, obtain a subsequence of the array whose sum of elements is strictly greater than the sum of the non included elements in such subsequence.

If there are multiple solutions, return the subsequence with minimum size and if there still exist multiple solutions, return the subsequence with the maximum total sum of all its elements. A subsequence of an array can be obtained by erasing some (possibly zero) elements from the array.

Note that the solution with the given constraints is guaranteed to be unique. Also return the answer sorted in non-increasing order.

Example 1:

1
2
3
Input: nums = [4,3,10,9,8]
Output: [10,9]
Explanation: The subsequences [10,9] and [10,8] are minimal such that the sum of their elements is strictly greater than the sum of elements not included, however, the subsequence [10,9] has the maximum total sum of its elements.

给你一个数组 nums,请你从中抽取一个子序列,满足该子序列的元素之和 严格 大于未包含在该子序列中的各元素之和。如果存在多个解决方案,只需返回 长度最小 的子序列。如果仍然有多个解决方案,则返回 元素之和最大 的子序列。与子数组不同的地方在于,「数组的子序列」不强调元素在原数组中的连续性,也就是说,它可以通过从数组中分离一些(也可能不分离)元素得到。

找出这样的最小子序列,肯定从数组中最大数开始一个一个提取,直到子序列的和大于剩余在原数组的元素之和。关键在于怎么提取数组中的最大数。可以将数组排序。注意是严格大于!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
vector<int> minSubsequence(vector<int>& nums) {
vector<int> res;
sort(nums.begin(), nums.end());
int sum = 0;
for(int i : nums)
sum += i;
sum /= 2;
for(int i = nums.size()-1; i >= 0; i --) {
if(sum < 0)
break;
sum -= nums[i];
res.push_back(nums[i]);
}
return res;
}
};

Leetcode1408. String Matching in an Array

Given an array of string words. Return all strings in words which is substring of another word in any order. String words[i] is substring of words[j], if can be obtained removing some characters to left and/or right side of words[j].

Example 1:

1
2
3
4
Input: words = ["mass","as","hero","superhero"]
Output: ["as","hero"]
Explanation: "as" is substring of "mass" and "hero" is substring of "superhero".
["hero","as"] is also a valid answer.

Example 2:

1
2
3
Input: words = ["leetcode","et","code"]
Output: ["et","code"]
Explanation: "et", "code" are substring of "leetcode".

找到字符串数组中哪些字符串是另一些字符串的子串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:

static bool cmp(string a, string b) {
return a.length() < b.length();
}

vector<string> stringMatching(vector<string>& words) {
set<string> res;
sort(words.begin(), words.end(), cmp);
int len = words.size();
for(int i = 0; i < len; i ++)
for(int j = i + 1; j < len; j ++)
if(words[j].find(words[i]) != -1)
res.insert(words[i]);
return vector<string>(res.begin(), res.end());
}
};

Leetcode1413. Minimum Value to Get Positive Step by Step Sum

Given an array of integers nums, you start with an initial positive value startValue. In each iteration, you calculate the step by step sum of startValue plus elements in nums (from left to right). Return the minimum positive value of startValue such that the step by step sum is never less than 1.

Example 1:

1
2
3
4
5
6
7
8
9
10
Input: nums = [-3,2,-3,4,2]
Output: 5
Explanation: If you choose startValue = 4, in the third iteration your step by step sum is less than 1.
step by step sum
startValue = 4 | startValue = 5 | nums
(4 -3 ) = 1 | (5 -3 ) = 2 | -3
(1 +2 ) = 3 | (2 +2 ) = 4 | 2
(3 -3 ) = 0 | (4 -3 ) = 1 | -3
(0 +4 ) = 4 | (1 +4 ) = 5 | 4
(4 +2 ) = 6 | (5 +2 ) = 7 | 2

Example 2:

1
2
3
Input: nums = [1,2]
Output: 1
Explanation: Minimum start value should be positive.

求startValue使得从左到右加nums里的数的和不小于1,其实就是求个前缀和。从左往右依次累加nums的和,如果遇到和为负数的情况,只要保证 startValue = min(负数和) + 1 即可;如果全为正数,startValue = 1。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int minStartValue(vector<int>& nums) {
int minn = 99999, sum = 0;
for(int i = 0; i < nums.size(); i ++) {
sum += nums[i];
minn = min(minn, sum);
}
if(minn < 0)
return abs(minn) + 1;
return 1;
}
};

Leetcode1417. Reformat The String

Given alphanumeric string s. (Alphanumeric string is a string consisting of lowercase English letters and digits). You have to find a permutation of the string where no letter is followed by another letter and no digit is followed by another digit. That is, no two adjacent characters have the same type.

Return the reformatted string or return an empty string if it is impossible to reformat the string.

Example 1:

1
2
3
Input: s = "a0b1c2"
Output: "0a1b2c"
Explanation: No two adjacent characters have the same type in "0a1b2c". "a0b1c2", "0a1b2c", "0c2a1b" are also valid permutations.

Example 2:

1
2
3
Input: s = "leetcode"
Output: ""
Explanation: "leetcode" has only characters so we cannot separate them by digits.

给你一个混合了数字和字母的字符串 s,其中的字母均为小写英文字母。如果letter的个数和digit的个数符合合并要求的话,我们要把长度较长的放在首位。比如说”111”和”aa”合并的话,结果一定是”1a1a1”,否则如果’a’打头的话,没有办法将所有的1分割开。

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
class Solution {
public:

string reformat(string s) {
string res;
vector<char> a, b;
for(int i = 0; i < s.length(); i ++) {
if('0' <= s[i] && s[i] <= '9')
a.push_back(s[i]);
else
b.push_back(s[i]);
}
int lena = a.size(), lenb = b.size();
int abss = lena - lenb;
cout<<abss<<endl;
if(abs(abss) > 1)
return "";
int k = 0;
if(lena > lenb) {
k = 0;
for(char c : a) {
s[k] = c;
k += 2;
}
k = 1;
for(char c : b) {
s[k] = c;
k += 2;
}
}
else {
k = 0;
for(char c : b) {
s[k] = c;
k += 2;
}
k = 1;
for(char c : a) {
s[k] = c;
k += 2;
}
}

return s;
}
};

Leetcode1418. Display Table of Food Orders in a Restaurant

Given the array orders, which represents the orders that customers have done in a restaurant. More specifically orders[i]=[customerNamei,tableNumberi,foodItemi] where customerNamei is the name of the customer, tableNumberi is the table customer sit at, and foodItemi is the item customer orders.

Return the restaurant’s “display table”. The “display table” is a table whose row entries denote how many of each food item each table ordered. The first column is the table number and the remaining columns correspond to each food item in alphabetical order. The first row should be a header whose first column is “Table”, followed by the names of the food items. Note that the customer names are not part of the table. Additionally, the rows should be sorted in numerically increasing order.

Example 1:

1
2
3
4
5
6
7
8
9
10
11
Input: orders = [["David","3","Ceviche"],["Corina","10","Beef Burrito"],["David","3","Fried Chicken"],["Carla","5","Water"],["Carla","5","Ceviche"],["Rous","3","Ceviche"]]
Output: [["Table","Beef Burrito","Ceviche","Fried Chicken","Water"],["3","0","2","1","0"],["5","0","1","0","1"],["10","1","0","0","0"]]
Explanation:
The displaying table looks like:
Table,Beef Burrito,Ceviche,Fried Chicken,Water
3 ,0 ,2 ,1 ,0
5 ,0 ,1 ,0 ,1
10 ,1 ,0 ,0 ,0
For the table 3: David orders "Ceviche" and "Fried Chicken", and Rous orders "Ceviche".
For the table 5: Carla orders "Water" and "Ceviche".
For the table 10: Corina orders "Beef Burrito".

找到好的数据存储结构就行了。

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
class Solution {
public:
vector<vector<string>> displayTable(vector<vector<string>>& orders) {
vector<vector<string> > res(1);
map<int, map<string, int>> mp;

for(vector<string> a : orders) {
if(find(res[0].begin(), res[0].end(), a[2]) == res[0].end())
res[0].push_back(a[2]);
mp[stoi(a[1])][a[2]] ++;
}
sort(res[0].begin(), res[0].end());

for(auto it = mp.begin(); it != mp.end(); it ++) {
for(string a : res[0])
if(it->second.find(a) == it->second.end())
it->second.insert(pair<string, int>(a, 0));
}
res[0].insert(res[0].begin(), "Table");

for(auto it = mp.begin(); it != mp.end(); it ++) {
vector<string> temp;
temp.push_back(to_string(it->first));
for(auto itt = it->second.begin(); itt != it->second.end(); itt ++) {
temp.push_back(to_string(itt->second));
}
res.push_back(temp);
}
return res;
}
};

Leetcode1422. Maximum Score After Splitting a String

Given a string s of zeros and ones, return the maximum score after splitting the string into two non-empty substrings (i.e. left substring and right substring).

The score after splitting a string is the number of zeros in the left substring plus the number of ones in the right substring.

Example 1:

1
2
3
4
5
6
7
8
9
Input: s = "011101"
Output: 5
Explanation:
All possible ways of splitting s into two non-empty substrings are:
left = "0" and right = "11101", score = 1 + 4 = 5
left = "01" and right = "1101", score = 1 + 3 = 4
left = "011" and right = "101", score = 1 + 2 = 3
left = "0111" and right = "01", score = 1 + 1 = 2
left = "01110" and right = "1", score = 2 + 1 = 3

Example 2:

1
2
3
Input: s = "00111"
Output: 5
Explanation: When left = "00" and right = "111", we get the maximum score = 2 + 3 = 5

Example 3:

1
2
Input: s = "1111"
Output: 3

求出左侧 0 数量和右侧 1 数量之和最多的情况,遍历一次,每次更新最大值,注意要保证字符串始终被切分为 2 个子字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int maxScore(string s) {
int left = 0, right = 0, res = 0;
for(int i = 0; i < s.length(); i ++)
if(s[i] == '1')
right ++;
for(int i = 0; i < s.length()-1; i ++) {
if(s[i] == '1') {
res = max(res, left + right -1);
right --;
}
else {
res = max(res, left + right + 1);
left ++;
}
}
return res;
}
};

Leetcode1424. Diagonal Traverse II

Given a list of lists of integers, nums, return all elements of nums in diagonal order as shown in the below images.

Example 1:

1
2
Input: nums = [[1,2,3],[4,5,6],[7,8,9]]
Output: [1,4,2,7,5,3,8,6,9]

Example 2:

1
2
Input: nums = [[1,2,3,4,5],[6,7],[8],[9,10,11],[12,13,14,15,16]]
Output: [1,6,2,8,7,3,9,4,12,10,5,13,11,14,15,16]

按照对角线的指引输出数字,这个题本来是按照模拟的方法做,但是特殊的case太多,所以参考大佬的做法,直接记下来这个数字在结果数组中的位置,用i+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
class Solution {
public:

static bool cmp(pair<int, pair<int, int>> a, pair<int, pair<int, int>> b) {
if(a.first != b.first)
return a.first < b.first;
else
return a.second.first > b.second.first;
}

vector<int> findDiagonalOrder(vector<vector<int>>& nums) {
vector<int> res;
vector<pair<int, pair<int, int>>> pa;
for(int i = 0; i < nums.size(); i ++)
for(int j = 0; j < nums[i].size(); j ++) {
pa.push_back(make_pair(i+j, make_pair(i, nums[i][j])));
}
sort(pa.begin(), pa.end(), cmp);
for(auto a : pa) {
res.push_back(a.second.second);
}
return res;
}
};

Leetcode1425. Constrained Subsequence Sum

Given an integer array nums and an integer k, return the maximum sum of a non-empty subsequence of that array such that for every two consecutive integers in the subsequence, nums[i] and nums[j], where i < j, the condition j - i <= k is satisfied.

A subsequence of an array is obtained by deleting some number of elements (can be zero) from the array, leaving the remaining elements in their original order.

Example 1:

1
2
3
Input: nums = [10,2,-10,5,20], k = 2
Output: 37
Explanation: The subsequence is [10, 2, 5, 20].

Example 2:

1
2
3
Input: nums = [-1,-2,-3], k = 1
Output: -1
Explanation: The subsequence must be non-empty, so we choose the largest number.

Example 3:

1
2
3
Input: nums = [10,-2,-10,-5,20], k = 2
Output: 23
Explanation: The subsequence is [10, -2, -5, 20].

思路:动态规划 dp[i] = Max(nums[i], nums[i] + dp[i + j]) 其中0 < j <= k,但是直接遍历dp[i + j]会超时,所以要维护一个能快速查找dp[i + 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
27
28
29
30
class Solution {
public:
int constrainedSubsetSum(vector<int>& a, int k) {
// 是一个结合了滑动窗口最大值的dp
// 使用滑动窗口计算最大值,再使用dp求最大值
int n = a.size();
deque<int> d;
int ret = -10000;

vector<int> f(n);
// f[i] 是考虑前i个元素且选中第i个元素时构造的最大子序列和
for (int i = 0; i < n; i ++) {
int l = i - k - 1; // 当前窗口的范围
if (!d.empty() && d.front() == l)
d.pop_front(); // 先把上一个窗口的左端点踢出来

f[i] = a[i];
if (!d.empty())
f[i] = max(f[i], f[d.front()] + a[i]);

while(!d.empty() && f[d.back()] <= f[i])
d.pop_back();

d.push_back(i);

ret = max(ret, f[i]);
}
return ret;
}
};

Leetcode1431. Kids With the Greatest Number of Candies

Given the array candies and the integer extraCandies, where candies[i] represents the number of candies that the ith kid has. For each kid check if there is a way to distribute extraCandies among the kids such that he or she can have the greatest number of candies among them. Notice that multiple kids can have the greatest number of candies.

Example 1:

1
2
3
4
5
6
7
8
Input: candies = [2,3,5,1,3], extraCandies = 3
Output: [true,true,true,false,true]
Explanation:
Kid 1 has 2 candies and if he or she receives all extra candies (3) will have 5 candies --- the greatest number of candies among the kids.
Kid 2 has 3 candies and if he or she receives at least 2 extra candies will have the greatest number of candies among the kids.
Kid 3 has 5 candies and this is already the greatest number of candies among the kids.
Kid 4 has 1 candy and even if he or she receives all extra candies will only have 4 candies.
Kid 5 has 3 candies and if he or she receives at least 2 extra candies will have the greatest number of candies among the kids.

给你一个数组 candies 和一个整数 extraCandies ,其中 candies[i] 代表第 i 个孩子拥有的糖果数目。对每一个孩子,检查是否存在一种方案,将额外的 extraCandies 个糖果分配给孩子们之后,此孩子有 最多 的糖果。注意,允许有多个孩子同时拥有最多的糖果数目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
vector<bool> kidsWithCandies(vector<int>& candies, int extraCandies) {
vector<bool> res(candies.size(), false);
int maxx = -1;
for(int i = 0; i < candies.size(); i ++)
if(maxx < candies[i])
maxx = candies[i];
for(int i = 0; i < candies.size(); i ++)
if(candies[i] + extraCandies >= maxx)
res[i] = true;
return res;
}
};

Leetcode1436. Destination City

You are given the array paths, where paths[i] = [cityAi, cityBi] means there exists a direct path going from cityAi to cityBi. Return the destination city, that is, the city without any path outgoing to another city.

It is guaranteed that the graph of paths forms a line without any loop, therefore, there will be exactly one destination city.

Example 1:

1
2
3
Input: paths = [["London","New York"],["New York","Lima"],["Lima","Sao Paulo"]]
Output: "Sao Paulo"
Explanation: Starting at "London" city you will reach "Sao Paulo" city which is the destination city. Your trip consist of: "London" -> "New York" -> "Lima" -> "Sao Paulo".

得到特征:所有destination都出现一次且只出现在list的第二位,第一遍把独特的list第二个city找出来,第二遍遍历把独一无二的找出来就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
string destCity(vector<vector<string>>& paths) {
map<string, pair<int, int>> mp;
for(int i = 0; i < paths.size(); i ++) {
mp[paths[i][0]].first++;
mp[paths[i][1]].second++;
}
for(auto it = mp.begin(); it != mp.end(); it ++) {
if(it->second.first == 0 && it->second.second == 1)
return it->first;
}
return "";
}
};

Leetcode1441. Build an Array With Stack Operations

Given an array target and an integer n. In each iteration, you will read a number from list = {1,2,3…, n}. Build the target array using the following operations:

  • Push: Read a new element from the beginning list, and push it in the array.
  • Pop: delete the last element of the array.

If the target array is already built, stop reading more elements.
You are guaranteed that the target array is strictly increasing, only containing numbers between 1 to n inclusive.

Return the operations to build the target array. You are guaranteed that the answer is unique.

Example 1:

1
2
3
4
5
6
Input: target = [1,3], n = 3
Output: ["Push","Push","Pop","Push"]
Explanation:
Read number 1 and automatically push in the array -> [1]
Read number 2 and automatically push in the array then Pop it -> [1]
Read number 3 and automatically push in the array -> [1,3]

依次读取1~n之间的数字,如果数字等于target[0],那么只要做Push操作,同时删掉target[0];如果不相等,那么做Push和Pop操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
vector<string> buildArray(vector<int>& target, int n) {
vector<string> res;
int j = 0, i;
for(i = 1; i <= n && j < target.size(); i ++) {
if(target[j] == i) {
res.push_back("Push");
j ++;
}
else {
res.push_back("Push");
res.push_back("Pop");
}
}
return res;
}
};

Leetcode1442. Count Triplets That Can Form Two Arrays of Equal XOR

Given an array of integers arr.

We want to select three indices i, j and k where (0 <= i < j <= k < arr.length).

Let’s define a and b as follows:

  • a = arr[i] ^ arr[i + 1] ^ … ^ arr[j - 1]
  • b = arr[j] ^ arr[j + 1] ^ … ^ arr[k]

Note that ^ denotes the bitwise-xor operation.

Return the number of triplets (i, j and k) Where a == b.

Example 1:

1
2
3
Input: arr = [2,3,1,6,7]
Output: 4
Explanation: The triplets are (0,1,2), (0,2,2), (2,3,4) and (2,4,4)

Example 2:

1
2
Input: arr = [1,1,1,1,1]
Output: 10

这个题利用到前段时间学到的新知识——前缀异或和,根据异或运算的性质——相等的数进行异或,结果为0。

从题意中可以得出,我们所要求的是满足条件的三元组的数量,其中三元组的范围为0 <= i < j <= k < arr.length,所以在得到前缀异或和之后,我们可以通过暴力方法获取所有的可能情况,然后依次进行对比,得到最终的结果。

根据上面i, j, k三者的关系,我们可以转化为如下代码,又因为判断条件为——区间[i, j-1]和[j, k]的异或和相等,所以枚举所有可能的区间,然后比较异或和是否相等即可:

为什么题目中是a=arr[i]^arr[i+1]^…^arr[j-1], b=arr[j]^arr[j+1]^…^arr[k]而代码中直接比较前k+1和前i和元素的异或和s[k+1] == s[i]呢?

因为当a==b时,满足arr[i]^arr[i+1]^…^arr[j-1] == arr[j]^arr[j+1]^…^arr[k],也就是满足s[j]^s[i] == s[k+1]^s[j],等价于s[k+1] == s[i]。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
int countTriplets(vector<int>& arr) {
int ret = 0;
int n = arr.size();
vector<int> dp(n, 0);

dp[0] = arr[0];
for (int i = 1; i < n; i ++)
dp[i] = dp[i-1] ^ arr[i];

int res = 0;
for (int i = 0; i < n; i ++)
for (int j = i+1; j < n; j ++) {
int tmp = i > 0 ? (dp[i-1] ^ dp[j]) : dp[j];
if (tmp == 0)
res += (j - i);
}
return res;
}
};

Leetcode1446. Consecutive Characters

Given a string s, the power of the string is the maximum length of a non-empty substring that contains only one unique character. Return the power of the string.

Example 1:

1
2
3
Input: s = "leetcode"
Output: 2
Explanation: The substring "ee" is of length 2 with the character 'e' only.

Example 2:

1
2
3
Input: s = "abbcccddddeeeeedcba"
Output: 5
Explanation: The substring "eeeee" is of length 5 with the character 'e' only.

找到最长连续相同字母的子串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int maxPower(string s) {
int count = 1;
char c = s[0];
int res = -1;
for(int i = 1; i < s.length(); i ++) {
if(c == s[i]) {
count ++;
}
else {
res = max(res, count);
c = s[i];
count = 1;
}
}
res = max(res, count);
return res;
}
};

Leetcode1450. Number of Students Doing Homework at a Given Time

Given two integer arrays startTime and endTime and given an integer queryTime. The ith student started doing their homework at the time startTime[i] and finished it at time endTime[i].

Return the number of students doing their homework at time queryTime. More formally, return the number of students where queryTime lays in the interval [startTime[i], endTime[i]] inclusive.

Example 1:

1
2
3
4
5
6
Input: startTime = [1,2,3], endTime = [3,2,7], queryTime = 4
Output: 1
Explanation: We have 3 students where:
The first student started doing homework at time 1 and finished at time 3 and wasn't doing anything at time 4.
The second student started doing homework at time 2 and finished at time 2 and also wasn't doing anything at time 4.
The third student started doing homework at time 3 and finished at time 7 and was the only student doing homework at time 4.

简单题,无聊,找到在queryTime时写作业的人。

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
int busyStudent(vector<int>& startTime, vector<int>& endTime, int queryTime) {
int res = 0;
for(int i = 0; i < startTime.size(); i ++) {
if(startTime[i] <= queryTime && queryTime <= endTime[i])
res ++;
}
return res;
}
};

Leetcode1455. Check If a Word Occurs As a Prefix of Any Word in a Sentence

Given a sentence that consists of some words separated by a single space, and a searchWord. You have to check if searchWord is a prefix of any word in sentence. Return the index of the word in sentence where searchWord is a prefix of this word (1-indexed).

If searchWord is a prefix of more than one word, return the index of the first word (minimum index). If there is no such word return -1. A prefix of a string S is any leading contiguous substring of S.

Example 1:

1
2
3
Input: sentence = "i love eating burger", searchWord = "burg"
Output: 4
Explanation: "burg" is prefix of "burger" which is the 4th word in the sentence.

Example 2:

1
2
3
Input: sentence = "this problem is an easy problem", searchWord = "pro"
Output: 2
Explanation: "pro" is prefix of "problem" which is the 2nd and the 6th word in the sentence, but we return 2 as it's the minimal index.

Example 3:

1
2
3
Input: sentence = "i am tired", searchWord = "you"
Output: -1
Explanation: "you" is not a prefix of any word in the sentence.

Example 4:

1
2
Input: sentence = "i use triple pillow", searchWord = "pill"
Output: 4

找给定字符串是句子中的哪个词的前缀,简单但是字符串的题要注意下标的自增。

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
class Solution {
public:
int isPrefixOfWord(string sentence, string searchWord) {
int count = 1;
for(int i = 0; i < sentence.length();) {
if(sentence[i] == ' ') {
count ++;
i ++;
}
else {
int j;
for(j = 0; i < sentence.length() && j < searchWord.length() && sentence[i] != ' '; i ++) {
if(sentence[i] == searchWord[j]) {
j ++;
}
else {
break;
}
}
if(j == searchWord.length()) {
return count;
}
for(; i < sentence.length(); i ++)
if(sentence[i] == ' ')
break;
}

}
return -1;
}
};

还能用流做,不过流能显示水平嘛

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
class Solution {
public:
int isPrefixOfWord(string sentence, string searchWord) {
int n = int(sentence.length());
int n1 = int(searchWord.length());
int res = 0;
int t = 0;
for(int i = 0; i < n; i ++) {
if(i == 0 || sentence[i - 1] == ' ') {
res ++;
if(sentence[i] == searchWord[0]) {
if(n1 == 1)
return res;
for(int j = 1; j < n1; j ++) {
i++;
if(sentence[i] != searchWord[j]) {
t = 0;
break;
}
else
t = 1;
}
if(t == 1)
return res;
}
}
}
return -1;
}
};

Leetcode1460. Make Two Arrays Equal by Reversing Sub-arrays

Given two integer arrays of equal length target and arr. In one step, you can select any non-empty sub-array of arr and reverse it. You are allowed to make any number of steps. Return True if you can make arr equal to target, or False otherwise.

Example 1:

1
2
3
4
5
6
7
Input: target = [1,2,3,4], arr = [2,4,1,3]
Output: true
Explanation: You can follow the next steps to convert arr to target:
1- Reverse sub-array [2,4,1], arr becomes [1,4,2,3]
2- Reverse sub-array [4,2], arr becomes [1,2,4,3]
3- Reverse sub-array [4,3], arr becomes [1,2,3,4]
There are multiple ways to convert arr to target, this is not the only way to do so.

Example 2:

1
2
3
Input: target = [7], arr = [7]
Output: true
Explanation: arr is equal to target without any reverses.

Example 3:

1
2
Input: target = [1,12], arr = [12,1]
Output: true

判断一个数组能否通过翻转子串变成另一个数组,通过统计每个数字的个数判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
bool canBeEqual(vector<int>& target, vector<int>& arr) {
vector<int> res(1001, 0);
for(int i = 0; i < arr.size(); i ++)
res[arr[i]] ++;
for(int i = 0; i < target.size(); i ++) {
res[target[i]] --;
if(res[target[i]] < 0)
return false;
}
return true;
}
};

Leetcode1464. Maximum Product of Two Elements in an Array

Given the array of integers nums, you will choose two different indices i and j of that array. Return the maximum value of (nums[i]-1)*(nums[j]-1).

Example 1:

1
2
3
Input: nums = [3,4,5,2]
Output: 12
Explanation: If you choose the indices i=1 and j=2 (indexed from 0), you will get the maximum value, that is, (nums[1]-1)*(nums[2]-1) = (4-1)*(5-1) = 3*4 = 12.

Example 2:

1
2
3
Input: nums = [1,5,4,5]
Output: 16
Explanation: Choosing the indices i=1 and j=3 (indexed from 0), you will get the maximum value of (5-1)*(5-1) = 16.

返回最大的两个数之积,找到最大的两个数就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int maxProduct(vector<int>& nums) {
int max1 = -1, max2 = -1;
for(int i = 0; i < nums.size(); i ++) {
if(nums[i] > max1) {
max2 = max1;
max1 = nums[i];
}
else if(nums[i] > max2) {
max2 = nums[i];
}
}
return (max1 - 1) * (max2 - 1);
}
};

Leetcode1470. Shuffle the Array

Given the array nums consisting of 2n elements in the form [x1,x2,…,xn,y1,y2,…,yn]. Return the array in the form [x1,y1,x2,y2,…,xn,yn].

Example 1:

1
2
3
Input: nums = [2,5,1,3,4,7], n = 3
Output: [2,3,5,4,1,7]
Explanation: Since x1=2, x2=5, x3=1, y1=3, y2=4, y3=7 then the answer is [2,3,5,4,1,7].

Example 2:

1
2
Input: nums = [1,2,3,4,4,3,2,1], n = 4
Output: [1,4,2,3,3,2,4,1]

转换数组。。。。

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
vector<int> shuffle(vector<int>& nums, int n) {
vector<int> res;
for(int i = 0; i < nums.size()/2; i ++) {
res.push_back(nums[i]);
res.push_back(nums[i+n]);
}
return res;
}
};

Leetcode1475. Final Prices With a Special Discount in a Shop

Given the array prices where prices[i] is the price of the ith item in a shop. There is a special discount for items in the shop, if you buy the ith item, then you will receive a discount equivalent to prices[j] where j is the minimum index such that j > i and prices[j] <= prices[i], otherwise, you will not receive any discount at all.

Return an array where the ith element is the final price you will pay for the ith item of the shop considering the special discount.

Example 1:

1
2
3
4
5
6
7
Input: prices = [8,4,6,2,3]
Output: [4,2,4,2,3]
Explanation:
For item 0 with price[0]=8 you will receive a discount equivalent to prices[1]=4, therefore, the final price you will pay is 8 - 4 = 4.
For item 1 with price[1]=4 you will receive a discount equivalent to prices[3]=2, therefore, the final price you will pay is 4 - 2 = 2.
For item 2 with price[2]=6 you will receive a discount equivalent to prices[3]=2, therefore, the final price you will pay is 6 - 2 = 4.
For items 3 and 4 you will not receive any discount at all.

对每个价格,从这个价格开始往后找第一个小于它的,这就是当前价格可以折扣的数量,当前价格减掉找到的这个价格。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
vector<int> finalPrices(vector<int>& prices) {
for(int i = 0; i < prices.size(); i ++) {
for(int j = i + 1; j < prices.size(); j ++){
if(prices[j] <= prices[i]) {
prices[i] -= prices[j];
break;
}
}
}
return prices;
}
};

Leetcode1480. Running Sum of 1d Array

Given an array nums. We define a running sum of an array as runningSum[i] = sum(nums[0]…nums[i]). Return the running sum of nums.

Example 1:

1
2
3
Input: nums = [1,2,3,4]
Output: [1,3,6,10]
Explanation: Running sum is obtained as follows: [1, 1+2, 1+2+3, 1+2+3+4].

求前缀和

1
2
3
4
5
6
7
8
9
class Solution {
public:
vector<int> runningSum(vector<int>& nums) {
for(int i = 1; i < nums.size(); i ++) {
nums[i] = nums[i] + nums[i-1];
}
return nums;
}
};

Leetcode1486. XOR Operation in an Array

Given an integer n and an integer start. Define an array nums where nums[i] = start + 2*i (0-indexed) and n == nums.length. Return the bitwise XOR of all elements of nums.

Example 1:

1
2
3
4
Input: n = 5, start = 0
Output: 8
Explanation: Array nums is equal to [0, 2, 4, 6, 8] where (0 ^ 2 ^ 4 ^ 6 ^ 8) = 8.
Where "^" corresponds to bitwise XOR operator.

求异或和。

1
2
3
4
5
6
7
8
9
class Solution {
public:
int xorOperation(int n, int start) {
int res = start;
for(int i = 1; i < n; i ++)
res = res ^ (start + i * 2);
return res;
}
};

Leetcode1491. Average Salary Excluding the Minimum and Maximum Salary

Given an array of unique integers salary where salary[i] is the salary of the employee i. Return the average salary of employees excluding the minimum and maximum salary.

Example 1:

1
2
3
4
Input: salary = [4000,3000,1000,2000]
Output: 2500.00000
Explanation: Minimum salary and maximum salary are 1000 and 4000 respectively.
Average salary excluding minimum and maximum salary is (2000+3000)/2= 2500

去掉最值求平均。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
double average(vector<int>& salary) {
int minn = 1e7, maxx = -1;
for(int i = 0; i < salary.size(); i ++) {
if(salary[i] > maxx)
maxx = salary[i];
if(salary[i] < minn)
minn = salary[i];
}
int sum = 0, count = 0;
for(int i = 0; i < salary.size(); i ++)
if(salary[i] != minn && salary[i] != maxx) {
sum += salary[i];
count ++;
}
return (double)sum / count;
}
};

Leetcode1492. The kth Factor of n

Given two positive integers n and k. A factor of an integer n is defined as an integer i where n % i == 0. Consider a list of all factors of n sorted in ascending order, return the kth factor in this list or return -1 if n has less than k factors.

Example 1:

1
2
3
Input: n = 12, k = 3
Output: 3
Explanation: Factors list is [1, 2, 3, 4, 6, 12], the 3rd factor is 3.

Example 2:

1
2
3
Input: n = 7, k = 2
Output: 7
Explanation: Factors list is [1, 7], the 2nd factor is 7.

找一个数的第k个因子,简单。评测机不稳定,我提交了三次有两次是memory战胜100%。

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
int kthFactor(int n, int k) {
int res = 0;
for(int i = 1; i <= n; i++){
if(n % i == 0 && --k == 0)
return i;
}
return -1;
}
};

Leetcode1496. Path Crossing

Given a string path, where path[i] = ‘N’, ‘S’, ‘E’ or ‘W’, each representing moving one unit north, south, east, or west, respectively. You start at the origin (0, 0) on a 2D plane and walk on the path specified by path.

Return True if the path crosses itself at any point, that is, if at any time you are on a location you’ve previously visited. Return False otherwise.

Example 1:

1
2
3
Input: path = "NES"
Output: false
Explanation: Notice that the path doesn't cross any point more than once.

看路线有没有环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
bool isPathCrossing(string path) {
int x = 0, y = 0;
set<pair<int, int>> se;
se.insert({x, y});
for(char c : path) {
switch(c){
case 'N': y++; break;
case 'S': y--; break;
case 'E': x++; break;
case 'W': x--; break;
}
if(se.find({x, y}) != se.end())
return true;
se.insert({x, y});
}
return false;
}
};

Leetcode1207. Unique Number of Occurrences

Given an array of integers arr, write a function that returns true if and only if the number of occurrences of each value in the array is unique.

Example 1:

1
2
3
Input: arr = [1,2,2,1,1,3]
Output: true
Explanation: The value 1 has 3 occurrences, 2 has 2 and 3 has 1. No two values have the same number of occurrences.

Example 2:

1
2
Input: arr = [1,2]
Output: false

Example 3:

1
2
Input: arr = [-3,0,1,-3,1,1,1,-3,10,0]
Output: true

不同的数字拥有不同的个数,则为真,反之为假;意思就是对数组中的数字进行计数,要保证每个数的个数都是不一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
bool uniqueOccurrences(vector<int>& arr) {
vector<int> cishu(1000, -1);
unordered_map<int, int> mp;
for(int i = 0; i < arr.size(); i ++) {
mp[arr[i]] ++;
}
for(auto it = mp.begin(); it != mp.end(); it ++) {
if(cishu[it->second] != -1)
return false;
else
cishu[it->second] = it->first;
}
return true;
}
};

另一种做法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    bool uniqueOccurrences(vector<int>& arr) {
int i;
map<int, int> mp;
map<int, int> mpp;
map<int, int>::iterator it;
for(i = 0; i < arr.size(); i ++)
{
mp[arr[i]] ++;
}
bool ans = true;
for(it = mp.begin(); it != mp.end(); it ++)
{
if(mpp[it->second] == 1) {
ans = false;
break;
}
mpp[it->second] = 1;
}
return ans;
}
};

Leetcode1209. Remove All Adjacent Duplicates in String II

You are given a string s and an integer k, a k duplicate removal consists of choosing k adjacent and equal letters from s and removing them, causing the left and the right side of the deleted substring to concatenate together.

We repeatedly make k duplicate removals on s until we no longer can.

Return the final string after all such duplicate removals have been made. It is guaranteed that the answer is unique.

Example 1:

1
2
3
Input: s = "abcd", k = 2
Output: "abcd"
Explanation: There's nothing to delete.

Example 2:

1
2
3
4
5
Input: s = "deeedbbcccbdaa", k = 3
Output: "aa"
Explanation: First delete "eee" and "ccc", get "ddbbbdaa"
Then delete "bbb", get "dddaa"
Finally delete "ddd", get "aa"

Example 3:

1
2
Input: s = "pbbcggttciiippooaais", k = 2
Output: "ps"

Constraints:

  • 1 <= s.length <= 105
  • 2 <= k <= 104
  • s only contains lower case English letters.

这道题是之前那道 Remove All Adjacent Duplicates In String 的拓展,那道题只是让移除相邻的相同字母,而这道题让移除连续k个相同的字母,规则都一样,移除后的空位不保留,断开的位置重新连接,则有可能继续生成可以移除的连续字母。最直接暴力的解法就是多次扫描,每次都移除连续k个字母,然后剩下的字母组成新的字符串,再次进行扫描,但是这种方法现在超时了 Time Limit Exceeded,所以必须要用更加高效的解法。由于多次扫描可能会超时,所以要尽可能的在一次遍历中就完成,对于已经相邻的相同字母容易清除,断开的连续字母怎么一次处理呢?答案是在统计的过程中及时清除连续的字母,由于只能遍历一次,所以清除的操作可以采用字母覆盖的形式的,则这里可以使用双指针 Two Pointers 来操作,i指向的是清除后没有k个连续相同字母的位置,j是当前遍历原字符串的位置,这里还需要一个数组 cnt,其中 cnt[i] 表示字母 s[i] 连续出现的个数。i和j初始化均为0,开始遍历字符串s,用 s[j] 来覆盖 s[i],然后来更新 cnt[i],判断若i大于0,且 s[i - 1] 等于 s[i],说明连续字母的个数增加了一个,用 cnt[i-1] + 1 来更新 cnt[i],否则 cnt[i] 置为1。这样最终前字符串s的前i个字母就是最终移除后剩下的结果,直接返回即可,参见代码如下:

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
string removeDuplicates(string s, int k) {
int i = 0, n = s.size();
vector<int> cnt(n);
for (int j = 0; j < n; ++j, ++i) {
s[i] = s[j];
cnt[i] = (i > 0 && s[i - 1] == s[i]) ? cnt[i - 1] + 1 : 1;
if (cnt[i] == k) i -= k;
}
return s.substr(0, i);
}
};

我们也可以使用栈来做,这里用个数组来模拟栈,栈中放一个由字符的出现和次数和该字符组成的 pair 对儿,初始化时放个 {0, ‘#’} 进去,是为了防止栈为空。然后开始遍历字符串s的每个字符c,此时判断若栈顶的 pair 对儿不是字符c,则组成的新的 pair 对儿 {1, c} 压入栈中,否则将栈顶 pair 对儿的次数自增1,若此时正好等于k了,则将栈顶 pair 对儿移除,这样最终的残留部分都按顺序保留在栈中了。此时就发现用数组的好处了,因为可以从开头遍历,按顺序将剩余部分放入结果 res 中即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
string removeDuplicates(string s, int k) {
string res;
vector<pair<int, char>> st{{0, '#'}};
for (char c : s) {
if (st.back().second != c) {
st.push_back({1, c});
} else if (++st.back().first == k) {
st.pop_back();
}
}
for (auto &a : st) {
res.append(a.first, a.second);
}
return res;
}
};

Leetcode1217. Play with Chips

There are some chips, and the i-th chip is at position chips[i].

You can perform any of the two following types of moves any number of times (possibly zero) on any chip:

  • Move the i-th chip by 2 units to the left or to the right with a cost of 0.
  • Move the i-th chip by 1 unit to the left or to the right with a cost of 1.

There can be two or more chips at the same position initially. Return the minimum cost needed to move all the chips to the same position (any position).

Example 1:

1
2
3
Input: chips = [1,2,3]
Output: 1
Explanation: Second chip will be moved to positon 3 with cost 1. First chip will be moved to position 3 with cost 0. Total cost is 1.

Example 2:

1
2
3
Input: chips = [2,2,2,3,3]
Output: 2
Explanation: Both fourth and fifth chip will be moved to position two with cost 1. Total minimum cost will be 2.

移动一格cost1,移动2格cost0,本质上偶数位置的可以免费移动到任意偶数位,奇数位可以免费移动到任意奇数位,那实际上就可以把奇的都放到一个位置上,偶的放到一个位置上,然后哪边少就把少的移动到多的去。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int minCostToMoveChips(vector<int>& chips) {
int odd = 0, even = 0;
for(int i = 0; i < chips.size(); i ++) {
if(chips[i] % 2)
odd ++;
else
even ++;
}
return min(odd, even);
}
};

Leetcode1221. Split a String in Balanced Strings

Balanced strings are those who have equal quantity of ‘L’ and ‘R’ characters. Given a balanced string s split it in the maximum amount of balanced strings. Return the maximum amount of splitted balanced strings.

Example 1:

1
2
3
Input: s = "RLRRLLRLRL"
Output: 4
Explanation: s can be split into "RL", "RRLL", "RL", "RL", each substring contains same number of 'L' and 'R'.

Example 2:

1
2
3
Input: s = "RLLLLRRRLR"
Output: 3
Explanation: s can be split into "RL", "LLLRRR", "LR", each substring contains same number of 'L' and 'R'.

Example 3:

1
2
3
Input: s = "LLLLRRRR"
Output: 1
Explanation: s can be split into "LLLLRRRR".

Example 4:

1
2
3
Input: s = "RLRRRLLRLL"
Output: 2
Explanation: s can be split into "RL", "RRRLLRLL", since each substring contains an equal number of 'L' and 'R'

简单统计L和R的数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int balancedStringSplit(string s) {
int cur = 0;
int len = s.length();
int l = 0, r = 0;
for(int i = 0; i < len; i ++) {
if(s[i] == 'L')
l ++;
if(s[i] == 'R')
r ++;
if(l == r) {
l = r = 0;
cur ++;
}
}
return cur;
}
};

Leetcode1222. Queens That Can Attack the King

On an 8x8 chessboard, there can be multiple Black Queens and one White King.

Given an array of integer coordinates queens that represents the positions of the Black Queens, and a pair of coordinates king that represent the position of the White King, return the coordinates of all the queens (in any order) that can attack the King.

Example 1:

1
2
3
4
5
6
7
8
9
Input: queens = [[0,1],[1,0],[4,0],[0,4],[3,3],[2,4]], king = [0,0]
Output: [[0,1],[1,0],[3,3]]
Explanation:
The queen at [0,1] can attack the king cause they're in the same row.
The queen at [1,0] can attack the king cause they're in the same column.
The queen at [3,3] can attack the king cause they're in the same diagnal.
The queen at [0,4] can't attack the king cause it's blocked by the queen at [0,1].
The queen at [4,0] can't attack the king cause it's blocked by the queen at [1,0].
The queen at [2,4] can't attack the king cause it's not in the same row/column/diagnal as the king.

Example 2:

1
2
Input: queens = [[0,0],[1,1],[2,2],[3,4],[3,5],[4,4],[4,5]], king = [3,3]
Output: [[2,2],[3,4],[4,4]]

Example 3:

1
2
Input: queens = [[5,6],[7,7],[2,1],[0,7],[1,6],[5,1],[3,7],[0,3],[4,0],[1,2],[6,3],[5,0],[0,4],[2,2],[1,1],[6,4],[5,4],[0,0],[2,6],[4,5],[5,2],[1,4],[7,5],[2,3],[0,5],[4,2],[1,0],[2,7],[0,1],[4,6],[6,1],[0,6],[4,3],[1,7]], king = [3,4]
Output: [[2,3],[1,4],[1,6],[3,7],[4,3],[5,4],[4,5]]

queue可以8个方向进行移动,也就是kill,但是queue不能跨越其他单位进行kill。那么我们就可以反向来思考,将king进行8个方向的移动,如果碰到了queue,就说明是可以被kill的,同时,遇到了之后就停止继续那个方向。既然我们需要在棋盘上进行移动,就需要首先将棋盘摆出来。

  • 定义一个二维数组用来表示棋盘
  • 将queue摆放在棋盘上,也就是将棋盘上queue的位置标注为1
  • 定义8个方向,dx和dy表示每个方向上x,y的delta值
  • 在每个方向上对king在棋盘范围内进行移动,如果遇到了queue(plant格子==1),那么将该格子放入到结果中,同时终止该方向上的移动
  • 8个方向都移动结束后返回结果
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
class Solution {
public:
vector<vector<int>> queensAttacktheKing(vector<vector<int>>& queens, vector<int>& king) {
int x = king[0], y = king[1];
vector<vector<int>> graph(8, vector<int>(8, 0));
vector<vector<int>> res;
for(int i = 0; i < queens.size(); i ++)
graph[queens[i][0]][queens[i][1]] = 1;
int dy[8] = {0,0,-1,1,-1,1,-1,1};
int dx[8] = {-1,1,0,0,-1,-1,1,1};
int xx, yy;
for(int i = 0; i < 8; i ++) {
int x = king[0], y = king[1];
xx = x + dx[i];
yy = y + dy[i];
while(0 <= xx && xx <= 7 && 0 <= yy && yy <= 7) {
if(graph[xx][yy] == 1) {
res.push_back({xx, yy});
break;
}
xx = xx + dx[i];
yy = yy + dy[i];
}
}
return res;
}
};

Leetcode1227. Airplane Seat Assignment Probability

n passengers board an airplane with exactly n seats. The first passenger has lost the ticket and picks a seat randomly. But after that, the rest of passengers will:

Take their own seat if it is still available,
Pick other seats randomly when they find their seat occupied
What is the probability that the n-th person can get his own seat?

Example 1:

1
2
3
Input: n = 1
Output: 1.00000
Explanation: The first person can only get the first seat.

Example 2:

1
2
3
Input: n = 2
Output: 0.50000
Explanation: The second person has a probability of 0.5 to get the second seat (when first person gets the first seat).

Constraints:

  • 1 <= n <= 10^5

这道题说是有n个人要登机,且飞机上正好有n个座位,说是第一个人会在n个座位中随机选一个位置坐,然后从第二个人开始,遵循这样的选座方法:若其对应的座位号是空的,则坐到自己的位置,否则就在剩余的位置中随机挑选一个位置坐,现在问第n个人能坐到自己的位置的概率是多少。这道题其实没有太多的算法在里面,本质上其实是一道数学题,一般来说这种类似脑筋急转弯的题目在数学推导方面完成了之后,代码可能就非常的简单了,甚至一两行就可以搞定了,但难点就是在于数学推导的部分。现在来分析一下吧,由于第一个人是随机挑选座位,其挑选的座位对后面的人的影响是不同的,需要分情况来讨论:

当第一个人正好选到了自己的座位时,这种情况的概率是 1/n,那么对于之后的所有人来说,自己的座位都是空的,可以直接坐,那么每个人坐到自己位子的概率也就是第一个人坐到自己位置的概率,都为 1/n(包括第n个人)。

当第一个人直接一勾子坐到第n个座位上(概率是 1/n),那么不管中间的人怎么坐,第n个人都无法再坐到自己的位置上了,概率为0。

当第一个人坐到了范围 [2, n-1] 中的任意一个位置,共有的 n-2 个位置可供选择,到达这种情况的总概率是 (n-2)/n,但坐到每一个位子的概率还是 1/n。若第一个人坐到了第二个位子,第二个人此时就有三种选择:1)坐到第一个人的位子,则之后所有的人都可以坐到自己的位子了,包括第n个人,概率是 (n-2)/n * 1/(n-2)。2)坐到第n个座位,则第n个人就无法坐自己位子了,概率是0。3)坐其他的座位,范围是 [1, 1] 并 [3, n-1],这里还是得分情况讨论,有没有没发现,这三种情况其实就是对应着第一个人开始的三种情况,也就是说当前实际上就变成了一个共 n-1 个座位的子问题,此时第二个人就相当于变成了第一个人,但可能有的童鞋会有疑问,不对吧,此时的第二个人不能坐自己的位置,而第一个人开始是可以坐自己的位置的,两人的情况不同啊,怎么能说是子问题呢?其实看是不是子问题,主要是看对所求的结果是否有影响,求的只是第n个人能坐到自己位置的概率,即便第二个人不能坐自己的位置,但是可以坐第一个人的位置,那么就相当于前两个人都坐了自己的位置,对后面的人没有影响,所以可以看作是子问题,这种情况的概率是 (n-2)/n * 1/(n-2) * f(n-1)。当第一个人坐到第三个位子的时候,那么第二个人就可以坐自己的位置,第三个人实际又面临相同的三个选择,此时就是共有 n-2 个座位的子问题, 这种情况的概率是 (n-2)/n * 1/(n-2) * f(n-2),后面都是依次类推。

所以,整个的概率可以写为如下表达式:

f(n) = 1/n + 0 + (n-2)/n * (1/(n-2) * f(n-1) + 1/(n-2) * f(n-2) + … + 1/(n-2) * f(2))

化简一下可得:

f(n) = 1/n + 1/n * (f(n-1) + f(n-2) + … + f(2))

注意这是n大于2的情况,n小于等于2的时候,可以直接分析出来,就是 0.5。现在的目标是要化简上面的表达式,首先两边同时乘以n,可以得到:

n * f(n) = 1 + f(n-1) + f(n-2) + … + f(2)

把上面这个称作公式1,然后将上面公式中的n用 n-1 替换,可以得到公式2:

(n-1) * f(n-1) = 1 + f(n-2) + f(n-3) + … + f(2)

然后用公式1减去公式2,可以得到:

n * f(n) - (n-1) * f(n-1) = f(n-1)

化简后可得:

f(n) = f(n-1)

我们已经知道 f(2) = 0.5,那么根据上面这个式子,就可以知道任何大于2的n的函数值都是 0.5,所以这道题也就一行代码搞定了,参见代码如下:

1
2
3
4
5
6
class Solution {
public:
double nthPersonGetsNthSeat(int n) {
return n == 1 ? 1.0 : 0.5;
}
};

Leetcode1232. Check If It Is a Straight Line

You are given an array coordinates, coordinates[i] = [x, y], where [x, y] represents the coordinate of a point. Check if these points make a straight line in the XY plane.

Example 1:

1
2
Input: coordinates = [[1,2],[2,3],[3,4],[4,5],[5,6],[6,7]]
Output: true

Example 2:

1
2
Input: coordinates = [[1,1],[2,2],[3,4],[4,5],[5,6],[7,7]]
Output: false

判断给出的这些坐标点是否形成一条直线。我自己写的很繁琐,有可能有冗余的部分。通过斜率判断。

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
class Solution {
public:

static bool cmp(const vector<int>& a, const vector<int>& b) {
if(a[0] > b[0])
return true;
else if(a[0] < b[0])
return false;
else
if(a[1] > b[1])
return true;
else
return false;
}

bool checkStraightLine(vector<vector<int>>& coordinates) {
sort(coordinates.begin(), coordinates.end(), cmp);
int dx = coordinates[1][0] - coordinates[0][0];
int dy = coordinates[1][1] - coordinates[0][1];
if(dx == 0) {
for(int i = 1; i < coordinates.size(); i ++)
if(coordinates[i][0] != coordinates[0][0])
return false;
return true;
}
if(dy == 0) {
for(int i = 1; i < coordinates.size(); i ++)
if(coordinates[i][1] != coordinates[0][1])
return false;
return true;
}
// (y1-y0)/(x1-x0) = (yi-y0)/(xi-x0)
//-> (y1-y0)*(xi-x0) = (yi-y0)*(x1-x0)
auto &c0 = coordinates[0], &c1 = coordinates[1];

for(int i = 2; i < coordinates.size(); i ++) {
auto &c2 = coordinates[i];
if ((c1[1] - c0[1])*(c2[0] - c0[0]) != (c2[1] - c0[1])*(c1[0] - c0[0]))
return false;
}
return true;
}
};

Leetcode1237. Find Positive Integer Solution for a Given Equation

Given a function f(x, y) and a value z, return all positive integer pairs x and y where f(x,y) == z.

The function is constantly increasing, i.e.:

1
2
f(x, y) < f(x + 1, y)
f(x, y) < f(x, y + 1)

The function interface is defined like this:

1
2
3
4
5
interface CustomFunction {
public:
// Returns positive integer f(x, y) for any given positive integer x and y.
int f(int x, int y);
};

For custom testing purposes you’re given an integer function_id and a target z as input, where function_id represent one function from an secret internal list, on the examples you’ll know only two functions from the list.

You may return the solutions in any order.

Example 1:

1
2
3
Input: function_id = 1, z = 5
Output: [[1,4],[2,3],[3,2],[4,1]]
Explanation: function_id = 1 means that f(x, y) = x + y

Example 2:

1
2
3
Input: function_id = 2, z = 5
Output: [[1,5],[5,1]]
Explanation: function_id = 2 means that f(x, y) = x * y

无聊的题,遍历就行,找到满足那个函数的取值。

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
/*
* // This is the custom function interface.
* // You should not implement it, or speculate about its implementation
* class CustomFunction {
* public:
* // Returns f(x, y) for any given positive integers x and y.
* // Note that f(x, y) is increasing with respect to both x and y.
* // i.e. f(x, y) < f(x + 1, y), f(x, y) < f(x, y + 1)
* int f(int x, int y);
* };
*/

class Solution {
public:
vector<vector<int>> findSolution(CustomFunction& customfunction, int z) {
vector<vector<int>> res;
for(int i = 1; i < 1001; i ++) {
for(int j = 1; j < 1001; j ++) {
if(customfunction.f(i, j) == z)
res.push_back({i, j});
else if(customfunction.f(i, j) > z)
break;
}
}
return res;
}
};

Leetcode1252. Cells with Odd Values in a Matrix

Given n and m which are the dimensions of a matrix initialized by zeros and given an array indices where indices[i] = [ri, ci]. For each pair of [ri, ci] you have to increment all cells in row ri and column ci by 1.

Return the number of cells with odd values in the matrix after applying the increment to all indices.

Example 1:

1
2
3
4
5
Input: n = 2, m = 3, indices = [[0,1],[1,1]]
Output: 6
Explanation: Initial matrix = [[0,0,0],[0,0,0]].
After applying first increment it becomes [[1,2,1],[0,1,0]].
The final matrix will be [[1,3,1],[1,3,1]] which contains 6 odd numbers.

Example 2:

1
2
3
Input: n = 2, m = 2, indices = [[1,1],[0,0]]
Output: 0
Explanation: Final matrix = [[2,2],[2,2]]. There is no odd number in the final matrix.

这道题的意思就是把indices给出的那行和那列的数在原有的基础上+1。处理完之后,计算数组中奇数的个数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int oddCells(int n, int m, vector<vector<int>>& indices) {
vector<vector<int>> mapp(n, vector<int>(m, 0));
for(int i = 0; i < indices.size(); i ++) {
for(int j = 0; j < m; j ++)
mapp[indices[i][0]][j] ++;
for(int j = 0; j < n; j ++)
mapp[j][indices[i][1]] ++;
}
int res = 0;
for(int i = 0; i < n; i ++)
for(int j = 0; j < m; j ++)
if(mapp[i][j] % 2 != 0)
res ++;
return res;
}
};

Leetcode1260. Shift 2D Grid

Given a 2D grid of size m x n and an integer k. You need to shift the grid k times.

In one shift operation:

  • Element at grid[i][j] moves to grid[i][j + 1].
  • Element at grid[i][n - 1] moves to grid[i + 1][0].
  • Element at grid[m - 1][n - 1] moves to grid[0][0].
  • Return the 2D grid after applying shift operation k times.

Example 1:

1
2
Input: grid = [[1,2,3],[4,5,6],[7,8,9]], k = 1
Output: [[9,1,2],[3,4,5],[6,7,8]]

Example 2:

1
2
Input: grid = [[3,8,1,9],[19,7,2,5],[4,6,11,10],[12,0,21,13]], k = 4
Output: [[12,0,21,13],[3,8,1,9],[19,7,2,5],[4,6,11,10]]

Example 3:

1
2
Input: grid = [[1,2,3],[4,5,6],[7,8,9]], k = 9
Output: [[1,2,3],[4,5,6],[7,8,9]]

矩阵转换,每个元素向右移动k个位置,可以循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
vector<vector<int>> shiftGrid(vector<vector<int>>& grid, int k) {

int m = grid.size(), n = grid[0].size();
vector<vector<int>> res(m, vector<int>(n, 0));
for(int i = 0; i < m; i ++) {
int x, y;
for(int j = 0; j < n; j ++) {
y = j + k;
x = i;
if(y >= n) {
int temp;
temp = y / n;
y = y % n;
x = (x + temp) % m;
}
res[x][y] = grid[i][j];
}
}
return res;
}
};

Leetcode1266. Minimum Time Visiting All Points

On a plane there are n points with integer coordinates points[i] = [xi, yi]. Your task is to find the minimum time in seconds to visit all points.

You can move according to the next rules:

In one second always you can either move vertically, horizontally by one unit or diagonally (it means to move one unit vertically and one unit horizontally in one second).
You have to visit the points in the same order as they appear in the array.

Example 1:

1
2
3
4
5
6
Input: points = [[1,1],[3,4],[-1,0]]
Output: 7
Explanation: One optimal path is [1,1] -> [2,2] -> [3,3] -> [3,4] -> [2,3] -> [1,2] -> [0,1] -> [-1,0]
Time from [1,1] to [3,4] = 3 seconds
Time from [3,4] to [-1,0] = 4 seconds
Total time = 7 seconds

Example 2:

1
2
Input: points = [[3,2],[-2,2]]
Output: 5

为了计算两个点的最短时间对应的路径,我们应该尽量走对角,比如(1, 1) 到 (3, 4), 通过走对角方式(1, 1) -> (2, 2) -> (3, 3) -> (3, 4), 不能直接从(1, 1)到 (3, 4), 会先走3对角,在往垂直方向1。

针对(x1, y1) -> (x2, y2), 水平方向 |x2 - x1|, 垂直方向|y2 - y1|, 走对角 min(|x2 - x1|, |y2 - y1|), 走水平或垂直max(|x2 - x1|, |y2 - y1|) - min(|x2 - x1|, |y2 - y1|), 加起来为max(|x2 - x1|, |y2 - y1|,

根据题意,可以直接贪心思想,求出相邻两点的时间,并累加。

1
2
3
4
5
6
7
8
9
10
class Solution {
public:
int minTimeToVisitAllPoints(vector<vector<int>>& points) {
int cnt = 0;
for(int i = 1; i < points.size(); i ++) {
cnt += max(abs(points[i][0] - points[i - 1][0]), abs(points[i][1] - points[i - 1][1]));
}
return cnt;
}
};

Leetcode1275. Find Winner on a Tic Tac Toe Game

Tic-tac-toe is played by two players A and B on a 3 x 3 grid.

Return the winner of the game if it exists (A or B), in case the game ends in a draw return “Draw”, if there are still movements to play return “Pending”.

You can assume that moves is valid (It follows the rules of Tic-Tac-Toe), the grid is initially empty and A will play first.

Example 1:

1
2
3
4
5
6
Input: moves = [[0,0],[2,0],[1,1],[2,1],[2,2]]
Output: "A"
Explanation: "A" wins, he always plays first.
"X " "X " "X " "X " "X "
" " -> " " -> " X " -> " X " -> " X "
" " "O " "O " "OO " "OOX"

Example 2:

1
2
3
4
5
6
Input: moves = [[0,0],[1,1],[0,1],[0,2],[1,0],[2,0]]
Output: "B"
Explanation: "B" wins.
"X " "X " "XX " "XXO" "XXO" "XXO"
" " -> " O " -> " O " -> " O " -> "XO " -> "XO "
" " " " " " " " " " "O "

Example 3:

1
2
3
4
5
6
Input: moves = [[0,0],[1,1],[2,0],[1,0],[1,2],[2,1],[0,1],[0,2],[2,2]]
Output: "Draw"
Explanation: The game ends in a draw since there are no moves to make.
"XXO"
"OOX"
"XOX"

Example 4:

1
2
3
4
5
6
Input: moves = [[0,0],[1,1]]
Output: "Pending"
Explanation: The game has not finished yet.
"X "
" O "
" "
  1. 虽然有A、B、Pending、Draw四种答案的可能。我们首先判断A、B谁能赢,再讨论A、B都未胜的情况下游戏是结束了还是继续进行;
  2. 判断A、B是否有人能取胜,只需要判断最后一个落棋的人是否能胜;(因为要是另外一个人赢了,游戏就结束了,不再有继续下棋的机会)
  3. 用数组记录最后落棋者的走棋情况,如果等于三,游戏结束,此人胜利;(以3x3为例,其余可以类推)
  4. 最后落棋者为未胜时,棋盘被下满则Draw,棋盘未下满则Pending。
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
class Solution {
public:
string tictactoe(vector<vector<int>>& moves) {
int cnt[8] = {0};
// 用数组记录0-2行、0-2列、正对角线、副对角线是否已满3个棋子
// count[0-2]对应0-2行、count[3-5]对应0-2列、count[6]对应正对角线、count[7]对应副对角线
for(int i = moves.size()-1; i >= 0; i -= 2) {
cnt[moves[i][0]] ++;
// 此棋对行的影响
cnt[moves[i][1]+3] ++;
// 此棋对列的影响
if(moves[i][0] == moves[i][1])
cnt[6] ++;
// 此棋对对角线的影响
if(moves[i][0] + moves[i][1] == 2)
cnt[7] ++;
// 满3个棋子则胜利
if(cnt[moves[i][0]] == 3 || cnt[moves[i][1] + 3] == 3 || cnt[6] == 3 || cnt[7] == 3) {
for(int i = 0 ; i < 8;i ++)
cout << cnt[i]<<endl;
return i % 2 ? "B" : "A";
}
}
return moves.size() < 9 ? "Pending" : "Draw";
}
};

Leetcode1277. Count Square Submatrices with All Ones

Given a m * n matrix of ones and zeros, return how many square submatrices have all ones.

Example 1:

1
2
3
4
5
6
7
8
9
10
11
12
Input: matrix =
[
[0,1,1,1],
[1,1,1,1],
[0,1,1,1]
]
Output: 15
Explanation:
There are 10 squares of side 1.
There are 4 squares of side 2.
There is 1 square of side 3.
Total number of squares = 10 + 4 + 1 = 15.

Example 2:

1
2
3
4
5
6
7
8
9
10
11
Input: matrix = 
[
[1,0,1],
[1,1,0],
[1,1,0]
]
Output: 7
Explanation:
There are 6 squares of side 1.
There is 1 square of side 2.
Total number of squares = 6 + 1 = 7.

Constraints:

  • 1 <= arr.length <= 300
  • 1 <= arr[0].length <= 300
  • 0 <= arr[i][j] <= 1
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
class Solution {
public:
int countSquares(vector<vector<int>>& matrix) {
if (matrix.empty() || matrix[0].empty()) return 0;
int m = matrix.size(), n = matrix[0].size();
int count = 0;
int s = std::min(m, n);
for (int i = 0; i < m; ++ i) {
for (int j = 0; j < n; ++ j) {
// 发现为 1 的值, 然后从这个值出发, 访问以这个值为左上角的子方阵
// 子方阵的大小 k 的范围为 [1, s]
// 之后访问子方阵中的 matrix[i : i + k, j : j + k] 范围内的每个元素
// 判断是否都是 1, 如果都是 1, 那么最后 is_all_ones 肯定为 true,
// 此时累加 count.
if (matrix[i][j] == 1) {
count ++;
bool is_all_ones = true;
for (int k = 1; k <= s; ++ k) {
if ((i + k >= m) || (j + k >= n)) break; // 越界则不用考虑了
for (int h = i; h <= i + k && is_all_ones; ++ h) {
if ((j + k >= n) ||
(j + k < n && matrix[h][j + k] != 1))
is_all_ones = false;
}
for (int w = j; w <= j + k && is_all_ones; ++ w) {
if ((i + k >= m) ||
(i + k < m && matrix[i + k][w] != 1))
is_all_ones = false;
}
if (is_all_ones) count ++;
}
}
}
}
return count;
}
};

令 dp[i][j] 为以 A[i][j] 为右下角元素的所能得到的最大的 square submatrices 的个数.

如果 A[i][j] == 0, 则 dp[i][j] = 0, 表示以 A[i][j] 为右下角元素的 square submatrices 为 0.

如果 A[i][j] == 1, 那么 dp[i][j] 的值由 dp[i - 1][j], dp[i - 1][j - 1] 以及 dp[i][j - 1] 中的最小值决定:

dp[i][j] = min(dp[i - 1][j], dp[i - 1][j - 1], dp[i][j - 1]) + 1

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
class Solution {
public:
int countSquares(vector<vector<int>>& matrix) {
int n = matrix.size(), m = matrix[0].size();
vector<vector<int>> dp1(n+1, vector<int>(m+1, 0)), dp2(n+1, vector<int>(m+1, 0)), dp(n+1, vector<int>(m+1, 0));

for (int r = 1; r <= n; r ++) {
for (int c = 1; c <= m; c ++) {
if (!matrix[r-1][c-1])
dp1[r][c] = dp2[r][c] = 0;
else {
dp1[r][c] = dp1[r-1][c] + 1;
dp2[r][c] = dp2[r][c-1] + 1;
}
}
}

int ret = 0;
for (int r = 1; r <= n; r ++) {
for (int c = 1; c <= m; c ++) {
if (matrix[r-1][c-1]) {
dp[r][c] = max(1, min(dp[r-1][c-1]+1, min(dp1[r][c], dp2[r][c])));
ret += dp[r][c];
}
}
}
return ret;
}
};

优化版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int countSquares(vector<vector<int>>& matrix) {
if (matrix.empty() || matrix[0].empty()) return 0;
int m = matrix.size(), n = matrix[0].size();
int count = 0;
for (int i = 0; i < m; ++ i) {
for (int j = 0; j < n; ++ j) {
if (i > 0 && j > 0 && matrix[i][j] == 1)
matrix[i][j] = std::min(matrix[i - 1][j - 1],
std::min(matrix[i - 1][j], matrix[i][j - 1])) + 1;
count += matrix[i][j]; // 其余情况
}
}
return count;
}
};

Leetcode1281. Subtract the Product and Sum of Digits of an Integer

Given an integer number n, return the difference between the product of its digits and the sum of its digits.

Example 1:

1
2
3
4
5
6
Input: n = 234
Output: 15
Explanation:
Product of digits = 2 * 3 * 4 = 24
Sum of digits = 2 + 3 + 4 = 9
Result = 24 - 9 = 15

Example 2:

1
2
3
4
5
6
Input: n = 4421
Output: 21
Explanation:
Product of digits = 4 * 4 * 2 * 1 = 32
Sum of digits = 4 + 4 + 2 + 1 = 11
Result = 32 - 11 = 21

给定整数n,返回其数字乘积与数字总和之间的差。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:

int subtractProductAndSum(int n) {
int he = 0, ji = 1;
while(n) {
int temp = n % 10;
n /= 10;
he += temp;
ji *= temp;
}
return ji - he;
}
};

Leetcode1287. Element Appearing More Than 25% In Sorted Array

Given an integer array sorted in non-decreasing order, there is exactly one integer in the array that occurs more than 25% of the time.

Return that integer.

Example 1:

1
2
Input: arr = [1,2,2,6,6,6,6,7,10]
Output: 6

从第0个元素开始判断,i+1/4size 的元素是否还是自己,若是,则该值为所求。否则,判断下一个元素。一直到,size-1/4size的元素,若该元素还不是,那么以后的元素也不可能出现超过1/4的频率了,也就不用再循环判断了。

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
int findSpecialInteger(vector<int>& arr) {
int step = arr.size() / 4;
for(int i = 0; i < arr.size() - step; i ++) {
if(arr[i] == arr[i+step])
return arr[i];
}
return -1;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int findSpecialInteger(vector<int>& arr) {
int N = arr.size();
int freq = N / 4;

int pos = 0;
for (int i = 1; i < arr.size(); ++i) {
if (arr[i] == arr[i - 1])
continue;

if ( (i - pos) > freq)
return arr[i-1];
pos = i;
}

return arr.back();
}

Leetcode1289. Minimum Falling Path Sum II

Given an n x n integer matrix grid, return the minimum sum of a falling path with non-zero shifts.

A falling path with non-zero shifts is a choice of exactly one element from each row of grid such that no two elements chosen in adjacent rows are in the same column.

Example 1:

1
2
3
4
5
6
7
8
Input: arr = [[1,2,3],[4,5,6],[7,8,9]]
Output: 13
Explanation:
The possible falling paths are:
[1,5,9], [1,5,7], [1,6,7], [1,6,8],
[2,4,8], [2,4,9], [2,6,7], [2,6,8],
[3,4,8], [3,4,9], [3,5,7], [3,5,9]
The falling path with the smallest sum is [1,5,7], so the answer is 13.

Example 2:

1
2
Input: grid = [[7]]
Output: 7

给你一个整数方阵 arr ,定义「非零偏移下降路径」为:从 arr 数组中的每一行选择一个数字,且按顺序选出来的数字中,相邻数字不在原数组的同一列。

请你返回非零偏移下降路径数字和的最小值。

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
class Solution {
public:
int minFallingPathSum(vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
vector<int> dp(n, 0), dp2;

for (int i = 0; i < n; i ++)
dp[i] = matrix[0][i];
for (int i = 1; i < m; i ++) {
dp2.assign(n, 0);
for (int j = 0; j < n; j ++) {
int minn = INT_MAX;
for (int k = 0; k < n; k ++)
if (k != j)
minn = min(minn, dp[k]);
dp2[j] = minn + matrix[i][j];
}
swap(dp, dp2);
}

int res = INT_MAX;
for (int i = 0; i < n; i ++)
res = min(res, dp[i]);
return res;
}
};

Leetcode1290. Convert Binary Number in a Linked List to Integer

Given head which is a reference node to a singly-linked list. The value of each node in the linked list is either 0 or 1. The linked list holds the binary representation of a number.

Return the decimal value of the number in the linked list.

Example 1:

1
2
3
Input: head = [1,0,1]
Output: 5
Explanation: (101) in base 2 = (5) in base 10

Example 2:

1
2
Input: head = [0]
Output: 0

Example 3:

1
2
Input: head = [1]
Output: 1

Example 4:

1
2
Input: head = [1,0,0,1,0,0,1,1,1,0,0,0,0,0,0]
Output: 18880

Example 5:

1
2
Input: head = [0,0]
Output: 0

二进制链表转换成十进制数

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
int getDecimalValue(ListNode* head) {
int res = 0;
while(head) {
res = res * 2 + head->val;
head = head->next;
}
return res;
}
};

Leetcode1295. Find Numbers with Even Number of Digits

Given an array nums of integers, return how many of them contain an even number of digits.

Example 1:

1
2
3
4
5
6
7
8
9
Input: nums = [12,345,2,6,7896]
Output: 2
Explanation:
12 contains 2 digits (even number of digits).
345 contains 3 digits (odd number of digits).
2 contains 1 digit (odd number of digits).
6 contains 1 digit (odd number of digits).
7896 contains 4 digits (even number of digits).
Therefore only 12 and 7896 contain an even number of digits.

Example 2:

1
2
3
4
Input: nums = [555,901,482,1771]
Output: 1
Explanation:
Only 1771 contains an even number of digits.

送分题,判断一个数里有几个数字组成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int findNumbers(vector<int>& nums) {
int digits, result=0;
for(int i=0;i<nums.size();i++) {
digits=0;
int num = nums[i];
while(num) {
digits++;
num/=10;
}
if(digits%2==0)
result ++;
}
return result;
}
};

Leetcode1299. Replace Elements with Greatest Element on Right Side

Given an array arr, replace every element in that array with the greatest element among the elements to its right, and replace the last element with -1.

After doing so, return the array.

Example 1:

1
2
Input: arr = [17,18,5,4,6,1]
Output: [18,6,6,6,1,-1]

替换当前元素为,当前元素以后元素的最大值。 最后一个元素替换为-1。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
vector<int> replaceElements(vector<int>& arr) {
int maxx = -1;
vector<int> res(arr.size(), 0);
for(int i = arr.size()-2; i >= 0; i --) {
maxx = max(maxx, arr[i+1]);
res[i] = maxx;
}
res[arr.size()-1] = -1;
return res;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
vector<int> replaceElements(vector<int>& arr) {
int maxx = -1;
for(int i = arr.size()-1; i >= 0; i --) {
int temp = arr[i];
arr[i] = maxx;
maxx = max(maxx, temp);
}
return arr;
}
};

链接:https://zhuanlan.zhihu.com/p/147250351
来源:知乎

位运算的百度百科如下: 程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算就是直接对整数在内存中的二进制位进行操作。

位操作的优势位运算是一种底层的运算,往往比我们普通的运算要快上许多许多位运算是最高效而且占用内存最少的算法操作,执行效率非常高位运算操作的是二进制数,会拥有一些二进制的特性,在实际问题可以方便运用位运算只需较低的空间需求位运算使用能使程序变得更加简洁和优美位运算可以表示一些状态集合运算符号下面的a和b都是整数类型,则:

  • 按位与a & b
  • 按位或a | b
  • 按位异或a ^ b
  • 按位取反~a
  • 左移a << b
  • 带符号右移a >> b

C语言中位运算符之间,按优先级顺序排列为优先级符号:

  1. ~
  2. <<、>>
  3. &
  4. ^
  5. |
  6. &=、^=、|=、<<=、>>=

概念简介以及技巧

and运算 &

判断奇偶数:对于除0以外的任意数x,使用x&1==1作为逻辑判断即可

1
2
3
4
if (x&1==1)
{

}

判断某个二进制位是否为1,比如第7位, 0x40转到二进制是0100 0000,代表第7位是1。

1
2
3
4
if (n&0x40)
{
//TODO:添加你要处理的代码
}

字节读取

1
2
3
4
(x >>  0) & 0x000000ff	/* 获取第0个字节 */
(x >> 8) & 0x000000ff /* 获取第1个字节 */
(x >> 16) & 0x000000ff /* 获取第2个字节 */
(x >> 24) & 0x000000ff /* 获取第3个字节 */

判断一个数是不是 2 的指数

1
2
3
4
bool isPowerOfTwo(int n) {
if (n <= 0) return false;
return (n & (n - 1)) == 0;
}

取余

1
2
3
4
5
6
//得到余数
int Yu(int num,int n)
{
int i = 1 << n;
return num&(i-1);
}

指定二进制位数截取:比如说16位二进制数A:1000 1000 1000 1000,如果你想获得A的哪一位,就把数字B:0000 0000 0000 0000的那一位设置为1.比如说我想获得A的第三位就把B的第三位数字设置为1,则B为0000 0000 0000 0100,之后A、B求与,结果若为0,说明A的第三位为0,结果为1,说明A的第三位为1。同理:若要获得A的第五位,就把B设置为0000 0000 0001 0000,之后再求与。通常在程序中数字B被称为掩码,就是专门用来测试某一位是否为0的数值。

统计二进制中 1 的个数:利用x=x&(x-1),会将x用二进制表示时最右边的一个1变为0,因为x-1会将该位变为0。

1
2
3
4
5
6
7
8
9
int Count(int x)
{
int sum=0;
while(x)
{ sum++;
x=x&(x-1);
}
return sum;
}

or操作

生成组合编码,进行状态压缩:当把二进制当作集合使用时,可以用or操作来增加元素。稍后详见“把二进制码当作集合使用”。

合并编码:在对字节码进行加密时,加密后的两段bit需要重新合并成一个字节,这时就需要使用or操作。

求一个数的二进制表达中0的个数

1
2
3
4
5
6
7
8
9
10
int Grial(int x)
{
int count = 0;
while (x + 1)
{
count++;
x |= (x + 1);
}
return count;
}

xor操作

两个整数交换变量名

1
2
3
4
5
void swap(int &a, int &b) {
a ^= b;
b ^= a;
a ^= b;
}

判断两个数是否异号

1
2
3
4
5
int x = -1, y = 2;
bool f = ((x ^ y) < 0); // true

int x = 3, y = 2;
bool f = ((x ^ y) < 0); // false

数据加密:将需要加密的内容看做A,密钥看做B,A ^ B=加密后的内容C。而解密时只需要将C ^ 密钥B=原内容A。如果没有密钥,就不能解密!

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define KEY 0x86
int main()
{
char p_data[16] = {"Hello World!"};
char Encrypt[16]={0},Decode[16]={0};
int i;

for(i = 0; i < strlen(p_data); i++)
{
Encrypt[i] = p_data[i] ^ KEY;
}

for(i = 0; i < strlen(Encrypt); i++)
{
Decode[i] = Encrypt[i] ^ KEY;
}

printf("Initial date: %s\n",p_data);
printf("Encrypt date: %s\n",Encrypt);
printf("Decode date: %s\n",Decode);

return 0;
}

数字判重:利用了二进制数的性质:x^y^y = x。我们可见,当同一个数累计进行两次xor操作,相当于自行抵销了,剩下的就是不重复的数。

1
2
3
4
5
6
7
int find(int[] arr){
int tmp = arr[0];
for(int i = 1;i < arr.length; i++){
tmp = tmp ^ arr[i];
}
return tmp;
}

not操作

交换符号

1
2
3
int reversal(int a) {
return ~a + 1;
}

取绝对值(效率高):

  • n>>31 取得n的符号:若n为正数,n>>31等于0;若n为负数,n>>31等于-1;若n为正数 n^0=0,数不变;若n为负数,有n^-1 需要计算n和-1的补码,然后进行异或运算,结果n变号并且为n的绝对值减1,再减去-1就是绝对值
1
2
3
4
int abs(int n)
{
return (n ^ (n >> 31)) - (n >> 31);
}

也可以这样使用

1
2
3
4
5
int abs(int n) 
{
int i = n >> 31;
return i == 0 ? n : (~n + 1);
}

从低位到高位,将n的第m位置1。将1左移m-1位找到第m位,得到000…1…000,n在和这个数做或运算

1
2
3
4
int setBitToOne(int n, int m)
{
return n | (1 << (m-1));
}

同理从低位到高位,将n的第m位置0,代码如下

1
2
3
4
int setBitToZero(int n, int m)
{
return n & ~(1 << (m-1));
}

shl操作 & shr操作

求2的N次方1<<n

高低位交换

1
2
unsigned short a = 34520;
a = (a >> 8) | (a << 8);

进行二进制逆序

1
2
3
4
5
unsigned short a = 34520;
a = ((a & 0xAAAA) >> 1) | ((a & 0x5555) << 1);
a = ((a & 0xCCCC) >> 2) | ((a & 0x3333) << 2);
a = ((a & 0xF0F0) >> 4) | ((a & 0x0F0F) << 4);
a = ((a & 0xFF00) >> 8) | ((a & 0x00FF) << 8);

获得int型最大最小值

1
2
3
4
5
6
7
8
int getMaxInt()
{
return (1 << 31) - 1;//2147483647, 由于优先级关系,括号不可省略
}
int getMinInt()
{
return 1 << 31;//-2147483648
}

m的n次方

1
2
3
4
5
6
7
8
9
10
11
12
//自己重写的pow()方法
int pow(int m , int n){
int sum = 1;
while(n != 0){
if(n & 1 == 1){
sum *= m;
}
m *= m;
n = n >> 1;
}
return sum;
}

找出不大于N的最大的2的幂指数

1
2
3
4
5
6
7
int findN(int n){
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8 // 整型一般是 32 位,上面我是假设 8 位。
return (n + 1) >> 1;
}

二分查找32位整数的前导0个数

1
2
3
4
5
6
7
8
9
10
11
12
13
int nlz(unsigned x)
{
int n;

if (x == 0) return(32);
n = 1;
if ((x >> 16) == 0) {n = n +16; x = x <<16;}
if ((x >> 24) == 0) {n = n + 8; x = x << 8;}
if ((x >> 28) == 0) {n = n + 4; x = x << 4;}
if ((x >> 30) == 0) {n = n + 2; x = x << 2;}
n = n - (x >> 31);
return n;
}

位图的操作

将 x 的第 n 位置1,可以通过 x |= (x << n) 来实现set_bit(char x, int n);

将 x 的第 n 位清0,可以通过 x &= ~(1 << n) 来实现clr_bit(char x, int n);

取出 x 的第 n 位的值,可以通过 (x >> n) & 1 来实现get_bit(char x, int n);

如下:

1
2
3
#define clr_bit(x, n) ( (x) &= ~(1 << (n)) )
#define set_bit(x, n) ( (x) |= (1 << (n)) )
#define get_bit(x, n) ( ((x)>>(n)) & 1 )

Leetcode1.Two Sum

Given an array of integers, return indices of the two numbers such that they add up to a specific target.

You may assume that each input would have exactly one solution, and you may not use the same element twice.

Example:

1
2
3
4
5
Given nums = [2, 7, 11, 15], target = 9,

Because nums[0] + nums[1] = 2 + 7 = 9,

return [0, 1].

也十分简单,不知道啥时候做的了,现在补上。就是找一对数,使二者之和等于target,可以暴力,也可以用巧妙的方法,下边有巧妙方法,是从solution中找的。

我的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> result;
int length = nums.size(),j=-1;
for(int i=0;i<length;i++){
for(j=i+1;j<length;j++)
if(nums[j]==target-nums[i]){
result.push_back(i);
result.push_back(j);
return result;
}
}
return result;
}
};

跟我一样的方法,用java实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
map.put(nums[i], i);
}
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement) && map.get(complement) != i) {
return new int[] { i, map.get(complement) };
}
}
throw new IllegalArgumentException("No two sum solution");
}

第三种方法:One-pass Hash Table
It turns out we can do it in one-pass. While we iterate and inserting elements into the table, we also look back to check if current element’s complement already exists in the table. If it exists, we have found a solution and return immediately.

1
2
3
4
5
6
7
8
9
10
11
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[] { map.get(complement), i };
}
map.put(nums[i], i);
}
throw new IllegalArgumentException("No two sum solution");
}

反正都是很简单的。。。

Leetcode2. Add Two Numbers

You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.

You may assume the two numbers do not contain any leading zero, except the number 0 itself.

Example:

1
2
3
Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 0 -> 8
Explanation: 342 + 465 = 807.

非常简单,给两个链表,相当于两个数,不过是倒着的,然后求这两个数的和再转换成链表。只是第一次用类的方法做题,不太习惯,然后对链表的使用也快忘光了,这是一个良好的开始吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
long int ll1=0,ll2=0,result = 0;
ListNode* head=new ListNode(0);
ListNode* curr=head;
int t=0;//jinwei
while(l1 != NULL || l2 != NULL){
ll1 = (l1!=NULL)? l1->val:0;
ll2 = (l2!=NULL)? l2->val:0;
int sum=t+ll1+ll2;
t=sum/10;
curr->next=new ListNode(sum%10);
curr=curr->next;
if(l1!=NULL) l1=l1->next;
if(l2!=NULL) l2=l2->next;
}
if(t>0)
{
curr->next=new ListNode(t);
}
return head->next;
}
};

Leetcode3. Longest Substring Without Repeating Characters

Given a string, find the length of the longest substring without repeating characters.

Example 1:

1
2
3
Input: "abcabcbb"
Output: 3
Explanation: The answer is "abc", with the length of 3.

Example 2:

1
2
3
Input: "bbbbb"
Output: 1
Explanation: The answer is "b", with the length of 1.

Example 3:

1
2
3
4
Input: "pwwkew"
Output: 3
Explanation: The answer is "wke", with the length of 3.
Note that the answer must be a substring, "pwke" is a subsequence and not a substring.

给了我们一个字符串,让求最长的无重复字符的子串,注意这里是子串,不是子序列,所以必须是连续的。先不考虑代码怎么实现,如果给一个例子中的例子 “abcabcbb”,让你手动找无重复字符的子串,该怎么找。博主会一个字符一个字符的遍历,比如 a,b,c,然后又出现了一个a,那么此时就应该去掉第一次出现的a,然后继续往后,又出现了一个b,则应该去掉一次出现的b,以此类推,最终发现最长的长度为3。所以说,需要记录之前出现过的字符,记录的方式有很多,最常见的是统计字符出现的个数,但是这道题字符出现的位置很重要,所以可以使用 HashMap 来建立字符和其出现位置之间的映射。进一步考虑,由于字符会重复出现,到底是保存所有出现的位置呢,还是只记录一个位置?我们之前手动推导的方法实际上是维护了一个滑动窗口,窗口内的都是没有重复的字符,需要尽可能的扩大窗口的大小。由于窗口在不停向右滑动,所以只关心每个字符最后出现的位置,并建立映射。窗口的右边界就是当前遍历到的字符的位置,为了求出窗口的大小,需要一个变量 left 来指向滑动窗口的左边界,这样,如果当前遍历到的字符从未出现过,那么直接扩大右边界,如果之前出现过,那么就分两种情况,在或不在滑动窗口内,如果不在滑动窗口内,那么就没事,当前字符可以加进来,如果在的话,就需要先在滑动窗口内去掉这个已经出现过的字符了,去掉的方法并不需要将左边界 left 一位一位向右遍历查找,由于 HashMap 已经保存了该重复字符最后出现的位置,所以直接移动 left 指针就可以了。维护一个结果 res,每次用出现过的窗口大小来更新结果 res,就可以得到最终结果啦。

这里可以建立一个 HashMap,建立每个字符和其最后出现位置之间的映射,然后需要定义两个变量 res 和 left,其中 res 用来记录最长无重复子串的长度,left 指向该无重复子串左边的起始位置的前一个,由于是前一个,所以初始化就是 -1,然后遍历整个字符串,对于每一个遍历到的字符,如果该字符已经在 HashMap 中存在了,并且如果其映射值大于 left 的话,那么更新 left 为当前映射值。然后映射值更新为当前坐标i,这样保证了 left 始终为当前边界的前一个位置,然后计算窗口长度的时候,直接用 i-left 即可,用来更新结果 res。

这里解释下程序中那个 if 条件语句中的两个条件 m.count(s[i]) && m[s[i]] > left,因为一旦当前字符 s[i] 在 HashMap 已经存在映射,说明当前的字符已经出现过了,而若 m[s[i]] > left 成立,说明之前出现过的字符在窗口内,那么如果要加上当前这个重复的字符,就要移除之前的那个,所以让 left 赋值为 m[s[i]],由于 left 是窗口左边界的前一个位置(这也是 left 初始化为 -1 的原因,因为窗口左边界是从0开始遍历的),所以相当于已经移除出滑动窗口了。举一个最简单的例子 “aa”,当 i=0 时,建立了 a->0 的映射,并且此时结果 res 更新为1,那么当 i=1 的时候,发现a在 HashMap 中,并且映射值0大于 left 的 -1,所以此时 left 更新为0,映射对更新为 a->1,那么此时 i-left 还为1,不用更新结果 res,那么最终结果 res 还为1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map<char, int> m;
int left = -1, res = 0;
for(int i = 0; i < s.length(); i ++) {
if(m.count(s[i]) && m[s[i]] > left) {
left = m[s[i]];
}
m[s[i]] = i;
res = max(res, i - left);
}
return res;
}
};

Leetcode4. Median of Two Sorted Arrays

Given two sorted arrays nums1 and nums2 of size m and n respectively, return the median of the two sorted arrays.

The overall run time complexity should be O(log (m+n)).

Example 1:

1
2
3
Input: nums1 = [1,3], nums2 = [2]
Output: 2.00000
Explanation: merged array = [1,2,3] and median is 2.

Example 2:

1
2
3
Input: nums1 = [1,2], nums2 = [3,4]
Output: 2.50000
Explanation: merged array = [1,2,3,4] and median is (2 + 3) / 2 = 2.5.

Example 3:

1
2
Input: nums1 = [0,0], nums2 = [0,0]
Output: 0.00000

Example 4:

1
2
Input: nums1 = [], nums2 = [1]
Output: 1.00000

Example 5:

1
2
Input: nums1 = [2], nums2 = []
Output: 2.00000

给出两个有序数组,要求找出这两个数组合并以后的有序数组中的中位数。要求时间复杂度为 O(log (m+n))。

这一题最容易想到的办法是把两个数组合并,然后取出中位数。但是合并有序数组的操作是 O(m+n) 的,不符合题意。看到题目给的 log 的时间复杂度,很容易联想到二分搜索。

由于要找到最终合并以后数组的中位数,两个数组的总大小也知道,所以中间这个位置也是知道的。只需要二分搜索一个数组中切分的位置,另一个数组中切分的位置也能得到。为了使得时间复杂度最小,所以二分搜索两个数组中长度较小的那个数组。

关键的问题是如何切分数组 1 和数组 2 。其实就是如何切分数组 1 。先随便二分产生一个 midA,切分的线何时算满足了中位数的条件呢?即,线左边的数都小于右边的数,即,nums1[midA-1] ≤ nums2[midB] && nums2[midB-1] ≤ nums1[midA] 。如果这些条件都不满足,切分线就需要调整。如果 nums1[midA] < nums2[midB-1],说明 midA 这条线划分出来左边的数小了,切分线应该右移;如果 nums1[midA-1] > nums2[midB],说明 midA 这条线划分出来左边的数大了,切分线应该左移。经过多次调整以后,切分线总能找到满足条件的解。

假设现在找到了切分的两条线了,数组 1 在切分线两边的下标分别是 midA - 1 和 midA。数组 2 在切分线两边的下标分别是 midB - 1 和 midB。最终合并成最终数组,如果数组长度是奇数,那么中位数就是 max(nums1[midA-1], nums2[midB-1])。如果数组长度是偶数,那么中间位置的两个数依次是:max(nums1[midA-1], nums2[midB-1]) 和 min(nums1[midA], nums2[midB]),那么中位数就是 (max(nums1[midA-1], nums2[midB-1]) + min(nums1[midA], nums2[midB])) / 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
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int len1 = nums1.size(), len2 = nums2.size();
if (len1 > len2)
return findMedianSortedArrays(nums2, nums1);
int mid1 = 0, mid2 = 0, k = (len1+len2+1) / 2;
int low = 0, high = len1;
while(low <= high) {
mid1 = (low+high) / 2;
mid2 = k - mid1;
if (mid1 < len1 && nums1[mid1] < nums2[mid2-1])
low = mid1+1;
else if (mid1 > 0 && nums1[mid1-1] > nums2[mid2])
high = mid1-1;
else
break;
}

int val1 = 0, val2 = 0;
if (mid1 == 0) // nums1里边的都比nums2大
val1 = nums2[mid2-1];
else if (mid2 == 0)
val1 = nums1[mid1-1];
else
val1 = max(nums1[mid1-1], nums2[mid2-1]);
if ((len1+len2) % 2 == 1)
return (double)val1;

if (mid1 == len1)
val2 = nums2[mid2];
else if (mid2 == len2)
val2 = nums1[mid1];
else
val2 = min(nums1[mid1], nums2[mid2]);
return ((double)val1+val2)/2;
}
};

Leetcode5. Longest Palindromic Substring

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example 1:

1
2
3
Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.

Example 2:

1
2
Input: "cbbd"
Output: "bb"

使用动态规划:dp[i][i]=1;//单个字符是回文串

dp[i][i+1]=1 if s[i]=s[i+1];//连续两个相同字符是回文串

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
class Solution {
public:
string longestPalindrome(string s) {
int len = s.size();
if(len==0 || len==1)
return s;
int start = 0;
int max = 1;

int dp[len][len];
memset(dp,0,sizeof(dp));
for(int i=0;i<len;i++){
dp[i][i]=1;
if(i<len-1 && s[i]==s[i+1]){
dp[i][i+1]=1;
max=2;
start=i;
}
}

//l表示检索的子串长度,等于3表示先检索长度为3的子串
for(int l=3;l<=len;l++)
for(int i=0;i+l-1<len;i++){
int j=l+i-1; //终止字符位置
if(s[i]==s[j] && dp[i+1][j-1]==1){ //状态转移
dp[i][j]=1;
start = i;
max = l;
}
}
return s.substr(start,max);
}
};

另一种区间dp方法:

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
class Solution {
public:
string longestPalindrome(string s) {
int len = s.length();
int start = 0, max_len = 1;

vector<vector<bool>> dp(len, vector<bool>(len, false));
for (int i = 0; i < len; i ++)
dp[i][i] = true;

for (int l = 2; l <= len; l ++) {
for (int i = 0; i+l <= len; i ++) {
int j = i+l-1;
if (s[i] == s[j]) {
dp[i][j] = (dp[i+1][j-1] && (s[i] == s[j])) || l == 2;
if (dp[i][j]) {
max_len = l;
start = i;
}
}
}
}

return s.substr(start, max_len);
}
};

马拉车方法:

这个马拉车算法 Manacher‘s Algorithm 是用来查找一个字符串的最长回文子串的线性方法,由一个叫 Manacher 的人在 1975 年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性,这是非常了不起的。对于回文串想必大家都不陌生,就是正读反读都一样的字符串,比如 “bob”, “level”, “noon” 等等,那么如何在一个字符串中找出最长回文子串呢,可以以每一个字符为中心,向两边寻找回文子串,在遍历完整个数组后,就可以找到最长的回文子串。但是这个方法的时间复杂度为 O(n*n),并不是很高效,下面我们来看时间复杂度为 O(n)的马拉车算法。

由于回文串的长度可奇可偶,比如 “bob” 是奇数形式的回文,”noon” 就是偶数形式的回文,马拉车算法的第一步是预处理,做法是在每一个字符的左右都加上一个特殊字符,比如加上 ‘#’,那么

bob –> #b#o#b#

noon –> #n#o#o#n#

这样做的好处是不论原字符串是奇数还是偶数个,处理之后得到的字符串的个数都是奇数个,这样就不用分情况讨论了,而可以一起搞定。接下来我们还需要和处理后的字符串t等长的数组p,其中 p[i] 表示以 t[i] 字符为中心的回文子串的半径,若 p[i] = 1,则该回文子串就是 t[i] 本身,那么我们来看一个简单的例子:

1
2
# 1 # 2 # 2 # 1 # 2 # 2 #
1 2 1 2 5 2 1 6 1 2 3 2 1

为啥我们关心回文子串的半径呢?看上面那个例子,以中间的 ‘1’ 为中心的回文子串 “#2#2#1#2#2#” 的半径是6,而未添加#号的回文子串为 “22122”,长度是5,为半径减1。这是个普遍的规律么?我们再看看之前的那个 “#b#o#b#”,我们很容易看出来以中间的 ‘o’ 为中心的回文串的半径是4,而 “bob”的长度是3,符合规律。再来看偶数个的情况 “noon”,添加#号后的回文串为 “#n#o#o#n#”,以最中间的 ‘#’ 为中心的回文串的半径是5,而 “noon” 的长度是4,完美符合规律。所以我们只要找到了最大的半径,就知道最长的回文子串的字符个数了。只知道长度无法定位子串,我们还需要知道子串的起始位置。

我们还是先来看中间的 ‘1’ 在字符串 “#1#2#2#1#2#2#” 中的位置是7,而半径是6,貌似 7-6=1,刚好就是回文子串 “22122” 在原串 “122122” 中的起始位置1。那么我们再来验证下 “bob”,”o” 在 “#b#o#b#” 中的位置是3,但是半径是4,这一减成负的了,肯定不对。所以我们应该至少把中心位置向后移动一位,才能为0啊,那么我们就需要在前面增加一个字符,这个字符不能是#号,也不能是s中可能出现的字符,所以我们暂且就用美元号吧,毕竟是博主最爱的东西嘛。这样都不相同的话就不会改变p值了,那么末尾要不要对应的也添加呢,其实不用的,不用加的原因是字符串的结尾标识为 ‘\0’,等于默认加过了。那此时 “o” 在 “$#b#o#b#” 中的位置是4,半径是4,一减就是0了,貌似没啥问题。我们再来验证一下那个数字串,中间的 ‘1’ 在字符串 “$#1#2#2#1#2#2#” 中的位置是8,而半径是6,这一减就是2了,而我们需要的是1,所以我们要除以2。之前的 “bob” 因为相减已经是0了,除以2还是0,没有问题。再来验证一下 “noon”,中间的 ‘#’ 在字符串 “$#n#o#o#n#” 中的位置是5,半径也是5,相减并除以2还是0,完美。可以任意试试其他的例子,都是符合这个规律的,最长子串的长度是半径减1,起始位置是中间位置减去半径再除以2。

那么下面我们就来看如何求p数组,需要新增两个辅助变量 mx 和 id,其中 id 为能延伸到最右端的位置的那个回文子串的中心点位置,mx 是回文串能延伸到的最右端的位置,需要注意的是,这个 mx 位置的字符不属于回文串,所以才能用 mx-i 来更新 p[i] 的长度而不用加1,由 mx 的更新方式 mx = i + p[i] 也能看出来 mx 是不在回文串范围内的,这个算法的最核心的一行如下:

1
p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;

可以这么说,这行要是理解了,那么马拉车算法基本上就没啥问题了,那么这一行代码拆开来看就是

如果 mx > i, 则p[i] = min( p[2 * id - i] , mx - i )

否则,p[i] = 1

mx - i > P[j]的时候,以 S[j] 为中心的回文子串包含在以 S[id] 为中心的回文子串中,由于 i 和 j 对称,以 S[i] 为中心的回文子串必然包含在以 S[id] 为中心的回文子串中,所以必有 P[i] = P[j],其中 j = 2*id - i,因为 j 到 id 之间到距离等于 id 到 i 之间到距离,为 i - id,所以 j = id - (i - id) = 2*id - i

P[j] >= mx - i的时候,以 S[j] 为中心的回文子串不一定完全包含于以 S[id] 为中心的回文子串中,但是基于对称性可知,下图中两个绿框所包围的部分是相同的,也就是说以 S[i] 为中心的回文子串,其向右至少会扩张到 mx 的位置,也就是说P[i] = mx - i。至于 mx 之后的部分是否对称,就只能老老实实去匹配了,这就是后面紧跟到 while 循环的作用。

对于 mx <= i 的情况,无法对 P[i] 做更多的假设,只能 P[i] = 1,然后再去匹配了。

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
class Solution {
public:
string longestPalindrome(string s) {
int len = s.length(), lens = len*2+2, id = 0, maxx = 0, reslen = 0;
int start;

vector<int> p(lens, 0);
string ss(lens, ' ');
ss[0] = '$';
ss[1] = '#';
for (int i = 0; i < len; i ++) {
ss[i*2+2] = s[i];
ss[i*2+3] = '#';
}
for (int i = 1; i < lens; i ++) {
if (maxx > i)
p[i] = min(p[2*id - i], maxx - i);
else
p[i] = 1;
while(ss[i+p[i]] == ss[i-p[i]])
p[i] ++;
if (p[i]+i > maxx) {
maxx = p[i] + i;
id = i;
}
if (p[i] > reslen) {
reslen = p[i];
start = i;
}
}

return s.substr((start-reslen)/2, reslen-1);
}
};

Leetcode6. ZigZag Conversion

The string “PAYPALISHIRING” is written in a zigzag pattern on a given number of rows like this: (you may want to display this pattern in a fixed font for better legibility)

1
2
3
P   A   H   N
A P L S I I G
Y I R

And then read line by line: “PAHNAPLSIIGYIR”

Write the code that will take a string and make this conversion given a number of rows:

string convert(string s, int numRows);
Example 1:

1
2
Input: s = "PAYPALISHIRING", numRows = 3
Output: "PAHNAPLSIIGYIR"

Example 2:

1
2
3
4
5
6
7
8
Input: s = "PAYPALISHIRING", numRows = 4
Output: "PINALSIGYAHRPI"
Explanation:

P I N
A L S I G
Y A H R
P I

建立一个大小为 numRows 的字符串数组,为的就是把之字形的数组整个存进去,然后再把每一行的字符拼接起来,就是想要的结果了。顺序就是按列进行遍历,首先前 numRows 个字符就是按顺序存在每行的第一个位置,然后就是 ‘之’ 字形的连接位置了,可以发现其实都是在行数区间 [1, numRows-2] 内,只要按顺序去取字符就可以了,最后把每行都拼接起来即为所求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
string convert(string s, int numRows) {
if (numRows <= 1) return s;
string res;
int i = 0, n = s.size();
vector<string> vec(numRows);
while(i < n) {
for (int pos = 0; pos < numRows && i < n; ++pos)
vec[pos] += s[i++];
for (int pos = numRows - 2; pos >= 1 && i < n; --pos)
vec[pos] += s[i++];
}
for (auto &a : vec) res += a;
return res;
}
};

Leetcode7. Reverse Integer

Given a 32-bit signed integer, reverse digits of an integer.

Example 1:

1
2
Input: 123
Output: 321

Example 2:

1
2
Input: -123
Output: -321

Example 3:

1
2
Input: 120
Output: 21

Note:

  • Assume we are dealing with an environment which could only store integers within the 32-bit signed integer range: [−231, 231 − 1]. For the purpose of this problem, assume that your function returns 0 when the reversed integer overflows.

翻转数字问题需要注意的就是溢出问题,看了许多网上的解法,由于之前的 OJ 没有对溢出进行测试,所以网上很多人的解法没有处理溢出问题也能通过 OJ。现在 OJ 更新了溢出测试,所以还是要考虑到。为什么会存在溢出问题呢,由于int型的数值范围是 -2147483648~2147483647, 那么如果要翻转 1000000009 这个在范围内的数得到 9000000001,而翻转后的数就超过了范围。博主最开始的想法是,用 long 型数据,其数值范围为 -9223372036854775808~9223372036854775807, 远大于 int 型这样就不会出现溢出问题。但实际上 OJ 给出的官方解答并不需要使用 long,一看比自己的写的更精简一些,它没有特意处理正负号,仔细一想,果然正负号不影响计算,而且没有用 long 型数据,感觉写的更好一些,那么就贴出来吧:

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int reverse(int x) {
int res = 0;
while (x != 0) {
if (abs(res) > INT_MAX / 10) return 0;
res = res * 10 + x % 10;
x /= 10;
}
return res;
}
};

在贴出答案的同时,OJ 还提了一个问题 To check for overflow/underflow, we could check if ret > 214748364 or ret < –214748364 before multiplying by 10. On the other hand, we do not need to check if ret == 214748364, why? (214748364 即为 INT_MAX / 10)

为什么不用 check 是否等于 214748364 呢,因为输入的x也是一个整型数,所以x的范围也应该在 -2147483648~2147483647 之间,那么x的第一位只能是1或者2,翻转之后 res 的最后一位只能是1或2,所以 res 只能是 2147483641 或 2147483642 都在 int 的范围内。但是它们对应的x为 1463847412 和 2463847412,后者超出了数值范围。所以当过程中 res 等于 214748364 时, 输入的x只能为 1463847412, 翻转后的结果为 2147483641,都在正确的范围内,所以不用 check。

我们也可以用 long 型变量保存计算结果,最后返回的时候判断是否在 int 返回内,但其实题目中说了只能存整型的变量,所以这种方法就只能当个思路扩展了,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
int reverse(int x) {
long res = 0;
while (x != 0) {
res = 10 * res + x % 10;
x /= 10;
}
return (res > INT_MAX || res < INT_MIN) ? 0 : res;
}
};

Leetcode8. String to Integer (atoi)

Implement atoi which converts a string to an integer.

The function first discards as many whitespace characters as necessary until the first non-whitespace character is found. Then, starting from this character, takes an optional initial plus or minus sign followed by as many numerical digits as possible, and interprets them as a numerical value.

The string can contain additional characters after those that form the integral number, which are ignored and have no effect on the behavior of this function.

If the first sequence of non-whitespace characters in str is not a valid integral number, or if no such sequence exists because either str is empty or it contains only whitespace characters, no conversion is performed.

If no valid conversion could be performed, a zero value is returned.

Note:

Only the space character ‘ ‘ is considered as whitespace character.
Assume we are dealing with an environment which could only store integers within the 32-bit signed integer range: [−231, 231 − 1]. If the numerical value is out of the range of representable values, INT_MAX (231 − 1) or INT_MIN (−231) is returned.

Example 1:

1
2
Input: "42"
Output: 42

Example 2:

1
2
3
4
Input: "   -42"
Output: -42
Explanation: The first non-whitespace character is '-', which is the minus sign.
Then take as many numerical digits as possible, which gets 42.

Example 3:

1
2
3
Input: "4193 with words"
Output: 4193
Explanation: Conversion stops at digit '3' as the next character is not a numerical digit.

Example 4:

1
2
3
4
Input: "words and 987"
Output: 0
Explanation: The first non-whitespace character is 'w', which is not a numerical
digit or a +/- sign. Therefore no valid conversion could be performed.

Example 5:

1
2
3
4
Input: "-91283472332"
Output: -2147483648
Explanation: The number "-91283472332" is out of the range of a 32-bit signed integer.
Thefore INT_MIN (−231) is returned.

Example 6:

1
2
Input: "+1"
Output: 1

实现一个字符串转数字的函数。只需要返回位于字符串前缀最长的一个数字,如-12a就返回-12。除了正常情况还有很多的corner case,下面列举一下可能的情况:

  • 忽略前缀空格;
  • 一个数字前只能有一个单元运算符(负号或者正号);
  • 读到非数字之后的余串忽略;
  • 当数字不小于2147483647,认为正溢出,直接返回2147483647;
  • 当数字不大于-2147483648,认为负溢出,直接返回-2147483648;
  • 最好用long long存储数据避免溢出。

本来是直接处理的,没想到corner case太多了,所以学习人家的办法先过滤一下前缀再处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int myAtoi(string str) {
long long result=0;
int i=0;
while(i<str.length() && str[i]==' ')
i++;
if (!isdigit (str[i]) && str[i] != '+' && str[i] != '-')
return 0;
bool islittle = (str[i] == '-' ? true : false);
if (!isdigit(str[i]))
i++;
for(;i<str.length();i++) {
if(str[i]<'0' || str[i]>'9')
break;
if(str[i]>='0' && str[i]<='9') {
result = result * 10 + str[i]-'0';
if (!islittle && result >= 2147483647) return 2147483647;
if (islittle && -result <= -2147483648) return -2147483648;
}
}
if(islittle)
result = -result;
return result;
}

Leetcode9. Palindrome Number

Determine whether an integer is a palindrome. An integer is a palindrome when it reads the same backward as forward.

Example 1:

1
2
Input: 121
Output: true

Example 2:

1
2
3
Input: -121
Output: false
Explanation: From left to right, it reads -121. From right to left, it becomes 121-. Therefore it is not a palindrome.

Example 3:

1
2
3
Input: 10
Output: false
Explanation: Reads 01 from right to left. Therefore it is not a palindrome.

Follow up: Could you solve it without converting the integer to a string?

回文数,如果是负数直接返回false,正数的话转成string再判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
bool isPalindrome(int x) {
int i=0;
if(x<0)
return false;
int length = 0;
int str[100000];
while(x>0){
str[i++]=(x%10);
x/=10;
}
int middle = i/2;
int j=0;
while(j<middle){
if(str[j]!=str[i-1-j])
return false;
j++;
}
return true;
}
};

Leetcode10. Regular Expression Matching

Given an input string (s) and a pattern (p), implement regular expression matching with support for ‘.’ and ‘*’.

  • ‘.’ Matches any single character.
  • ‘*’ Matches zero or more of the preceding element.
    The matching should cover the entire input string (not partial).

Note:

  • s could be empty and contains only lowercase letters a-z.
  • p could be empty and contains only lowercase letters a-z, and characters like . or *.

Example 1:

1
2
3
4
5
Input:
s = "aa"
p = "a"
Output: false
Explanation: "a" does not match the entire string "aa".

Example 2:

1
2
3
4
5
Input:
s = "aa"
p = "a*"
Output: true
Explanation: '*' means zero or more of the precedeng element, 'a'. Therefore, by repeating 'a' once, it becomes "aa".

Example 3:

1
2
3
4
5
Input:
s = "ab"
p = ".*"
Output: true
Explanation: ".*" means "zero or more (*) of any character (.)".

Example 4:

1
2
3
4
5
Input:
s = "aab"
p = "c*a*b"
Output: true
Explanation: c can be repeated 0 times, a can be repeated 1 time. Therefore it matches "aab".

Example 5:

1
2
3
4
Input:
s = "mississippi"
p = "mis*is*p*."
Output: false

这道求正则表达式匹配的题和那道 Wildcard Matching 的题很类似,不同点在于*的意义不同,在之前那道题中,表示可以代替任意个数的字符,而这道题中的*表示之前那个字符可以有0个,1个或是多个,就是说,字符串 ab,可以表示b或是 aaab,即a的个数任意,这道题的难度要相对之前那一道大一些,分的情况的要复杂一些,需要用递归 Recursion 来解,大概思路如下:

  • 若p为空,若s也为空,返回 true,反之返回 false。

  • 若p的长度为1,若s长度也为1,且相同或是p为 ‘.’ 则返回 true,反之返回 false。

  • 若p的第二个字符不为*,若此时s为空返回 false,否则判断首字符是否匹配,且从各自的第二个字符开始调用递归函数匹配。

  • 若p的第二个字符为*,进行下列循环,条件是若s不为空且首字符匹配(包括 p[0] 为点),调用递归函数匹配s和去掉前两个字符的p(这样做的原因是假设此时的星号的作用是让前面的字符出现0次,验证是否匹配),若匹配返回 true,否则s去掉首字母(因为此时首字母匹配了,我们可以去掉s的首字母,而p由于星号的作用,可以有任意个首字母,所以不需要去掉),继续进行循环。

  • 返回调用递归函数匹配s和去掉前两个字符的p的结果(这么做的原因是处理星号无法匹配的内容,比如 s=ab, p=a*b,直接进入 while 循环后,我们发现 “ab” 和 “b” 不匹配,所以s变成 “b”,那么此时跳出循环后,就到最后的 return 来比较 “b” 和 “b” 了,返回 true。再举个例子,比如 s=””, p=”a*”,由于s为空,不会进入任何的 if 和 while,只能到最后的 return 来比较了,返回 true,正确)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
bool isMatch(string s, string p) {
if (p.empty()) return s.empty();
if (p.size() == 1) {
return (s.size() == 1 && (s[0] == p[0] || p[0] == '.'));
}
if (p[1] != '*') {
if (s.empty()) return false;
return (s[0] == p[0] || p[0] == '.') && isMatch(s.substr(1), p.substr(1));
}
while (!s.empty() && (s[0] == p[0] || p[0] == '.')) {
if (isMatch(s, p.substr(2))) return true;
s = s.substr(1);
}
return isMatch(s, p.substr(2));
}
};

上面的方法可以写的更加简洁一些,但是整个思路还是一样的,先来判断p是否为空,若为空则根据s的为空的情况返回结果。当p的第二个字符为号时,由于号前面的字符的个数可以任意,可以为0,那么我们先用递归来调用为0的情况,就是直接把这两个字符去掉再比较,或者当s不为空,且第一个字符和p的第一个字符相同时,再对去掉首字符的s和p调用递归,注意p不能去掉首字符,因为号前面的字符可以有无限个;如果第二个字符不为号,那么就老老实实的比较第一个字符,然后对后面的字符串调用递归,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
bool isMatch(string s, string p) {
if (p.empty()) return s.empty();
if (p.size() > 1 && p[1] == '*') {
return isMatch(s, p.substr(2)) || (!s.empty() && (s[0] == p[0] || p[0] == '.') && isMatch(s.substr(1), p));
} else {
return !s.empty() && (s[0] == p[0] || p[0] == '.') && isMatch(s.substr(1), p.substr(1));
}
}
};

我们也可以用 DP 来解,定义一个二维的 DP 数组,其中 dp[i][j] 表示 s[0,i) 和 p[0,j) 是否 match,然后有下面三种情况(下面部分摘自这个帖子):

  1. P[i][j] = P[i - 1][j - 1], if p[j - 1] != ‘*’ && (s[i - 1] == p[j - 1] || p[j - 1] == ‘.’);
  2. P[i][j] = P[i][j - 2], if p[j - 1] == ‘*’ and the pattern repeats for 0 times;
  3. P[i][j] = P[i - 1][j] && (s[i - 1] == p[j - 2] || p[j - 2] == ‘.’), if p[j - 1] == ‘*’ and the pattern repeats for at least 1 times.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
bool isMatch(string s, string p) {
int m = s.size(), n = p.size();
vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
dp[0][0] = true;
for (int i = 0; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (j > 1 && p[j - 1] == '*') {
dp[i][j] = dp[i][j - 2] || (i > 0 && (s[i - 1] == p[j - 2] || p[j - 2] == '.') && dp[i - 1][j]);
} else {
dp[i][j] = i > 0 && dp[i - 1][j - 1] && (s[i - 1] == p[j - 1] || p[j - 1] == '.');
}
}
}
return dp[m][n];
}
};

从视频里看来的dp做法,更加好懂

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
class Solution {
public:
bool isMatch(string s, string p) {
int slen = s.length(), plen = p.length();
vector<vector<bool> > f(slen+1, vector<bool>(plen+1, false));
s.insert(s.begin(), '0');
p.insert(p.begin(), '0');

f[0][0] = true;

for (int i = 1; i <= plen; i ++)
if (p[i] == '*') f[0][i] = f[0][i-1];
else if (i+1 > plen || p[i+1] != '*') break;
else f[0][i] = true; // p[i], *

for (int i = 1; i <= slen; i ++) {
for (int j = 1; j <= plen; j ++) {
if (p[j] == '*') {
f[i][j] = f[i][j-1];
continue;
}
if (j+1 <= plen && p[j+1] == '*') {
// s[i]可能有三种:
// 空
// 一个p[j]
// 若干个p[j]
f[i][j] = f[i][j-1] ||
`` (f[i-1][j-1] && (p[j] == '.' || s[i] == p[j])) ||
(f[i-1][j] && (p[j] == '.' || s[i] == p[j]));
}
else {
f[i][j] = f[i-1][j-1] && (p[j] == '.' || s[i] == p[j]);
}
}
}
return f[slen][plen];
}
};

Leetcode11. Container With Most Water

Given n non-negative integers a1, a2, …, an , where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of line i is at (i, ai) and (i, 0). Find two lines, which together with x-axis forms a container, such that the container contains the most water.

Note: You may not slant the container and n is at least 2.

The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49.

Example:

Input: [1,8,6,2,5,4,8,3,7]
Output: 49

给定 n 个非负整数 (a1, a2, …, an),每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条竖直线,竖直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明: 你不能倾斜容器,且 n 的值至少为 2。

图中垂直线代表输入数组 [3,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

这道题在 LeetCode 中排序很靠前,相信你们都见过这道题目。题意理解起来很简单,但要做出来并不容易,尤其是得到O(n)时间复杂度的解法。即使看了答案知道了 O(n) 解法怎么做,也不一定能证明它的正确性。

双指针解法:和 Two Sum II 类似,这道题的搜索空间大小是 O(n^2) 数量级。暴力法一次考察搜索空间中的一个情况,时间复杂度自然也是 O(n^2) 。而我们希望用一种方法,一次排除多个情况,从而减少时间复杂度。

在一开始,我们考虑相距最远的两个柱子所能容纳水的面积。水的宽度是两根柱子之间的距离 d=8;水的高度取决于两根柱子之间较短的那个,即左边柱子的高度 h=3 。水的面积就是 8*3=24。

如果选择固定一根柱子,另外一根变化,水的面积会有什么变化吗?稍加思考可得:

  • 当前柱子是最两侧的柱子,水的宽度 为最大,其他的组合,水的宽度都比这个小。
  • 左边柱子较短,决定了水的高度为 3。如果移动左边的柱子,新的水面高度不确定,一定不会超过右边的柱子高度 7。
  • 如果移动右边的柱子,新的水面高度一定不会超过左边的柱子高度 3,也就是不会超过现在的水面高度。

由此可见,如果固定左边的柱子,移动右边的柱子,那么水的高度一定不会增加,且宽度一定减少,所以水的面积一定减少。这个时候,左边的柱子和任意一个其他柱子的组合,其实都可以排除了。也就是我们可以排除掉左边的柱子了。

排除左边这个柱子的操作,对应于双指针解法的代码,就是指针向右移动一位。对应于搜索空间,就是削减了一行的搜索空间,如下图所示。

削减一行的搜索空间

可以看到,这个搜索空间的削减方式和 Two Sum II 问题中的形状如出一辙(其实就是我把上一篇文章里的图直接搬过来了),如果你理解了 Two Sum II 问题,那一定能秒懂这道题。

同样的道理,假设两根柱子是右边的较短,我们就可以排除掉右边的柱子,削减一列的搜索空间,如下图所示。


削减一列的搜索空间

这样,经过 步以后,我们就能排除所有的搜索空间,检查完所有的可能性。

那么,我们最终就写出了这样的双指针代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public int maxArea(int[] height) {
int res = 0;
int i = 0;
int j = height.length - 1;
while (i < j) {
int area = (j - i) * Math.min(height[i], height[j]);
res = Math.max(res, area);
if (height[i] < height[j]) {
i++;
} else {
j--;
}
}
return res;
}

实话说,很少有人能在第一次接触到这一题的时候就立即想出这样巧妙的双指针解法,所以刷题提升的过程一定是伴随着“记答案”的。但是我们同时还要善于归纳和总结,因为死记硬背是个苦工夫,只有理解了思想,才能记得快、记得牢。

就比如 167 题的 Two Sum II 和这道题。两者都是用这样的双指针解法,从代码上看非常相似,但它们究竟为何相似呢?实际上,两道题就是因为削减搜索空间的原理相通,解题思路实际上是一模一样的。如果你能洞察这一点,那么距离举一反三也就不远了。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int maxArea(vector<int>& height) {
int res = 0, i = 0, j = height.size() - 1;
while (i < j) {
int h = min(height[i], height[j]);
res = max(res, h * (j - i));
while (i < j && h == height[i]) ++i;
while (i < j && h == height[j]) --j;
}
return res;
}
};

Leetcode12. Integer to Roman

Roman numerals are represented by seven different symbols: I, V, X, L, C, D and M.

1
2
3
4
5
6
7
8
Symbol       Value
I 1
V 5
X 10
L 50
C 100
D 500
M 1000

For example, two is written as II in Roman numeral, just two one’s added together. Twelve is written as, XII, which is simply X + II. The number twenty seven is written as XXVII, which is XX + V + II.

Roman numerals are usually written largest to smallest from left to right. However, the numeral for four is not IIII. Instead, the number four is written as IV. Because the one is before the five we subtract it making four. The same principle applies to the number nine, which is written as IX. There are six instances where subtraction is used:

  • I can be placed before V (5) and X (10) to make 4 and 9.
  • X can be placed before L (50) and C (100) to make 40 and 90.
  • C can be placed before D (500) and M (1000) to make 400 and 900.

Given an integer, convert it to a roman numeral. Input is guaranteed to be within the range from 1 to 3999.

Example 1:

1
2
Input: 3
Output: "III"

Example 2:

1
2
Input: 4
Output: "IV"

Example 3:

1
2
Input: 9
Output: "IX"

Example 4:

1
2
3
Input: 58
Output: "LVIII"
Explanation: L = 50, V = 5, III = 3.

Example 5:

1
2
3
Input: 1994
Output: "MCMXCIV"
Explanation: M = 1000, CM = 900, XC = 90 and IV = 4.

例如整数 1437 的罗马数字为 MCDXXXVII, 我们不难发现,千位,百位,十位和个位上的数分别用罗马数字表示了。 1000 - M, 400 - CD, 30 - XXX, 7 - VII。所以我们要做的就是用取商法分别提取各个位上的数字,然后分别表示出来。可以分为四类,100 到 300 一类,400 一类,500 到 800 一类,900 最后一类。每一位上的情况都是类似的,代码如下:

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
class Solution {
public:
string intToRoman(int num) {
string res = "";
vector<char> roman{'M', 'D', 'C', 'L', 'X', 'V', 'I'};
vector<int> value{1000, 500, 100, 50, 10, 5, 1};
for(int i = 0; i < 7; i += 2) {
int temp = num / value[i];
num = num % value[i];
if(temp < 4)
for(int ii = 0; ii < temp; ii ++)
res = res + roman[i];
else if(temp == 4) {
res = res + roman[i] + roman[i-1];
}
else if(temp > 4 && temp < 9) {
res = res + roman[i-1];
for(int ii = 6; ii <= temp; ii ++)
res = res + roman[i];
}
else if(temp == 9)
res = res + roman[i] + roman[i-2];
}
return res;
}
};

Leetcode13. Roman to Integer

Roman numerals are represented by seven different symbols: I, V, X, L, C, D and M.

Symbol Value
I 1
V 5
X 10
L 50
C 100
D 500
M 1000

For example, two is written as II in Roman numeral, just two one’s added together. Twelve is written as, XII, which is simply X + II. The number twenty seven is written as XXVII, which is XX + V + II.

Roman numerals are usually written largest to smallest from left to right. However, the numeral for four is not IIII. Instead, the number four is written as IV. Because the one is before the five we subtract it making four. The same principle applies to the number nine, which is written as IX. There are six instances where subtraction is used:

I can be placed before V (5) and X (10) to make 4 and 9.
X can be placed before L (50) and C (100) to make 40 and 90.
C can be placed before D (500) and M (1000) to make 400 and 900.
Given a roman numeral, convert it to an integer. Input is guaranteed to be within the range from 1 to 3999.

Example 1:

1
2
Input: "III"
Output: 3

Example 2:

1
2
Input: "IV"
Output: 4

Example 3:

1
2
Input: "IX"
Output: 9

Example 4:

1
2
3
Input: "LVIII"
Output: 58
Explanation: L = 50, V= 5, III = 3.

Example 5:

1
2
3
Input: "MCMXCIV"
Output: 1994
Explanation: M = 1000, CM = 900, XC = 90 and IV = 4.

题目描述:通过给定Roman数字,得到我们的Integer

解题思路:首先给出Roman计数规则 计数规则: 相同的数字连写,所表示的数等于这些数字相加得到的数,例如:III = 3 小的数字在大的数字右边,所表示的数等于这些数字相加得到的数,例如:VIII = 8 小的数字,限于(I、X和C)在大的数字左边,所表示的数等于大数减去小数所得的数, 例如:IV = 4 正常使用时,连续的数字重复不得超过三次 在一个数的上面画横线,表示这个数扩大1000倍(本题只考虑3999以内的数,所以用不到这条规则)

罗马数字共有7个,即I(1)、V(5)、X(10)、L(50)、C(100)、D(500)和M(1000)。按照下述的规则可以表示任意正整数。需要注意的是罗马数字中没有“0”,与进位制无关。一般认为罗马数字只用来记数,而不作演算。

重复数次:一个罗马数字重复几次,就表示这个数的几倍。右加左减:在较大的罗马数字的右边记上较小的罗马数字,表示大数字加小数字。在较大的罗马数字的左边记上较小的罗马数字,表示大数字减小数字。左减的数字有限制,仅限于I、X、C。比如45不可以写成VL,只能是XLV但是,左减时不可跨越一个位数。比如,99不可以用IC()表示,而是用XCIX()表示。(等同于阿拉伯数字每位数字分别表示。)左减数字必须为一位,比如8写成VIII,而非IIX。右加数字不可连续超过三位,比如14写成XIV,而非XIIII。(见下方“数码限制”一项。)加线乘千:在罗马数字的上方加上一条横线或者加上下标的Ⅿ,表示将这个数乘以1000,即是原数的1000倍。同理,如果上方有两条横线,即是原数的1000000()倍。数码限制:同一数码最多只能出现三次,如40不可表示为XXXX,而要表示为XL。例外:由于IV是古罗马神话主神朱庇特(即IVPITER,古罗马字母里没有J和U)的首字,因此有时用IIII代替IV。

这道题好就好在没有让我们来验证输入字符串是不是罗马数字,这样省掉不少功夫。需要用到 HashMap 数据结构,来将罗马数字的字母转化为对应的整数值,因为输入的一定是罗马数字,那么只要考虑两种情况即可:
第一,如果当前数字是最后一个数字,或者之后的数字比它小的话,则加上当前数字。
第二,其他情况则减去这个数字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
int romanToInt(string s) {
map<char, int> big;
int res = 0;
big.insert(pair<char, int>('I',1));
big.insert(pair<char, int>('V',2));
big.insert(pair<char, int>('X',3));
big.insert(pair<char, int>('L',4));
big.insert(pair<char, int>('C',5));
big.insert(pair<char, int>('D',6));
big.insert(pair<char, int>('M',7));
int small[7]={1, 5, 10, 50, 100, 500, 1000};

for(int i=0;i<s.size()-1;i++) {
if(big[s[i]] >= big[s[i+1]])
res += small[big[s[i]]-1];
else
res -= small[big[s[i]]-1];
}
res += small[big[s[s.size()-1]]-1];
return res;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int romanToInt(string s) {
int map[26];
map['I'-'A'] = 1; map['V'-'A'] = 5; map['X'-'A'] = 10; map['L'-'A'] = 50;
map['C'-'A'] = 100; map['D'-'A'] = 500; map['M'-'A'] = 1000;
int res = 0, n = s.size();
s.push_back(s[n-1]);
for(int i = 0; i < n; i++)
{
if(map[s[i]-'A'] >= map[s[i+1]-'A'])
res += map[s[i]-'A'];
else res -= map[s[i]-'A'];
}
return res;
}
};

Leetcode14. Longest Common Prefix

Write a function to find the longest common prefix string amongst an array of strings.

If there is no common prefix, return an empty string “”.

Example 1:

1
2
Input: ["flower","flow","flight"]
Output: "fl"

Example 2:

1
2
3
Input: ["dog","racecar","car"]
Output: ""
Explanation: There is no common prefix among the input strings.

Note:

  • All given inputs are in lowercase letters a-z.

这道题让我们求一系列字符串的共同前缀,没有什么特别的技巧,无脑查找即可,定义两个变量i和j,其中i是遍历搜索字符串中的字符,j是遍历字符串集中的每个字符串。这里将单词上下排好,则相当于一个各行长度有可能不相等的二维数组,遍历顺序和一般的横向逐行遍历不同,而是采用纵向逐列遍历,在遍历的过程中,如果某一行没有了,说明其为最短的单词,因为共同前缀的长度不能长于最短单词,所以此时返回已经找出的共同前缀。每次取出第一个字符串的某一个位置的单词,然后遍历其他所有字符串的对应位置看是否相等,如果有不满足的直接返回 res,如果都相同,则将当前字符存入结果,继续检查下一个位置的字符,参见代码如下:

C++ 解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if (strs.empty()) return "";
string res = "";
for (int j = 0; j < strs[0].size(); ++j) {
char c = strs[0][j];
for (int i = 1; i < strs.size(); ++i) {
if (j >= strs[i].size() || strs[i][j] != c) {
return res;
}
}
res.push_back(c);
}
return res;
}
};

我们可以对上面的方法进行适当精简,如果发现当前某个字符和第一个字符串对应位置的字符不相等,说明不会再有更长的共同前缀了,直接通过用 substr 的方法直接取出共同前缀的子字符串。如果遍历结束前没有返回结果的话,说明第一个单词就是公共前缀,返回为结果即可。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if (strs.empty()) return "";
for (int j = 0; j < strs[0].size(); ++j) {
for (int i = 0; i < strs.size(); ++i) {
if (j >= strs[i].size() || strs[i][j] != strs[0][j]) {
return strs[i].substr(0, j);
}
}
}
return strs[0];
}
};

我们再来看一种解法,这种方法给输入字符串数组排了个序,想想这样做有什么好处?既然是按字母顺序排序的话,那么有共同字母多的两个字符串会被排到一起,而跟大家相同的字母越少的字符串会被挤到首尾两端,那么如果有共同前缀的话,一定会出现在首尾两端的字符串中,所以只需要找首尾字母串的共同前缀即可。比如例子1排序后为 [“flight”, “flow”, “flower”],例子2排序后为 [“cat”, “dog”, “racecar”],虽然例子2没有共同前缀,但也可以认为共同前缀是空串,且出现在首尾两端的字符串中。由于是按字母顺序排的,而不是按长度,所以首尾字母的长度关系不知道,为了防止溢出错误,只遍历而这种较短的那个的长度,找出共同前缀返回即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if (strs.empty()) return "";
sort(strs.begin(), strs.end());
int i = 0, len = min(strs[0].size(), strs.back().size());
while (i < len && strs[0][i] == strs.back()[i]) ++i;
return strs[0].substr(0, i);
}
};

Leetcode15. 3Sum

Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.

Note: The solution set must not contain duplicate triplets.

Example:

1
2
3
4
5
6
7
Given array nums = [-1, 0, 1, 2, -1, -4],

A solution set is:
[
[-1, 0, 1],
[-1, -1, 2]
]

给定一个数组A,要求从A中找出这么三个元素a,b,c使得a + b + c = 0,返回由这样的a、b、c构成的三元组,且要保证三元组是唯一的。(即任意的两个三元组,它们里面的元素不能完全相同)

题解:

我们知道3和问题是由2和问题演化而来的,所以说我们可以根据2和问题的求法,来间接求解三和问题。常见的2和问题的求解方法,主要包括两种那:利用哈希表或者两用双指针。而三和问题,我们可以看成是在2和问题外面加上一层for循环,所以3和问题的常用解法也是分为两种:即利用哈希表和利用双指针。下面具体介绍两种方法:

方法1:利用哈希表

这种方法的基本思想是,将数组中每个元素和它的下标构成一个键值对存入到哈希表中,在寻找的过程中对于数组中的某两个元素a、b只需在哈希表中判断是否存在-a-b即可,由于在哈希表中的查找操作的时间复杂度为O(1),在数组中寻找寻任意的找两个元素a、b需要O(n^2),故总的时间复杂度为O(N^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
class Solution
{
public:
vector<vector<int> > threeSum(vector<int> &num)
{
vector<vector<int>> rs;
int len = num.size();
if(len == 0)
return rs;
sort(num.begin(),num.end());//排序是为了不重复处理后续重复出现的元素
for(int i = 0; i < len; i++)
{
if(i != 0 && num[i] == num[i - 1])//i重复出现时不重复处理
continue;
unordered_map<int,int> _map;//注意建立_map的位置
for(int j = i + 1; j < len; j++)
{
if(_map.find(-num[i]-num[j]) != _map.end())
{
rs.push_back({num[i],num[j],-num[i]-num[j]});
while(j + 1 < len && num[j] == num[j + 1])//j重复出现时不重复处理
j++;
}
_map.insert({num[j],j});//注意_map插入的元素是根据j来的不是根据i来的
}
}
return rs;
}
};

这种方法先对数组nums进行排序,然后在双重for循环中对哈希表进行操作,时间复杂度为O(N*logN)+O(N^2),所以总的时间复杂度为O(N^2),空间复杂度为O(N),典型的以时间换空间的策略。但是,有几个重要的点一定要掌握:

1.为什么要事先对数组nums进行排序?这是因为由于题目要求的是返回的三元组必须是重复的,如果直接利用哈希表不进行特殊处理的话,最终的三元组一定会包含重复的情况,所以我们对数组进行排序是为了对最终的结果进行去重,其中去重包括i重复的情况和j重复的情况分,不注意两种情况的处理方式是不同的,i是判断与i-1是否相同;而j是判断与j+1是否相同。

2.关于对三元组进行去重,实际上有两种方式:

(1)按照本例中的形式,先对数组进行排序,在遍历的过程中遇到重复元素的情况就跳过。

(2)不对数组事先排序,在遍历过程中不进行特殊的处理,在得到整个三元组集合后,在对集合中的三元组进行去重,删去重复的三元组。(一个简单的思路是对集合中每个三元组进行排序,然后逐个元素进行比较来判断三元组是否重复)。(这种思路可能会比本例中的方法性能更优一些)

3.注意哈希表建立的位置,是首先确定i的位置后,才开始创建哈希表的;而不是先建立哈希表,再根据i和j进行遍历。此外,哈希表中存储的元素是根据j的位置来决定的,相当于每次先固定一个i,然后建立一个新的哈希表,然后在遍历j,并根据j判断哈希表。(这个过程并不难理解,自己举个例子,画个图应该就明白了)

然而,我利用这种方法(上述代码),在leetcode上提交居然超时了!!!即方法1在leetcode没通过啊。

方法2:利用两个指针

这种方法是最常用的方法(leetcode上AC的代码大多都是这种方法),主要的思想是:必须先对数组进行排序(不排序的话,就不能利用双指针的思想了,所以说对数组进行排序是个大前提),每次固定i的位置,并利用两个指针j和k,分别指向数组的i+1位置和数组的尾元素,通过判断num[j]+num[k]与-num[i]的大小,来决定如何移动指针j和k,和leetcode上最大容器的拿到题目的思想类似。具体代码如下:

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
class Solution
{
public:
vector<vector<int> > threeSum(vector<int> &num)
{
vector<vector<int>> rs;
int len = num.size();
if(len == 0)
return rs;
sort(num.begin(),num.end());
for(int i = 0; i < len; i++)
{
int j = i + 1;
int k = len - 1;
if(i != 0 && num[i] == num[i - 1])//如果遇到重复元素的情况,避免多次考虑
continue;
while(j < k)//对于每一个num[i]从i之后的元素中,寻找对否存在三者之和为0的情况
{
if(num[i] + num[j] +num[k] == 0)//当三者之和为0的情况
{
rs.push_back({num[i],num[j],num[k]});
j++;//当此处的j,k满足时,别忘了向前/向后移动,判断下一个是否也满足
k--;
while(j < k && num[j] == num[j - 1])//如果遇到j重复的情况,也要避免重复考虑
j++;
while(j < k && num[k] == num[k + 1])//如果遇到k重复的情况,也要避免重复考虑
k--;
}
else if(num[i] + num[j] + num[k] < 0)//三者之和小于0的情况,说明num[j]太小了,需要向后移动
j++;
else//三者之和大于0的情况,说明num[k]太大了,需要向前移动
k--;
}
}
return rs;
}
};

该方法的时间复杂度为O(N*logN)+O(N^2)=O(N^2)和方法1实际上是一个数量级的,但是空间复杂度为O(1),所以说综合比较的话,还是方法2的性能更好一些。同样地,这种方法也有几个需要注意的点:

  1. 需要先对数组进行排序,一开始的时候也强调了,不排序的话整个思路就是错的;这种方法的一切都是建立在有序数组的前提下。
  2. 每次找到符合条件的num[j]和num[k]时,这时候,j指针要往前移动一次,同时k指针向后移动一次,避免重复操作,从而判断下个元素是否也符合
  3. 和方法1一样,都需要去重(且去重时,一般都是在找到满足条件的元素时才执行),由于该方法一定要求数组是有序的,所以就按照第一种去重方法来去重就好了。但是需要注意下与第1种方法去重的不同之处:
    • (1)i指针的去重同方法1一样,都是判断当前位置的元素与前一个位置的元素是否相同,如果相同,就忽略。这是因为前一个位置的元素已经处理过了,如果当前位置的元素与之相同的话,就没必要处理了,否则就会造成重复。
    • (2)j指针(还有k指针)的去重方法同方法1是不同的。先分析下方法1:

如果num[j]是符合条件的元素的话,并且下一个元素同num[j]相同的话,那么就没必要再去判断了,直接跳过就行了。那如果把nums[j] == num[j+1]改成num[j] == num[j-1]行吗?显然不行啊,举个例子就行,假如num[j] == 1且此时1正好符合,那么对于序列1,1….的话,当判断第一个1时,会把结果存入数组;如果改成num[j] == num[j-1]的话,判断第二个1的时候,会先把元素存入数组,然后再判断和前一个元素是否相同;即实际上这样已经发生重复操作了,如果是nums[j] == num[j+1]就是直接判断下一个元素,就是先判断在存储,就不会重复操作了。(也可以这样理解:由于去重操作只在找到重复元素的时候才进行,当num[j]满足时,如果num[j+1]也满足,则一定不用再判断了;而如果num[j-1]与num[j]相同的话,反而会把num[j-1]num[j]都存进去了)

分析下方法2:

对于方法2中的j指针和k指针,就比较好理解了;由于在判断是满足条件的元素的话,就会j++,k–,此时j和k的位置都发生了变化,就不知道是不是满足了,所以要根据前一个元素来判断,如果现在的元素与前一个元素(对于j来说就是j-1,对于k来说就是K+1)相同的话,就直接跳过,从而避免了重复操作。

与方法1中的j是不同的,方法1中的j并没有执行j++操作(或者说是后执行的j++)。方法2最终在leetcode上AC了,以后还是优先使用这种的方法吧!

以上问题都是针对2sum和3sum,那么对于4sum。。。ksum,上述解法也是可行的。所以对于Ksum问题来讲,通常有两种思路:

  1. 利用双指针。
  2. 利用哈希表。

这两种方法的本质都是,在外层有k-2层循环嵌套,最内层循环中采用双指针或者哈希表,所以总的时间复杂度为O(N^k-1)。对于Ksum问题,如果题目要求结果不能重复的话,一定要考虑去重,去重方法,上面第一个例子也讲了。

实际上,对于4sum问题,还有更优的解法。主要是利用哈希表,其中哈希表类为<int,vector<pair<int,int>>>型,其中key表示的是数组中任意两个元素的和,value表示的这两个元素对应下标构成的pair,即pair<i,j>,由于对于两组不同的元素(共4个)可能存在重复的和,即key值相同,所以value对应的是一个pair构成的数组。这样的话,后面只需要两次循环找出hash[target - num[i] - num[j]]即可,所以总的时间复杂为O(N^2),空间复杂度也为O(N^2)。(由于pair<int,int>本质就是个哈希表,所以这种方法的实质就是嵌套哈希表)

Leetcode16. 3Sum Closest

Given an array nums of n integers and an integer target, find three integers in nums such that the sum is closest to target. Return the sum of the three integers. You may assume that each input would have exactly one solution.

Example:

Given array nums = [-1, 2, 1, -4], and target = 1.

The sum that is closest to the target is 2. (-1 + 2 + 1 = 2).

题目分析:首先想到的是暴力解法,遍历出所有从数组中取不同的三个数的情况,比较它们与target的距离(可以用绝对值表示),然后将距离最小的一组的和输出即可。这种方法是超时的,简单分析一下,可以知道时间复杂度为O(n^3). 

通过分析,我们可以想到一种时间复杂度为O(n^2)的解法:假设数组中有len个元素,首先我们将数组中的元素按照从小到大的顺序进行排序。其次,看最终取出的三个数中的第一个数,若数组长度为n,那么有n种取法。假设取的第一个数是A[i],那么第二三两个数从A[i+1]~A[len]中取出。找到“第一个数为A[i]固定,后两个数在A[i]后面元素中取。并且三数之和离target最近的情况。”这时,我们用两个指针j,k分别指向A[i+1]A[len],如果此时三数之和A[i]+A[j]+A[k]<target,说明三数之和小了,我们将j后移一格;反之,若和大于target,则将k前移一格;直到j和k相遇为止。在这期间,保留与target最近的三数之和。一旦发现有“和等于target的情况”,立即输出即可。

由于取的第一个数可以是A[0]A[1]A[2],……, A[len-1],所以需要重复以上步骤n次。

为什么第一个数取了A[i]之后,第二三两个数只能在A[i+1]~A[len]中取呢? 因为这样可以避免重复。假设第二个数取了A[i-2],那么这样情况势必会包含在第一个数取A[i-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
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
int result = nums[0]+nums[1]+nums[2];
int dis = abs(result - target);
int len = nums.size();
if(len < 3)
return target;
sort(nums.begin(),nums.end());
for (int i = 0; i < len; i ++) {
int j = i+1, k = len-1;
while ( j < k ) {
int temp = abs(nums[i]+nums[j]+nums[k]-target);
if (temp < dis) {
dis = temp;
result = nums[i]+nums[j]+nums[k];
}
if (nums[i]+nums[j]+nums[k]<target)
j ++;
else if (nums[i]+nums[j]+nums[k]>target)
k --;
else
return target;
}
}
return result;
}
};

Leetcod17. Letter Combinations of a Phone Number

Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent.

A mapping of digit to letters (just like on the telephone buttons) is given below. Note that 1 does not map to any letters.

Example:

Input: “23”
Output: [“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”].

这道题让我们求电话号码的字母组合,即数字2到9中每个数字可以代表若干个字母,然后给一串数字,求出所有可能的组合。这里可以用递归 Recursion 来解,需要建立一个字典,用来保存每个数字所代表的字符串,然后还需要一个变量 level,记录当前生成的字符串的字符个数,实现套路和上述那些题十分类似。在递归函数中首先判断 level,如果跟 digits 中数字的个数相等了,将当前的组合加入结果 res 中,然后返回。我们通过 digits 中的数字到 dict 中取出字符串,然后遍历这个取出的字符串,将每个字符都加到当前的组合后面,并调用递归函数即可,参见代码如下:

简单深度优先搜索,别忘了调用完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
class Solution {
public:
vector<string> dict;
void work(string digits, vector<string>& result, int cur, string& temp) {
if(cur == digits.length()) {
result.push_back(temp);
return;
}
for(int i=0;i<dict[digits[cur]-'0'].length();i++) {
temp+=dict[digits[cur]-'0'][i];
work(digits, result, cur+1, temp);
temp.erase(temp.length() - 1);
}
}

vector<string> letterCombinations(string digits) {
vector<string> result;
dict.push_back("");
dict.push_back("");
dict.push_back("abc");
dict.push_back("def");
dict.push_back("ghi");
dict.push_back("jkl");
dict.push_back("mno");
dict.push_back("pqrs");
dict.push_back("tuv");
dict.push_back("wxyz");
if(digits.length() == 0) {
return result;
}

string temp="";
work(digits, result, 0, temp);
return result;
}
};

Leetcode18. 4Sum

Given an array nums of n integers and an integer target, are there elements a, b, c, and d in nums such that a + b + c + d = target? Find all unique quadruplets in the array which gives the sum of target. The solution set must not contain duplicate quadruplets.

Example:

1
2
3
4
5
6
7
Given array nums = [1, 0, -1, 0, -2, 2], and target = 0.
A solution set is:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]

题意

中文描述 给定一个数列 S ,包含 n 个整数.在 S 中找到四个元素 a, b, c, d 使得 a + b + c + d = target . 找到所有的独特的满足上述条件的四元组. 注意: 结果集包含的四元组不重复.

题解

算法及复杂度(45 ms) 本题与第 15 题 (3Sum) 比较类似,第 15 题是求三个数的和,本题是求四个数的和.建议先去练习第 15 题. 首先,为了避免结果的重复,先对数列进行排序.依照在 15 题中提到的: 在一段数列上找到和为固定值的两个数的复杂度可以为 O(n).于是,很简单的思路是: 先固定前两个数 nums[l1], nums[l2](使用两重循环),这样后两个数的和是固定的 target - nums[l1] - nums[l2] ,只需要在 (l2, len) 上进行和为固定值的两个数的寻找就可以了. 需要注意的是: 在算法运行过程中注意保证求出的结果不重复,控制方法参考AC代码。其实可以看出,即使求出的结果重复也可以在求出所有的结果后很容易找出所有不重复的结果,原因是根据求出的四元组的有序性。 在实现过程中,在一段数列上找到和为固定值的两个数直接使用了第 15 题中的 twoSum 函数。 时间复杂度: O(n ^ 3) . n 是数列的长度的, 排序时间复杂度为 O(nlogn) , 求解过程中:前两个数两重循环复杂度为 O(n ^ 2) , 后两个数查找过程复杂度为 O(n) , 总的时间复杂度为 O(nlogn) + O(n ^ 2) * O(n) , 即 O(n ^ 3) .

算法正确性

正确性证明 算法的正确性等同于枚举的正确性。 举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 给定数列和target
nums = [1,0,-1,0,-2,2]
target = 0

//排序数列
nums = [-2, -1, 0, 0, 1, 2]

//i = 0, j = 1, l = 2, r = 5
nums[i] + num[j] + num[l] + num[r] = -1 < target, l ++

//i = 0, j = 1, l = 3, r = 5
nums[i] + num[j] + num[l] + num[r] = -1 < target, l ++

//i = 0, j = 1, l = 4, r = 5
nums[i] + num[j] + num[l] + num[r] = 0 == target, l ++ , r--, 此时l !< r ,因此 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
27
28
29
30
31
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
int len = nums.size();
for(int i=0;i<len-3;i++) {
for(int j=i+1;j<len-2;j++) {
int left = j+1, right = len-1;
while(left < right){
int sum = nums[i]+nums[j]+nums[right]+nums[left];
if(sum == target) {
vector<int> out{nums[i], nums[j], nums[left], nums[right]};
res.push_back(out);
left ++;
right--;
while(left<right && nums[left]==nums[left-1]) left++; // 很重要的去重!
while(left<right && nums[right]==nums[right+1]) right--;// 很重要的去重!
} else if(sum > target) {
right --;
} else {
left ++;
}
}
while(j+1<len-2 && nums[j]==nums[j+1]) j++;
}
while(i+1<len-3 && nums[i]==nums[i+1]) i++;
}
return res;
}
};

Leetcode19. Remove Nth Node From End of List

Given a linked list, remove the n-th node from the end of list and return its head.

Example:

1
2
Given linked list: 1->2->3->4->5, and n = 2.
After removing the second node from the end, the linked list becomes 1->2->3->5.

Note: Given n will always be valid.

Follow up: Could you do this in one pass?

这道题让我们移除链表倒数第n个节点,限定n一定是有效的,即n不会大于链表中的元素总数。还有题目要求一次遍历解决问题,那么就得想些比较巧妙的方法了。比如首先要考虑的时,如何找到倒数第n个节点,由于只允许一次遍历,所以不能用一次完整的遍历来统计链表中元素的个数,而是遍历到对应位置就应该移除了。那么就需要用两个指针来帮助解题,pre 和 cur 指针。首先 cur 指针先向前走N步,如果此时 cur 指向空,说明N为链表的长度,则需要移除的为首元素,那么此时返回 head->next 即可,如果 cur 存在,再继续往下走,此时 pre 指针也跟着走,直到 cur 为最后一个元素时停止,此时 pre 指向要移除元素的前一个元素,再修改指针跳过需要移除的元素即可,pre相当于计数器了,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(!head->next) return NULL;
ListNode* pre = head, *cur = head;
for(int i = 0; i < n; i ++)
cur = cur->next;
if(!cur) return head->next;
while(cur->next) {
cur = cur->next;
pre = pre->next;
}
pre->next = pre->next->next;
return head;
}
};

Leetcode20. Valid Parentheses

Given a string containing just the characters ‘(‘, ‘)’, ‘{‘, ‘}’, ‘[‘ and ‘]’, determine if the input string is valid.

An input string is valid if:

  • Open brackets must be closed by the same type of brackets.
  • Open brackets must be closed in the correct order.
  • Note that an empty string is also considered valid.

Example 1:

1
2
Input: "()"
Output: true

Example 2:

1
2
Input: "()[]{}"
Output: true

Example 3:

1
2
Input: "(]"
Output: false

Example 4:

1
2
Input: "([)]"
Output: false

Example 5:

1
2
Input: "{[]}"
Output: true

算法及复杂度 (3 ms) 本题就是验证括号的顺序是否能保证正确匹配,通过自己简单的模拟就会发现:在括号的匹配过程中,右括号才是最重要的.每个右括号能且只能对应前边的一个左括号,因此每个右括号对应的左括号一定在前边出现,并且位置是确定的.

因此就萌生了一种模拟的思路: 遇到左括号就存起来,遇到右括号就进行匹配. 本题为什么想到用stack进行实现?在左括号的存储过程有很多结构进行实现,主要仔细分析右括号的匹配过程.不妨举个例子 s = “((()))”, 先用某种方式把左括号存起来,那么存储结果是 “(((“, 遇到第一个右括号,与前一个符号进行比对,发现是左括号(如果不是对应的左括号,就可以直接return false了,原因根据正确括号序列的定义),这样就进行了匹配,匹配成功之后,显然这一对括号已经没有作用了,因此就可以把这对括号覆盖掉或者删除掉,这里使用stack通过弹出顶部元素(即对应左括号)达到这个效果.

时间复杂度: O(n). n 表示括号序列的长度,只需要一次遍历就可以完成,因此是 O(n) 的复杂度.

正确性证明 模拟的思想,根据题目提供的方法在进行操作,提供的已知条件保证了算法的正确性. 举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
//输入序列
s = "([{)"

//分析s[0],左括号,入栈st
st = "("

//分析s[1],左括号,入栈st
st = "(["

//分析s[2],左括号,入栈st
st = "([{"

//分析s[3],右括号,尝试匹配st.top(),返现不匹配,返回false
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
class Solution {
public:
bool isValid(string s) {
if(s[0] == ')' || s[0] == '}' || s[0] == ']') {
return false;
} else if(s.length() == 0) {
return true;
}

stack<char>st;

st.push(s[0]);
for(int i = 1; i < s.length(); i ++) {
if(s[i] == ')') {
if(!st.empty() && st.top() == '(') {
st.pop();
} else {
return false;
}
}else if(!st.empty() && s[i] == '}') {
if(st.top() == '{') {
st.pop();
} else {
return false;
}
}else if(!st.empty() && s[i] == ']') {
if(st.top() == '[') {
st.pop();
} else {
return false;
}
} else {
st.push(s[i]);
}
}
if(st.size() == 0) {
return true;
}
return false;
}
};

Leetcode21. Merge Two Sorted Lists

Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together the nodes of the first two lists.

Example:

Input: 1->2->4, 1->3->4
Output: 1->1->2->3->4->4

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
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {

ListNode* result = new ListNode(-1);
ListNode* cur = result;
while(l1 != NULL && l2 != NULL) {
if(l1->val < l2->val) {
cur->next = l1;
cur = cur->next;
l1 = l1->next;
}
else {
cur->next = l2;
cur = cur->next;
l2 = l2->next;
}
}
while(l1) {
cur->next = l1;
cur = cur->next;
l1 = l1->next;
}
while(l2) {
cur->next = l2;
cur = cur->next;
l2 = l2->next;
}
// 上边两个while可以用这一句话代替。
// cur->next = l1 ? l1 : l2;
return result->next;
}
};

Leetcode22. Generate Parentheses

Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.

For example, given n = 3, a solution set is:

1
2
3
4
5
6
7
[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]

这道题给定一个数字n,让生成共有n个括号的所有正确的形式,对于这种列出所有结果的题首先还是考虑用递归 Recursion 来解,由于字符串只有左括号和右括号两种字符,而且最终结果必定是左括号3个,右括号3个,所以这里定义两个变量 left 和 right 分别表示剩余左右括号的个数。

如果在某次递归时,左括号的个数大于右括号的个数,说明此时生成的字符串中右括号的个数大于左括号的个数,即会出现 ‘)(‘ 这样的非法串,所以这种情况直接返回,不继续处理。

如果 left 和 right 都为0,则说明此时生成的字符串已有3个左括号和3个右括号,且字符串合法,则存入结果中后返回。如果以上两种情况都不满足,若此时 left 大于0,则调用递归函数,注意参数的更新,若 right 大于0,则调用递归函数,同样要更新参数,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
vector<string> result;

void dfs(int left, int right, string temp) {
if(left > right) return;
if(left == 0 && right == 0)
result.push_back(temp);
if(left > 0) dfs(left-1, right, temp+"(");
if(right > 0) dfs(left, right-1, temp+")");
}
vector<string> generateParenthesis(int n) {
dfs(n, n, "");
return result;
}
};

Leetcode23. Merge k Sorted Lists

You are given an array of k linked-lists lists, each linked-list is sorted in ascending order.

Merge all the linked-lists into one sorted linked-list and return it.

Example 1:

1
2
3
4
5
6
7
8
9
10
Input: lists = [[1,4,5],[1,3,4],[2,6]]
Output: [1,1,2,3,4,4,5,6]
Explanation: The linked-lists are:
[
1->4->5,
1->3->4,
2->6
]
merging them into one sorted list:
1->1->2->3->4->4->5->6

Example 2:

1
2
Input: lists = []
Output: []

Example 3:

1
2
Input: lists = [[]]
Output: []

借助分治的思想,把 K 个有序链表两两合并即可。相当于是第 21 题的加强版。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
ListNode* ans = NULL;
if (lists.size() == 0)
return NULL;
ans = new ListNode(-1);
for (int i = 0; i < lists.size(); i ++) {
ListNode* l1 = ans, *l2 = lists[i];
while(l2) {
ListNode* tmp = l2->next;
while(l1 && l1->next && l1->next->val < l2->val)
l1 = l1->next;
l2->next = l1->next;
l1->next = l2;
l2 = tmp;
}
}
return ans->next;
}
};

这里就需要用到分治法 Divide and Conquer Approach。简单来说就是不停的对半划分,比如k个链表先划分为合并两个 k/2 个链表的任务,再不停的往下划分,直到划分成只有一个或两个链表的任务,开始合并。举个例子来说比如合并6个链表,那么按照分治法,首先分别合并0和3,1和4,2和5。这样下一次只需合并3个链表,再合并1和3,最后和2合并就可以了。代码中的k是通过 (n+1)/2 计算的,这里为啥要加1呢,这是为了当n为奇数的时候,k能始终从后半段开始,比如当 n=5 时,那么此时 k=3,则0和3合并,1和4合并,最中间的2空出来。当n是偶数的时候,加1也不会有影响,比如当 n=4 时,此时 k=2,那么0和2合并,1和3合并,完美解决问题,参见代码如下:

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
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if (lists.empty()) return NULL;
int n = lists.size();
while (n > 1) {
int k = (n + 1) / 2;
for (int i = 0; i < n / 2; ++i) {
lists[i] = mergeTwoLists(lists[i], lists[i + k]);
}
n = k;
}
return lists[0];
}
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode *dummy = new ListNode(-1), *cur = dummy;
while (l1 && l2) {
if (l1->val < l2->val) {
cur->next = l1;
l1 = l1->next;
} else {
cur->next = l2;
l2 = l2->next;
}
cur = cur->next;
}
if (l1) cur->next = l1;
if (l2) cur->next = l2;
return dummy->next;
}
};

Leetcode24. Swap Nodes in Pairs

Given a linked list, swap every two adjacent nodes and return its head. You may not modify the values in the list’s nodes, only nodes itself may be changed.

Example: Given 1->2->3->4, you should return the list as 2->1->4->3.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if (head == NULL || head->next == NULL)
{
return head;
}
ListNode *cur = head, *next = head->next, *adj;
ListNode *prev = new ListNode(-1, head);
ListNode *res = prev;
while(cur && cur->next) {
adj = cur->next;
next = adj->next;
prev->next = adj;
adj->next = cur;
prev = cur;
cur->next = next;
cur = cur->next;
}
return res->next;
}
};
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
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
// We use a dummy head node to make handling head operations simpler
ListNode *dummy = new ListNode(-1), *tail = dummy;
// add the dummy node to list
tail->next = head;

while(head && head->next) {
ListNode *nextptr = head->next->next;
// swap the adjacent nodes
// 2nd node comes to 1st pos
tail->next = head->next;
// connecting 2nd node to 1st node
(head->next)->next = head;
// make the 1st node connected to next node on list
tail = head;
tail->next = nextptr;
head = nextptr;
}

head = dummy->next;
delete dummy;
return head;
}
};

Leetcode25. Reverse Nodes in k-Group

Given a linked list, reverse the nodes of a linked list k at a time and return its modified list.

k is a positive integer and is less than or equal to the length of the linked list. If the number of nodes is not a multiple of k then left-out nodes in the end should remain as it is.

Example:

Given this linked list: 1->2->3->4->5

For k = 2, you should return: 2->1->4->3->5

For k = 3, you should return: 3->2->1->4->5

Note:

Only constant extra memory is allowed.
You may not alter the values in the list’s nodes, only nodes itself may be changed.

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

把一个很长的链表分成很多个小链表,每一份的长度都是 k (最后一份的长度如果小于 k 则不需要反转),然后对每个小链表进行反转,最后将所有反转后的小链表按之前的顺序拼接在一起。

  • 第一,在反转子链表的时候,上一个子链表的尾必须知道
  • 第二,下一个子链表的头也必须知道
  • 第三,当前反转的链表的头尾都必须知道
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
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
if(head==NULL || head->next==NULL || k<=1){
return head;
}
ListNode* dummy = new ListNode(0);
dummy->next = head;
ListNode* pointer = dummy;
// 强行找一个前驱,整个链表的前驱

while(pointer != NULL){
ListNode* lastGroup = pointer;
// 记录上一个子链表的尾
int i;
for(i=0;i<k;i++){
pointer = pointer->next;
if(pointer==NULL)
break;
}

// 如果当前子链表的节点数满足 k, 就进行反转
// 反之,说明程序到尾了,节点数不够,不用反转
// 每次进行交换时记得把这个子链表前一个和后一个记下来
if(i==k){
// 记录下一个子链表的头,作为反转时的“哨兵”
// 并且在反转完之后把反转完之后的链表接起来
ListNode* nextGroup = pointer->next;

// 反转当前子链表,reverse 函数返回反转后子链表的头
ListNode* reversedList = reverse(lastGroup->next, nextGroup);

// lastGroup 是上一个子链表的尾,其 next 指向当前反转子链表的头
// 但是因为当前链表已经被反转,所以它指向的是反转后的链表的尾
pointer = lastGroup->next;

// 将上一个链表的尾连向反转后链表的头
lastGroup->next = reversedList;

// 当前反转后的链表的尾连向下一个子链表的头
pointer->next = nextGroup;
}
}
return dummy->next;
}

ListNode* reverse(ListNode* head, ListNode* tail) {
if(head==NULL || head->next==NULL){
return head;
}
ListNode* prev = NULL, *temp = NULL;
while(head!=NULL && head!=tail){
temp = head->next;
head->next = prev;
prev = head;
head = temp;
}
return prev;
}
};

Leetcode26. Remove Duplicates from Sorted Array

Given a sorted array nums, remove the duplicates in-place such that each element appear only once and return the new length.

Do not allocate extra space for another array, you must do this by modifying the input array in-place with O(1) extra memory.

Example 1:

1
2
3
4
5
Given nums = [1,1,2],

Your function should return length = 2, with the first two elements of nums being 1 and 2 respectively.

It doesn't matter what you leave beyond the returned length.

Example 2:

1
2
3
4
5
Given nums = [0,0,1,1,1,2,2,3,3,4],

Your function should return length = 5, with the first five elements of nums being modified to 0, 1, 2, 3, and 4 respectively.

It doesn't matter what values are set beyond the returned length.

Clarification:

Confused why the returned value is an integer but your answer is an array?

Note that the input array is passed in by reference, which means modification to the input array will be known to the caller as well.

Internally you can think of this:

1
2
3
4
5
6
7
8
// nums is passed in by reference. (i.e., without making a copy)
int len = removeDuplicates(nums);

// any modification to nums in your function would be known by the caller.
// using the length returned by your function, it prints the first len elements.
for (int i = 0; i < len; i++) {
print(nums[i]);
}

真的是非常简单的一道题,但是因为某种原因WA了好几次。。。去掉重复的数并返回去重之后的长度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int length=nums.size();
if(length==0)
return 0;
int j=0;
for(int i=1;i<length;i++){
if(nums[i]!=nums[j]){
j++;
nums[j]=nums[i];
}
}
return j+1;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if(nums.size() == 0)
return 0;
int prev = nums[0];
int res = 1;
for(int i = 1; i < nums.size(); i ++) {
if(prev != nums[i]) {
prev = nums[i];
nums[res] = nums[i];
res ++;
}
}
return res;
}
};

Leetcode27. Remove Element

Given an array nums and a value val, remove all instances of that value in-place and return the new length. Do not allocate extra space for another array, you must do this by modifying the input array in-place with O(1) extra memory. The order of elements can be changed. It doesn’t matter what you leave beyond the new length.

Example 1:

1
2
3
Given nums = [3,2,2,3], val = 3,
Your function should return length = 2, with the first two elements of nums being 2.
It doesn't matter what you leave beyond the returned length.

Example 2:

1
2
Given nums = [0,1,2,2,3,0,4,2], val = 2,
Your function should return length = 5, with the first five elements of nums containing 0, 1, 3, 0, and 4.

Note that the order of those five elements can be arbitrary.
It doesn’t matter what values are set beyond the returned length.

Clarification: Confused why the returned value is an integer but your answer is an array? Note that the input array is passed in by reference, which means modification to the input array will be known to the caller as well.

Internally you can think of this:

1
2
3
4
5
6
7
8
// nums is passed in by reference. (i.e., without making a copy)
int len = removeElement(nums, val);

// any modification to nums in your function would be known by the caller.
// using the length returned by your function, it prints the first len elements.
for (int i = 0; i < len; i++) {
print(nums[i]);
}

双指针做法,两个指针分别指向要被删除的值。

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int cur = 0, size = nums.size();
for(int i = 0; i < size; i ++) {
if(val != nums[i])
nums[cur++] = nums[i];
}
return cur;
}
};

Leetcode28. Implement strStr()

Implement strStr().

Return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.

Example 1:

1
2
Input: haystack = "hello", needle = "ll"
Output: 2

Example 2:

1
2
Input: haystack = "aaaaa", needle = "bba"
Output: -1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int strStr(string haystack, string needle) {
int hasize = haystack.size();
int neesize = needle.size();
if(hasize == 0 && neesize == 0)
return 0;
if (hasize < neesize)
return -1;
int len, j, k;
for(int i = 0; i <= hasize - neesize; i ++) {
len = 0;
for(j = i, k = 0; j < hasize && k < neesize && needle[k] == haystack[j]; j ++, k++)
len ++;
if(len == neesize)
return i;
}
return -1;
}
};

Leetcode29. Divide Two Integers

Given two integers dividend and divisor, divide two integers without using multiplication, division and mod operator. Return the quotient after dividing dividend by divisor.

The integer division should truncate toward zero, which means losing its fractional part. For example, truncate(8.345) = 8 and truncate(-2.7335) = -2.

Example 1:

1
2
3
Input: dividend = 10, divisor = 3
Output: 3
Explanation: 10/3 = truncate(3.33333..) = 3.

Example 2:

1
2
3
Input: dividend = 7, divisor = -3
Output: -2
Explanation: 7/-3 = truncate(-2.33333..) = -2.

一开始我用被除数一次一次地减除数,这样太慢了,遇到被除数为2147483648除数为1的情况就挂了。可以用被除数不断地减除数的1倍、2倍、4倍、8倍…这样就快了。使用位运算,被除数与除数同号时商为正,异号时商为负。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
int divide(int dividend, int divisor) {
if(divisor == 0 || (dividend == INT_MIN && divisor == -1))
return INT_MAX;
int sign = ((dividend > 0 && divisor > 0) || (dividend < 0 && divisor < 0)) ? 1 : -1;
long dd = labs(dividend);
long ds = labs(divisor);
long q = 0;
while(dd >= ds) {
long k = 0, temp = ds;
while(dd >= temp) {
q += (1 << k);
dd -= temp;
temp = temp << 1;
k += 1;
}
}
return q * sign;
}
};

这道题让我们求两数相除,而且规定不能用乘法,除法和取余操作,那么这里可以用另一神器位操作 Bit Manipulation,思路是,如果被除数大于或等于除数,则进行如下循环,定义变量t等于除数,定义计数p,当t的两倍小于等于被除数时,进行如下循环,t扩大一倍,p扩大一倍,然后更新 res 和m。这道题的 OJ 给的一些 test case 非常的讨厌,因为输入的都是 int 型,比如被除数是 -2147483648,在 int 范围内,当除数是 -1 时,结果就超出了 int 范围,需要返回 INT_MAX,所以对于这种情况就在开始用 if 判定,将其和除数为0的情况放一起判定,返回 INT_MAX。然后还要根据被除数和除数的正负来确定返回值的正负,这里采用长整型 long 来完成所有的计算,最后返回值乘以符号即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int divide(int dividend, int divisor) {
if (dividend == INT_MIN && divisor == -1) return INT_MAX;
long m = labs(dividend), n = labs(divisor), res = 0;
int sign = ((dividend < 0) ^ (divisor < 0)) ? -1 : 1;
if (n == 1) return sign == 1 ? m : -m;
while (m >= n) {
long t = n, p = 1;
while (m >= (t << 1)) {
t <<= 1;
p <<= 1;
}
res += p;
m -= t;
}
return sign == 1 ? res : -res;
}
};

Leetcode30. Substring with Concatenation of All Words

You are given a string s and an array of strings words of the same length. Return all starting indices of substring(s) in s that is a concatenation of each word in words exactly once, in any order, and without any intervening characters.

You can return the answer in any order.

Example 1:

1
2
3
4
Input: s = "barfoothefoobarman", words = ["foo","bar"]
Output: [0,9]
Explanation: Substrings starting at index 0 and 9 are "barfoo" and "foobar" respectively.
The output order does not matter, returning [9,0] is fine too.

Example 2:

1
2
Input: s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]
Output: []

Example 3:

1
2
Input: s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]
Output: [6,9,12]

这道题让我们求串联所有单词的子串,就是说给定一个长字符串,再给定几个长度相同的单词,让找出串联给定所有单词的子串的起始位置,还是蛮有难度的一道题。假设 words 数组中有n个单词,每个单词的长度均为 len,那么实际上这道题就让我们出所有长度为 nxlen 的子串,使得其刚好是由 words 数组中的所有单词组成。那么就需要经常判断s串中长度为 len 的子串是否是 words 中的单词,为了快速的判断,可以使用 HashMap,同时由于 words 数组可能有重复单词,就要用 HashMap 来建立所有的单词和其出现次数之间的映射,即统计每个单词出现的次数。

遍历s中所有长度为 nxlen 的子串,当剩余子串的长度小于 nxlen 时,就不用再判断了。所以i从0开始,到 (int)s.size() - nxlen 结束就可以了,注意这里一定要将 s.size() 先转为整型数,再进行解法。一定要形成这样的习惯,一旦 size() 后面要减去数字时,先转为 int 型,因为 size() 的返回值是无符号型,一旦减去一个比自己大的数字,则会出错。对于每个遍历到的长度为 nxlen 的子串,需要验证其是否刚好由 words 中所有的单词构成,检查方法就是每次取长度为 len 的子串,看其是否是 words 中的单词。为了方便比较,建立另一个 HashMap,当取出的单词不在 words 中,直接 break 掉,否则就将其在新的 HashMap 中的映射值加1,还要检测若其映射值超过原 HashMap 中的映射值,也 break 掉,因为就算当前单词在 words 中,但若其出现的次数超过 words 中的次数,还是不合题意的。在 for 循环外面,若j正好等于n,说明检测的n个长度为 len 的子串都是 words 中的单词,并且刚好构成了 words,则将当前位置i加入结果 res 即可,具体参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
if (s.empty() || words.empty()) return {};
vector<int> res;
int n = words.size(), len = words[0].size();
unordered_map<string, int> wordCnt;
for (auto &word : words) ++wordCnt[word];
for (int i = 0; i <= (int)s.size() - n * len; ++i) {
unordered_map<string, int> strCnt;
int j = 0;
for (j = 0; j < n; ++j) {
string t = s.substr(i + j * len, len);
if (!wordCnt.count(t)) break;
++strCnt[t];
if (strCnt[t] > wordCnt[t]) break;
}
if (j == n) res.push_back(i);
}
return res;
}
};

这道题还有一种 O(n) 时间复杂度的解法,设计思路非常巧妙,但是感觉很难想出来,博主目测还未到达这种水平。这种方法不再是一个字符一个字符的遍历,而是一个词一个词的遍历,比如根据题目中的例子,字符串s的长度n为 18,words 数组中有两个单词 (cnt=2),每个单词的长度 len 均为3,那么遍历的顺序为 0,3,6,8,12,15,然后偏移一个字符 1,4,7,9,13,16,然后再偏移一个字符 2,5,8,10,14,17,这样就可以把所有情况都遍历到,还是先用一个 HashMap m1 来记录 words 里的所有词,然后从0开始遍历,用 left 来记录左边界的位置,count 表示当前已经匹配的单词的个数。然后一个单词一个单词的遍历,如果当前遍历的到的单词t在 m1 中存在,那么将其加入另一个 HashMap m2 中,如果在 m2 中个数小于等于 m1 中的个数,那么 count 自增1,如果大于了,则需要做一些处理,比如下面这种情况:s = barfoofoo, words = {bar, foo, abc},给 words 中新加了一个 abc ,目的是为了遍历到 barfoo 不会停止,当遍历到第二 foo 的时候,m2[foo]=2,而此时m1[foo]=1,这时候已经不连续了,所以要移动左边界 left 的位置,先把第一个词t1=bar取出来,然后将m2[t1]自减1,如果此时m2[t1]<m1[t1]了,说明一个匹配没了,那么对应的 count 也要自减1,然后左边界加上个 len,这样就可以了。如果某个时刻 count 和 cnt 相等了,说明成功匹配了一个位置,将当前左边界 left 存入结果 res 中,此时去掉最左边的一个词,同时 count 自减1,左边界右移 len,继续匹配。如果匹配到一个不在 m1 中的词,说明跟前面已经断开了,重置 m2,count 为0,左边界 left 移到 j+len,参见代码如下:

解法二:

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
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
if (s.empty() || words.empty()) return {};
vector<int> res;
int n = s.size(), cnt = words.size(), len = words[0].size();
unordered_map<string, int> m1;
for (string w : words) ++m1[w];
for (int i = 0; i < len; ++i) {
int left = i, count = 0;
unordered_map<string, int> m2;
for (int j = i; j <= n - len; j += len) {
string t = s.substr(j, len);
if (m1.count(t)) {
++m2[t];
if (m2[t] <= m1[t]) {
++count;
} else {
while (m2[t] > m1[t]) {
string t1 = s.substr(left, len);
--m2[t1];
if (m2[t1] < m1[t1]) --count;
left += len;
}
}
if (count == cnt) {
res.push_back(left);
--m2[s.substr(left, len)];
--count;
left += len;
}
} else {
m2.clear();
count = 0;
left = j + len;
}
}
}
return res;
}
};

Leetcode31. Next Permutation

Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers. If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order). The replacement must be in-place and use only constant extra memory. Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the right-hand column.

1
2
3
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

这道题让我们求下一个排列顺序,由题目中给的例子可以看出来,如果给定数组是降序,则说明是全排列的最后一种情况,则下一个排列就是最初始情况,可以参见之前的博客 Permutations。再来看下面一个例子,有如下的一个数组1  2  7  4  3  1,下一个排列为:1  3  1  2  4  7

那么是如何得到的呢,我们通过观察原数组可以发现,如果从末尾往前看,数字逐渐变大,到了2时才减小的,然后再从后往前找第一个比2大的数字,是3,那么我们交换2和3,再把此时3后面的所有数字转置一下即可,步骤如下:

1
2
3
4
1  2  7  4  3  1
1  2  7  4  3  1
1  3  7  4  2  1
1  3  1  2  4  7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
void nextPermutation(vector<int>& nums) {
for(int i = nums.size()-2; i >= 0; i --) {
if(nums[i] < nums[i+1]) {
int j;
for(j = nums.size()-1; j > i; j --)
if(nums[j] > nums[i])
break;
swap(nums[i], nums[j]);
reverse(nums.begin()+i+1, nums.end());
return;
}
}
reverse(nums.begin(), nums.end());
}
};

Leetcode32. Longest Valid Parentheses

Given a string containing just the characters ‘(‘ and ‘)’, find the length of the longest valid (well-formed) parentheses substring.

Example 1:

1
2
3
Input: s = "(()"
Output: 2
Explanation: The longest valid parentheses substring is "()".

Example 2:

1
2
3
Input: s = ")()())"
Output: 4
Explanation: The longest valid parentheses substring is "()()".

Example 3:

1
2
Input: s = ""
Output: 0

这道求最长有效括号比之前那道 Valid Parentheses 难度要大一些,这里还是借助栈来求解,需要定义个 start 变量来记录合法括号串的起始位置,遍历字符串,如果遇到左括号,则将当前下标压入栈,如果遇到右括号,如果当前栈为空,则将下一个坐标位置记录到 start,如果栈不为空,则将栈顶元素取出,此时若栈为空,则更新结果和 i - start + 1 中的较大值,否则更新结果和 i - st.top() 中的较大值,参见代码如下:

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int longestValidParentheses(string s) {
int res = 0, start = 0, n = s.size();
stack<int> st;
for (int i = 0; i < n; ++i) {
if (s[i] == '(') st.push(i);
else if (s[i] == ')') {
if (st.empty()) start = i + 1;
else {
st.pop();
res = st.empty() ? max(res, i - start + 1) : max(res, i - st.top());
}
}
}
return res;
}
};

还有一种利用动态规划 Dynamic Programming 的解法,可参见网友喜刷刷的博客。这里使用一个一维 dp 数组,其中 dp[i] 表示以 s[i-1] 结尾的最长有效括号长度(注意这里没有对应 s[i],是为了避免取 dp[i-1] 时越界从而让 dp 数组的长度加了1),s[i-1] 此时必须是有效括号的一部分,那么只要 dp[i] 为正数的话,说明 s[i-1] 一定是右括号,因为有效括号必须是闭合的。当括号有重合时,比如 “(())”,会出现多个右括号相连,此时更新最外边的右括号的 dp[i] 时是需要前一个右括号的值 dp[i-1],因为假如 dp[i-1] 为正数,说明此位置往前 dp[i-1] 个字符组成的子串都是合法的子串,需要再看前面一个位置,假如是左括号,说明在 dp[i-1] 的基础上又增加了一个合法的括号,所以长度加上2。但此时还可能出现的情况是,前面的左括号前面还有合法括号,比如 “()(())”,此时更新最后面的右括号的时候,知道第二个右括号的 dp 值是2,那么最后一个右括号的 dp 值不仅是第二个括号的 dp 值再加2,还可以连到第一个右括号的 dp 值,整个最长的有效括号长度是6。所以在更新当前右括号的 dp 值时,首先要计算出第一个右括号的位置,通过 i-3-dp[i-1] 来获得,由于这里定义的 dp[i] 对应的是字符 s[i-1],所以需要再加1,变成 j = i-2-dp[i-1],这样若当前字符 s[i-1] 是左括号,或者j小于0(说明没有对应的左括号),或者 s[j] 是右括号,此时将 dp[i] 重置为0,否则就用 dp[i-1] + 2 + dp[j] 来更新 dp[i]。这里由于进行了 padding,可能对应关系会比较晕,大家可以自行带个例子一步一步执行,应该是不难理解的,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int longestValidParentheses(string s) {
int res = 0, n = s.size();
vector<int> dp(n + 1);
for (int i = 1; i <= n; ++i) {
int j = i - 2 - dp[i - 1];
if (s[i - 1] == '(' || j < 0 || s[j] == ')') {
dp[i] = 0;
} else {
dp[i] = dp[i - 1] + 2 + dp[j];
res = max(res, dp[i]);
}
}
return res;
}
};

此题还有一种不用额外空间的解法,使用了两个变量 left 和 right,分别用来记录到当前位置时左括号和右括号的出现次数,当遇到左括号时,left 自增1,右括号时 right 自增1。对于最长有效的括号的子串,一定是左括号等于右括号的情况,此时就可以更新结果 res 了,一旦右括号数量超过左括号数量了,说明当前位置不能组成合法括号子串,left 和 right 重置为0。但是对于这种情况 “(()” 时,在遍历结束时左右子括号数都不相等,此时没法更新结果 res,但其实正确答案是2,怎么处理这种情况呢?答案是再反向遍历一遍,采取类似的机制,稍有不同的是此时若 left 大于 right 了,则重置0,这样就可以 cover 所有的情况了,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int longestValidParentheses(string s) {
int res = 0, left = 0, right = 0, n = s.size();
for (int i = 0; i < n; ++i) {
(s[i] == '(') ? ++left : ++right;
if (left == right) res = max(res, 2 * right);
else if (right > left) left = right = 0;
}
left = right = 0;
for (int i = n - 1; i >= 0; --i) {
(s[i] == '(') ? ++left : ++right;
if (left == right) res = max(res, 2 * left);
else if (left > right) left = right = 0;
}
return res;
}
};

Leetcode33. Search in Rotated Sorted Array

Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand.

(i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]).

You are given a target value to search. If found in the array return its index, otherwise return -1.

You may assume no duplicate exists in the array.

Your algorithm’s runtime complexity must be in the order of O(log n).

Example 1:

1
2
Input: nums = [4,5,6,7,0,1,2], target = 0
Output: 4

Example 2:

1
2
Input: nums = [4,5,6,7,0,1,2], target = 3
Output: -1

这道题让在旋转数组中搜索一个给定值,若存在返回坐标,若不存在返回 -1。我们还是考虑二分搜索法,但是这道题的难点在于不知道原数组在哪旋转了。

二分搜索法的关键在于获得了中间数后,判断下面要搜索左半段还是右半段。如果中间的数小于最右边的数,则右半段是有序的,若中间数大于最右边数,则左半段是有序的,我们只要在有序的半段里用首尾两个数组来判断目标值是否在这一区域内,这样就可以确定保留哪半边了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while(left <= right) {
int mid = (left + right) / 2;
if(nums[mid] == target)
return mid;
if(nums[mid] < nums[right])
if(nums[mid] < target && target <= nums[right])
left = mid + 1;
else
right = mid - 1;
else
if(nums[left] <= target && target < nums[mid])
right = mid - 1;
else
left = mid + 1;
}
return -1;
}
};

Leetcode34. Find First and Last Position of Element in Sorted Array

Given an array of integers nums sorted in ascending order, find the starting and ending position of a given target value. Your algorithm’s runtime complexity must be in the order of O(log n). If the target is not found in the array, return [-1, -1].

Example 1:

1
2
Input: nums = [5,7,7,8,8,10], target = 8
Output: [3,4]

Example 2:

1
2
Input: nums = [5,7,7,8,8,10], target = 6
Output: [-1,-1]

这道题让我们在一个有序整数数组中寻找相同目标值的起始和结束位置,而且限定了时间复杂度为 O(logn),这是典型的二分查找法的时间复杂度,所以这里也需要用此方法,思路是首先对原数组使用二分查找法,找出其中一个目标值的位置,然后向两边搜索找出起始和结束的位置。我以为要用什么高级算法呢,没想到只是一个简单的二分。

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
class Solution {
public:

vector<int> searchRange(vector<int>& nums, int target) {
if(nums.size() == 0)
return {-1, -1};
if(nums.size() == 1)
if(nums[0] == target)
return {0, 0};
else
return {-1, -1};
int n1 = -1, n2 = -1;
int len = nums.size();
int left = 0, right = len-1, mid;
while(left <= right) {
mid = left + (right - left) / 2;
if(nums[mid] > target)
right = mid - 1;
else if(nums[mid] < target)
left = mid + 1;
else
break;
}
if(nums[mid] != target)
return {-1, -1};
int i, j;
for(i = mid; i < len-1; i ++) {
if(nums[i+1] != nums[mid])
break;
}
for(j = mid; j >= 1; j --) {
if(nums[j-1] != nums[mid])
break;
}
return {j, i};
}
};

Leetcode35. Search Insert Position

Given a sorted array and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order.

You may assume no duplicates in the array.

Example 1:

1
2
Input: [1,3,5,6], 5
Output: 2

Example 2:

1
2
Input: [1,3,5,6], 2
Output: 1

Example 3:

1
2
Input: [1,3,5,6], 7
Output: 4

Example 4:

1
2
Input: [1,3,5,6], 0
Output: 0

只是搜索需要插入的位置罢了,很简单,遍历或者二分都行。

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
if(target <= nums[0])
return 0;
for(int i = 1; i < nums.size(); i ++) {
if(target <= nums[i])
return i;
}
return nums.size();
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
if(target <= nums[0])
return 0;
int l = 0, r = nums.size(), mid;
while(l < r) {
mid = (l + r) / 2;
if(nums[mid] >= target)
r = mid;
else
l = mid + 1;
}
return r;
}
};

Leetcode36. Valid Sudoku

Determine if a 9x9 Sudoku board is valid. Only the filled cells need to be validated according to the following rules:

Each row must contain the digits 1-9 without repetition.
Each column must contain the digits 1-9 without repetition.
Each of the 9 3x3 sub-boxes of the grid must contain the digits 1-9 without repetition.

Example 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
Input:
[
["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
Output: true

Example 2:

1
2
3
4
5
6
7
8
9
10
11
12
13
Input:
[
["8","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
Output: false

Explanation: Same as Example 1, except with the 5 in the top left corner being
modified to 8. Since there are two 8’s in the top left 3x3 sub-box, it is invalid.
Note:

A Sudoku board (partially filled) could be valid but is not necessarily solvable.
Only the filled cells need to be validated according to the mentioned rules.
The given board contain only digits 1-9 and the character ‘.’.
The given board size is always 9x9.

横向、纵向不能存在重复的,而这道题目还加上了每一个3*3的九宫格也不能存在重复元素。根据上述分析,比较自然的可以想到创建三个列表储存目标数据,然而进行比较是否存在重复元素,进而进行相关判断。其中对于3*3的九宫格的索引值计算是一个需要思考的点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
bool isValidSudoku(vector<vector<char>>& board) {
int block_num, digit;
bool map[3][9][9];
for(int i=0;i<3;i++)
for(int j=0;j<9;j++)
for(int k=0;k<9;k++)
map[i][j][k]=false;

for(int i=0;i<9;i++)
for(int j=0;j<9;j++)
if(board[i][j]!='.') {
block_num = i/3*3+j/3;
digit = board[i][j]-'1';
if(map[0][i][digit]==true || map[1][j][digit]==true || map[2][block_num][digit]==true)
return false;
map[0][i][digit] = true;
map[1][j][digit] = true;
map[2][block_num][digit] = true;
}
return true;
}
};

Leetcode37. Sudoku Solver

Write a program to solve a Sudoku puzzle by filling the empty cells.

A sudoku solution must satisfy all of the following rules:

  • Each of the digits 1-9 must occur exactly once in each row.
  • Each of the digits 1-9 must occur exactly once in each column.
  • Each of the digits 1-9 must occur exactly once in each of the 9 3x3 sub-boxes of the grid.

The ‘.’ character indicates empty cells.

Input:

1
2
3
4
5
6
7
8
9
board = [["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]]

Output:

1
2
3
4
5
6
7
8
9
[["5","3","4","6","7","8","9","1","2"],
["6","7","2","1","9","5","3","4","8"],
["1","9","8","3","4","2","5","6","7"],
["8","5","9","7","6","1","4","2","3"],
["4","2","6","8","5","3","7","9","1"],
["7","1","3","9","2","4","8","5","6"],
["9","6","1","5","3","7","2","8","4"],
["2","8","7","4","1","9","6","3","5"],
["3","4","5","2","8","6","1","7","9"]]

一种实现形式是递归带上横纵坐标,由于是一行一行的填数字,且是从0行开始的,所以当i到达9的时候,说明所有的数字都成功的填入了,直接返回 ture。当j大于等于9时,当前行填完了,需要换到下一行继续填,则继续调用递归函数,横坐标带入 i+1。否则看若当前数字不为点,说明当前位置不需要填数字,则对右边的位置调用递归。若当前位置需要填数字,则应该尝试填入1到9内的所有数字,让c从1遍历到9,每当试着填入一个数字,都需要检验是否有冲突,使用另一个子函数 isValid 来检验是否合法,假如不合法,则跳过当前数字。若合法,则将当前位置赋值为这个数字,并对右边位置调用递归,若递归函数返回 true,则说明可以成功填充,直接返回 true。不行的话,需要重置状态,将当前位置恢复为点。若所有数字都尝试了,还是不行,则最终返回 false。检测当前数组是否合法的原理跟之前那道 Valid Sudoku 非常的相似,但更简单一些,因为这里只需要检测新加入的这个数字是否会跟其他位置引起冲突,分别检测新加入数字的行列和所在的小区间内是否有重复的数字即可。

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
class Solution {
public:
void solveSudoku(vector<vector<char>>& board) {
helper(board, 0, 0);
}

bool helper(vector<vector<char>>& board, int i, int j) {
if(i == 9)
return true;
if (j == 9)
return helper(board, i+1, 0);
if (board[i][j] != '.')
return helper(board, i, j+1);
for (int k = '1'; k <= '9'; k ++) {
if (isValid(board, i, j, k)) {
board[i][j] = k;
if (helper(board, i, j+1))
return true;
board[i][j] = '.';
}
}
return false;
}

bool isValid(vector<vector<char>>& board, int i, int j, char val) {
for (int k = 0; k < 9; k ++)
if (board[i][k] == val)
return false;
for (int k = 0; k < 9; k ++)
if (board[k][j] == val)
return false;
i /= 3; i *= 3;
j /= 3; j *= 3;
for (int k = 0; k < 3; k ++)
for (int kk = 0; kk < 3; kk ++)
if (board[i+k][j+kk] == val)
return false;
return true;
}
};

Leetcode38. Count and Say

The count-and-say sequence is the sequence of integers with the first five terms as following:

  1. 1
  2. 11
  3. 21
  4. 1211
  5. 111221
  • 1 is read off as “one 1” or 11.
  • 11 is read off as “two 1s” or 21.
  • 21 is read off as “one 2, then one 1” or 1211.

Given an integer n where 1 ≤ n ≤ 30, generate the nth term of the count-and-say sequence. You can do so recursively, in other words from the previous member read off the digits, counting the number of digits in groups of the same digit.

Note: Each term of the sequence of integers will be represented as a string.

Example 1:

1
2
3
Input: 1
Output: "1"
Explanation: This is the base case.

Example 2:

1
2
3
Input: 4
Output: "1211"
Explanation: For n = 3 the term was "21" in which we have two groups "2" and "1", "2" can be read as "12" which means frequency = 1 and value = 2, the same way "1" is read as "11", so the answer is the concatenation of "12" and "11" which is "1211".

题意是n=1时输出字符串1;n=2时,数上次字符串中的数值个数,因为上次字符串有1个1,所以输出11;n=3时,由于上次字符是11,有2个1,所以输出21;n=4时,由于上次字符串是21,有1个2和1个1,所以输出1211。依次类推,写个countAndSay(n)函数返回字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
string countAndSay(int n) {
if(n == 1)
return "1";
string str = countAndSay(n-1) + ' ';
int count = 1;
string s = "";
for(int i = 0; i < str.length() - 1; i ++){
if(str[i] == str[i+1]){
count++;
}
else {
s = s + to_string(count) + str[i];
count = 1;
}
}
return s;
}
};

Leetcode39. Combination Sum

Given a set of candidate numbers (candidates) (without duplicates) and a target number (target), find all unique combinations in candidates where the candidate numbers sums to target.

The same repeated number may be chosen from candidates unlimited number of times.

Note:

All numbers (including target) will be positive integers.
The solution set must not contain duplicate combinations.
Example 1:

1
2
3
4
5
6
Input: candidates = [2,3,6,7], target = 7,
A solution set is:
[
[7],
[2,2,3]
]

Example 2:

1
2
3
4
5
6
7
Input: candidates = [2,3,5], target = 8,
A solution set is:
[
[2,2,2,2],
[2,3,3],
[3,5]
]

本题采用回溯算法。

  1. 基本思路是先排好序,这样做的目的是为了对数组后面不可能出现的情况进行排除,有利于减少查找时间,即剪枝操作
  2. 外层循环对数组元素依次进行遍历,依次将 nums 中的元素加入中间集,一旦满足条件,就将中间集加入结果集
  3. 然后每次递归中把剩下的元素一一加到结果集合中,并且把目标减去加入的元素,然后把剩下元素(包括当前加入的元素)放到下一层递归中解决子问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
vector<vector<int> > result;

void dfs(vector<int>& candidates, vector<int> cur, int start, int target) {
if(target == 0) {
result.push_back(cur);
return;
}
for(int i = start; i < candidates.size(); i ++) {
if(candidates[i] > target)
break;
cur.push_back(candidates[i]);
dfs(candidates, cur, i, target - candidates[i]);
cur.pop_back();
}
}

vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
dfs(candidates, vector<int>(), 0, target);
return result;
}
};

Leetcode40. Combination Sum II

Given a collection of candidate numbers (candidates) and a target number (target), find all unique combinations in candidates where the candidate numbers sums to target.

Each number in candidates may only be used once in the combination.

Note:

  • All numbers (including target) will be positive integers.
  • The solution set must not contain duplicate combinations.

Example 1:

1
2
3
4
5
6
7
8
Input: candidates = [10,1,2,7,6,1,5], target = 8,
A solution set is:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]

Example 2:

1
2
3
4
5
6
Input: candidates = [2,5,2,1,2], target = 5,
A solution set is:
[
[1,2,2],
[5]
]

我的dfs,一开始没有剪枝,所以很慢,首先排序,然后在dfs的时候,如果有必要就return,如果碰到相同的跳过。排序很重要:

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
class Solution{
public:

vector<vector<int> > result;

void dfs(vector<int> candidates, int i, int length, vector<int> res, int current){
if(current == 0) {
result.push_back(res);
return;
}
else if(current < 0)
return;
for(int ii = i; ii < length; ii ++){
if(ii > i && candidates[ii] == candidates[ii-1])
continue;
res.push_back(candidates[ii]);
dfs(candidates, ii+1, length, res, current - candidates[ii]);
res.pop_back();
}
}

vector<vector<int>> combinationSum2(vector<int>& candidates, int target){
sort(candidates.begin(), candidates.end());
dfs(candidates, 0, candidates.size(), vector<int>{}, target);
return result;
}
};

大佬的做法:

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
class Solution{
public:

vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<vector<int>> res;
vector<int> current;
sort(candidates.begin(),candidates.end());
backTracking(candidates.begin(),current,res,candidates,target);
return res;
}

void backTracking(vector<int>::iterator n, vector<int>& current, vector<vector<int>>& res, const vector<int>& candidates, int target){
if(!target)
res.push_back(current);
else if(target > 0){
for( ; n != candidates.end() && *n <= target; ++ n){
current.push_back(*n);
backTracking(n+1, current, res, candidates, target-*n);
current.pop_back();
while(n + 1 != candidates.end() && *(n+1) == *n)
++n;
}
}
}
};

Leetcode41. First Missing Positive

Given an unsorted integer array, find the smallest missing positive integer.

Example 1:

1
2
Input: [1,2,0]
Output: 3

Example 2:

1
2
Input: [3,4,-1,1]
Output: 2

Example 3:

1
2
Input: [7,8,9,11,12]
Output: 1

Note:

Your algorithm should run in O(n) time and uses constant extra space.

把出现的数值放到与下标一致的位置,再判断什么位置最先出现不连续的数值,就是答案了。

在判断的时候,只要是已经到位了的元素即:A[i] - 1 == i了,那么判断如果有重复元素两个位置交换就最好考虑好两个位置出现的可能情况。考虑问题全面,两个条件都考虑好。

增加i!=A[i]表示i位置没到位,A[A[i]-1] != A[i]表示A[i]-1位置没到位,两个位置都判断也很好的。

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

Leetcode42. Trapping Rain Water

Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.

The above elevation map is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped. Thanks Marcos for contributing this image!

Example:

Input: [0,1,0,2,1,0,1,3,2,1,2,1]
Output: 6

给你n个非负整数表示n个连续区域各自的海拔,每个区域宽度为1,问一场足够大的雨后有多少单位的水能储存在里面。也可以理解成在一个有若干黑块和白块的矩形中,有多少个白块的左边和右边(不一定相邻)都至少有一个黑块。

解题思路(1):
可以开个二维矩阵存每个位置是黑块还是白块,然后一行一行扫描,记下最左和左右的黑块位置l、r和总黑块数cnt,这一行的答案就为r-l-cnt+2,最终答案就是每一行的答案和。这种方法时空复杂度都为O(n*max(height)),当数据很大的时候这种方法是不能接受的。

解题思路(2):
开两个栈,一个栈s存海拔,另一个栈id存该海拔对应的位置。对n个海拔从左往右扫描,对第i个海拔为height[i]的区域,初始化之前海拔变量pre为0,检查栈顶,当栈不空且s栈的栈顶海拔s.top()<=height[i]的时候,重复下列操作:答案增加(i-id.top()-1)*(s.top()-pre),然后pre更新为s.top(),弹出两个栈的栈顶。需要注意的是退出来后如果栈不空则还要增加答案(i-id.top()-1)*(height[i]-pre),然后再把height[i]和i分别压入s和id栈。这个思路时空复杂度均为O(n),完全可以接受。

算法正确性:
算法的关键点在于栈的存储和答案计算的部分。关于栈的存储,由于扫描是从左往右进行的,因此如果出现一个高海拔区域会把左边所有低海拔区域都挡住,后面的计算就不需要用到这些低海拔的区域了,所以从栈底到栈顶海拔逐渐减小。关于答案计算,可以理解为从低到高依次计算一个小矩形的面积,长为当前区域和栈顶区域的距离,宽为栈顶或当前区域与上次计算区域的高度差。

下面举一个简单例子走一遍算法帮助理解:[2,1,0,4,2,3]。初始时s栈和id栈均为空,答案ans为0。

第一步:height[0]=2,pre置为0,检查栈顶,栈s为空,直接将2压入s栈,0压入id栈;

第二步:height[1]=1,pre置为0,检查栈顶,s.top()=2>1,直接将1压入s栈,1压入id栈;

第三步:height[2]=0,pre置为0,检查栈顶,s.top()=1>0,直接将0压入s栈,2压入id栈;

第四步:height[3]=4,pre置为0,检查栈顶,s.top()=0<4,ans增加(i-id.top()-1)(s.top()-pre)=0pre=s.top()=0,弹出s和id栈的栈顶,继续;

检查栈顶,s.top()=1<4,ans增加(i-id.top()-1)(s.top()-pre)=1pre=s.top()=1,弹出s和id栈的栈顶,继续;

检查栈顶,s.top()=2<4,ans增加(i-id.top()-1)(s.top()-pre)=2pre=s.top()=2,弹出s和id栈的栈顶,继续;

检查栈顶,栈s为空,直接将4压入s栈,3压入id栈;

第五步:height[4]=2,pre置为0,检查栈顶,s.top()=4>2,直接将2压入s栈,4压入id栈;

第六步:height[5]=3,pre置为0,检查栈顶,s.top()=2<3,ans增加(i-id.top()-1)(s.top()-pre)=0pre=s.top()=0,弹出s和id栈的栈顶,继续;

检查栈顶,s.top()=4<3,跳出,ans增加(i-id.top()-1)*(s.top()-pre)=1pre=s.top()=2,将3压入s栈,5压入id栈。

最终ans为4。

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
int trap(vector<int>& height)
{
int ans=0;
stack<int> s,id;
int len=height.size();
for(int i=0;i<len;i++)
{
int pre=0;//前一次计算的海拔,初始化为0
while(!s.empty()&&height[i]>=s.top())//栈不空且当前海拔不小于栈顶海拔
{
ans=ans+(i-id.top()-1)*(s.top()-pre);//更新答案
pre=s.top();//更新上一次计算的海拔
s.pop();
id.pop();//栈顶元素弹出
}
if(!s.empty())//退出后如果栈不空还需要再计算一次
{
ans=ans+(i-id.top()-1)*(height[i]-pre);
pre=s.top();
}
s.push(height[i]);
id.push(i);//压栈
}
return ans;
}

这种方法是基于动态规划 Dynamic Programming 的,维护一个一维的 dp 数组,这个 DP 算法需要遍历两遍数组,第一遍在 dp[i] 中存入i位置左边的最大值,然后开始第二遍遍历数组,第二次遍历时找右边最大值,然后和左边最大值比较取其中的较小值,然后跟当前值 A[i] 相比,如果大于当前值,则将差值存入结果,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int trap(vector<int>& height) {
int res = 0, mx = 0, n = height.size();
vector<int> dp(n, 0);
for (int i = 0; i < n; ++i) {
dp[i] = mx;
mx = max(mx, height[i]);
}
mx = 0;
for (int i = n - 1; i >= 0; --i) {
dp[i] = min(dp[i], mx);
mx = max(mx, height[i]);
if (dp[i] > height[i]) res += dp[i] - height[i];
}
return res;
}
};

Leetcode43. Multiply Strings

Given two non-negative integers num1 and num2 represented as strings, return the product of num1 and num2, also represented as a string.

Example 1:

1
2
Input: num1 = "2", num2 = "3"
Output: "6"

Example 2:

1
2
Input: num1 = "123", num2 = "456"
Output: "56088"

大数乘法

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
class Solution {
public:
string multiply(string num1, string num2) {
int xx = 0;
int len1 = num1.length(), len2 = num2.length();
vector<int> res(len1*len2+2, 0);
int i;
for(i = len1-1; i >= 0; i --) {
for(int j = len2-1; j >= 0; j --) {
int ii = len1-1-i;
int jj = len2-1-j;
res[ii + jj] += (num1[i] - '0') * (num2[j] - '0');
xx = ii + jj;
int k = 0;

while(res[ii + jj + k] > 9) {
res[ii + jj + 1 + k] = (res[ii + jj + 1 + k] + res[ii + jj + k] / 10);
res[ii + jj + k] %= 10;
xx = ii + jj + 1 + k;
k ++;
}
}
}
string ress(xx+1, '0');
for(i = len1*len2+1; i >= 0; i --)
if(res[i] != 0)
break;
if(i == -1)
return "0";
for(int kk = 0; i >= 0; i --, kk ++)
ress[kk] = res[i] + '0';


return ress;
}
};

大佬的做法:

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
class Solution {
public:
// The key idea is based on how we compute the product.
// For emaple, 1234 x 123
// 1) we first compute 1234 x 3 and save the results to the string,
// 2) then we compute 1234 x 2, and notice that we will start from update
// the results from 10th
// 3) now 1234 x 1, update from 100th.
// To make the code simple, we reverse the input string so that we can use
// k = 0, 1 (10th), 2 (100th), 3 (1,000th), ... to update the results.
// In the end we just reverse the string.
// Time : O(len(num1) * len(num2))
string multiply(string num1, string num2) {
if( num1 == "0" || num2 == "0") return "0";
if( num1 == "1") return num2;
if( num2 == "1") return num1;

std::reverse(num1.begin(), num1.end());
std::reverse(num2.begin(), num2.end());
string ans="";
ans.reserve(num1.size() * num2.size());
int k = 0;
for(size_t i=0;i<num2.size();++i,++k){
multiply(num1, num2[i], ans, k);
}
std::reverse(ans.begin(), ans.end());
return ans;
}
int toNumber(char c) { return int(c - '0');}
// multiple a char to a string and updat the result in the resultant string
// k: the starting postion that we start to update the resultant string
void multiply(const string& num, const char c, std::string& res, int k){
int remain = 0;
for(size_t i=0;i<num.size();++i){
int prod = toNumber(num[i]) * toNumber(c) + remain;
if(res.size()<=k) {
remain = prod / 10;
res.push_back((prod - remain * 10)+'0');
}
else{
prod += int(res[k] - '0');
remain = prod / 10;
res[k] = prod - remain * 10 +'0';
}
++k;
}
if (remain != 0){
for(size_t i=k;i<res.size();++i){
int sum = int(res[i]-'0') + remain;
remain = sum / 10;
res[i] = sum - remain * 10+'0';
if (remain == 0){
break;
}
}
if (remain != 0) res.push_back(remain+'0');
}
}
};

Leetcode44. Wildcard Matching

Given an input string (s) and a pattern (p), implement wildcard pattern matching with support for ‘?’ and ‘*’ where:

  • ‘?’ Matches any single character.
  • ‘*’ Matches any sequence of characters (including the empty sequence).

The matching should cover the entire input string (not partial).

Example 1:

1
2
3
Input: s = "aa", p = "a"
Output: false
Explanation: "a" does not match the entire string "aa".

Example 2:

1
2
3
Input: s = "aa", p = "*"
Output: true
Explanation: '*' matches any sequence.

Example 3:

1
2
3
Input: s = "cb", p = "?a"
Output: false
Explanation: '?' matches 'c', but the second letter is 'a', which does not match 'b'.

Example 4:

1
2
3
Input: s = "adceb", p = "*a*b"
Output: true
Explanation: The first '*' matches the empty sequence, while the second '*' matches the substring "dce".

Example 5:

1
2
Input: s = "acdcb", p = "a*c?b"
Output: false

动态规划 Dynamic Programming 是一大神器,因为字符串跟其子串之间的关系十分密切,正好适合 DP 这种靠推导状态转移方程的特性。那么先来定义dp数组吧,使用一个二维 dp 数组,其中 dp[i][j] 表示 s中前i个字符组成的子串和p中前j个字符组成的子串是否能匹配。大小初始化为 (m+1) x (n+1),加1的原因是要包含 dp[0][0] 的情况,因为若s和p都为空的话,也应该返回 true,所以也要初始化 dp[0][0] 为 true。还需要提前处理的一种情况是,当s为空,p为连续的星号时的情况。由于星号是可以代表空串的,所以只要s为空,那么连续的星号的位置都应该为 true,所以先将连续星号的位置都赋为 true。然后就是推导一般的状态转移方程了,如何更新 dp[i][j],首先处理比较 tricky 的情况,若p中第j个字符是星号,由于星号可以匹配空串,所以如果p中的前 j-1 个字符跟s中前i个字符匹配成功了( dp[i][j-1] 为true)的话,则 dp[i][j] 也能为 true。或者若p中的前j个字符跟s中的前i-1个字符匹配成功了( dp[i-1][j] 为true )的话,则 dp[i][j] 也能为 true(因为星号可以匹配任意字符串,再多加一个任意字符也没问题)。若p中的第j个字符不是星号,对于一般情况,假设已经知道了s中前 i-1 个字符和p中前 j-1 个字符的匹配情况(即 dp[i-1][j-1] ),现在只需要匹配s中的第i个字符跟p中的第j个字符,若二者相等( s[i-1] == p[j-1] ),或者p中的第j个字符是问号( p[j-1] == ‘?’ ),再与上 dp[i-1][j-1] 的值,就可以更新 dp[i][j] 了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
bool isMatch(string s, string p) {
int len1 = s.length(), len2 = p.length();
bool dp[len1+1][len2+1];
memset(dp, 0, sizeof(bool)*(len1+1)*(len2+1));
dp[0][0] = true;
for (int i = 1; i <= len2; i ++)
if (p[i-1] == '*')
dp[0][i] = dp[0][i-1];
for (int i = 1; i <= len1; i ++)
for (int j = 1; j <= len2; j ++) {
if (p[j-1] == '*')
dp[i][j] = dp[i-1][j] || dp[i][j-1];
else
dp[i][j] = (s[i-1] == p[j-1] || p[j-1] == '?') && dp[i-1][j-1];
}
return dp[len1][len2];
}
};

Leetcode45. Jump Game II

Given an array of non-negative integers, you are initially positioned at the first index of the array.

Each element in the array represents your maximum jump length at that position.

Your goal is to reach the last index in the minimum number of jumps.

Example:

Input: [2,3,1,1,4]
Output: 2
Explanation: The minimum number of jumps to reach the last index is 2.
Jump 1 step from index 0 to 1, then 3 steps to the last index.
Note:

You can assume that you can always reach the last index.

关键的问题1:到底什么时候总步数+1呢?

  • 回答:假设到遍历到数组index=i的位置,在此之前jump到的位置为k;在位置k最远可以到达的范围是[k,reach],如果reach<i,说明[k-reach]之间必须再jump一次,这样才能保证i在可以reach的范围内!

关键问题2:那究竟在[k-reach]的哪个位置再次jump呢?

  • 回答:根据贪心算法,应该挑可以reach范围最远的那个点,如果需要求jump game的最短次数的jump路径,就需要记录这个点了。

定义两个变量,一个是reach,记下来可以到达的最远距离,另一个是lastreach,是上一步到达的最远距离。对每一个i,代表可以到达的位置,这个位置一定是小于reach的,如果i比上次能够到达的位置大了,就更新。在更新当前的i能够到达的最远位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int jump(vector<int>& nums) {
int res = 0, cur=0;
int reach=nums[0], lastreach=0;
for(int i = 0;i <= reach && i < nums.size(); i ++) {
if (i > lastreach) {
res++;
lastreach = reach;
}
reach = max(reach, i + nums[i]);
}
return res;
}
};

Leetcode46. Permutations

Given a collection of distinct integers, return all possible permutations.

Example:

1
2
3
4
5
6
7
8
9
10
Input: [1,2,3]
Output:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]

是一道典型的组合题,而此题是求全排列问题,还是用递归 DFS 来求解。这里需要用到一个 visited 数组来标记某个数字是否访问过,然后在 DFS 递归函数从的循环应从头开始。为啥这里的 level 要从0开始遍历,因为这是求全排列,每个位置都可能放任意一个数字,这样会有个问题,数字有可能被重复使用,由于全排列是不能重复使用数字的,所以需要用一个 visited 数组来标记某个数字是否使用过。

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
class Solution {
public:

vector<vector<int>> res;

void dfs(vector<int>& nums, vector<int> out, int num, vector<bool> visited) {
if(num == nums.size()) {
res.push_back(out);
}
for(int i = 0; i < nums.size(); i++) {
if(visited[i] == true)
continue;
visited[i] = true;
out.push_back(nums[i]);
dfs(nums, out, num+1, visited);
out.pop_back();
visited[i] = false;
}
}

vector<vector<int>> permute(vector<int>& nums) {
res.clear();
vector<bool> visited(nums.size(), 0);
vector<int> out;
if(nums.size() == 0)
return res;
dfs(nums, out, 0, visited);
return res;
}
};

Leetcode47. Permutations II

Given a collection of numbers that might contain duplicates, return all possible unique permutations.

Example:

1
2
3
4
5
6
7
Input: [1,1,2]
Output:
[
[1,1,2],
[1,2,1],
[2,1,1]
]

这道题是之前那道 Permutations 的延伸,由于输入数组有可能出现重复数字,如果按照之前的算法运算,会有重复排列产生,我们要避免重复的产生,在递归函数中要判断前面一个数和当前的数是否相等,如果相等,且其对应的 visited 中的值为1,当前的数字才能使用(下文中会解释这样做的原因),否则需要跳过,这样就不会产生重复排列了。只是在上一题的基础上加上一个去重的判断。

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<vector<int>> res;

void dfs(vector<int>& nums, vector<int> out, int num, vector<bool> visited) {
if(num == nums.size()) {
res.push_back(out);
}
for(int i = 0; i < nums.size(); i++) {
if(visited[i] == true)
continue;
if(i > 0 && nums[i-1] == nums[i] && visited[i-1] == true) {
continue;
// 去重
}
visited[i] = true;
out.push_back(nums[i]);
dfs(nums, out, num+1, visited);
out.pop_back();
visited[i] = false;
}
}

vector<vector<int>> permuteUnique(vector<int>& nums) {
res.clear();
vector<bool> visited(nums.size(), 0);
vector<int> out;
sort(nums.begin(), nums.end());
if(nums.size() == 0)
return res;
dfs(nums, out, 0, visited);
return res;
}
};

Leetcode48. Rotate Image

You are given an n x n 2D matrix representing an image. Rotate the image by 90 degrees (clockwise).

Note: You have to rotate the image in-place, which means you have to modify the input 2D matrix directly. DO NOT allocate another 2D matrix and do the rotation.

Example 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
Given input matrix = 
[
[1,2,3],
[4,5,6],
[7,8,9]
],

rotate the input matrix in-place such that it becomes:
[
[7,4,1],
[8,5,2],
[9,6,3]
]

Example 2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Given input matrix =
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
],

rotate the input matrix in-place such that it becomes:
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]

对于90度的翻转有很多方法,一步或多步都可以解,先来看一种直接的方法,这种方法是按顺时针的顺序去覆盖前面的数字,从四个顶角开始,然后往中间去遍历,每次覆盖的坐标都是同理,如下:

(i, j) <- (n-1-j, i) <- (n-1-i, n-1-j) <- (j, n-1-i)

这其实是个循环的过程,第一个位置又覆盖了第四个位置,这里i的取值范围是 [0, n/2),j的取值范围是 [i, n-1-i),至于为什么i和j是这个取值范围,为啥i不用遍历 [n/2, n),若仔细观察这些位置之间的联系,不难发现,实际上j列的范围 [i, n-1-i) 顺时针翻转 90 度,正好就是i行的 [n/2, n) 的位置,这个方法每次循环换四个数字,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size();
int temp;
for(int i = 0; i < n / 2; i ++)
for(int j = i; j < n - 1 - i; j ++) {
temp = matrix[i][j];
matrix[i][j] = matrix[n - 1 - j][i];
matrix[n - 1 - j][i] = matrix[n-1-i][n-1-j];
matrix[n-1-i][n-1-j] = matrix[j][n - 1 - i];
matrix[j][n - 1 - i] = temp;
}
}
};

Leetcode49. Group Anagrams

Given an array of strings, group anagrams together.

Example:

1
2
3
4
5
6
7
Input: ["eat", "tea", "tan", "ate", "nat", "bat"],
Output:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]

这道题让我们群组给定字符串集中所有的错位词,所谓的错位词就是两个字符串中字母出现的次数都一样,只是位置不同,比如 abc,bac, cba 等它们就互为错位词,那么如何判断两者是否是错位词呢,可以发现如果把错位词的字符顺序重新排列,那么会得到相同的结果,所以重新排序是判断是否互为错位词的方法,以字母计数后转为字符串作为 key,将所有错位词都保存到字符串数组中,建立 key 和当前的不同的错位词集合个数之间的映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
int a[26] = {0};
unordered_map<string, vector<string>> m;
for(int i = 0; i < strs.size(); i ++) {
int a[26] = {0};
for(int j = 0; j < strs[i].length(); j ++) {
a[strs[i][j]-'a'] ++;
}
string t;
for (int ii = 0; ii < 26; ++ ii) {
if (a[ii] == 0) continue;
t += string(1, ii + 'a') + to_string(a[ii]);
}
m[t].push_back(strs[i]);
}
vector<vector<string>> res;
for(auto a : m)
res.push_back(a.second);
return res;
}
};

Leetcode50. Pow(x, n)

Implement pow(x, n), which calculates x raised to the power n (xn).

Example 1:

1
2
Input: 2.00000, 10
Output: 1024.00000

Example 2:

1
2
Input: 2.10000, 3
Output: 9.26100

Example 3:

1
2
3
Input: 2.00000, -2
Output: 0.25000
Explanation: 2-2 = 1/22 = 1/4 = 0.25

用递归来折半计算,每次把n缩小一半,这样n最终会缩小到0,任何数的0次方都为1,这时候再往回乘,如果此时n是偶数,直接把上次递归得到的值算个平方返回即可,如果是奇数,则还需要乘上个x的值。还有一点需要引起注意的是n有可能为负数,对于n是负数的情况,我可以先用其绝对值计算出一个结果再取其倒数即可,之前是可以的,但是现在 test case 中加了个负2的31次方后,这就不行了,因为其绝对值超过了整型最大值,会有溢出错误,不过可以用另一种写法只用一个函数,在每次递归中处理n的正负,然后做相应的变换即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
double myPow(double x, int n) {
if(n == 0)
return 1;
double half = myPow(x, n/2);
if(n % 2 == 0)
half = half * half;
else if(n > 0)
half = half * half * x;
else
half = half * half / x;
return half;
}
};

Leetcode51. N-Queens

The n-queens puzzle is the problem of placing n queens on an n × n chessboard such that no two queens attack each other.

Given an integer n, return all distinct solutions to the n-queens puzzle.

Each solution contains a distinct board configuration of the n-queens’ placement, where ‘Q’ and ‘.’ both indicate a queen and an empty space respectively.

方法是使用回溯法。类似于走迷宫,由于每一行都只能有一个皇后,所以可以先在第一行放一个皇后,然后在第二行….第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
vector<vector<string>> solveNQueens(int n) {
vector<string> board(n, string(n, '.'));
vector<vector<string>> res;
solveNQueensHelper(n, 0, board, res);
return res;
}

void solveNQueensHelper(int n, int column, vector<string>& board, vector<vector<string>>& res){
if (column == n){// 容易错写成 column == n-1
// base case
res.push_back(board);
} else {
for (int row = 0; row < n; row++){
if (valid_queens(board, column, row)){
// choose
board[row][column] = 'Q';
// explore
solveNQueensHelper(n, column + 1, board, res);
// unchoose
board[row][column] = '.';
}
}
}
}


//确定棋盘上皇后位置是不是有效的
bool valid_queens(vector<string>& board, int column, int row){
int n = board.size();
//1)x = row (横向)
for(int i = 0; i < n; i++)
if (board[row][i]=='Q') return false;

//2) y = col(纵向):默认true

//3)col + row = y + x;(反对角线)
int s = column + row;
for( int i = column - 1; i >= 0 && s - i < n; i--)
if (board[s-i][i]=='Q') return false;

//4) col - row = y - x;(对角线)
s = column - row;
for (int i = column - 1; i >= 0 && i - s >= 0; i--)
if (board[i-s][i] == 'Q') return false;
return true;
}

Leetcode52. N-Queens II

输出上题中的结果个数。

Leetcode53. Maximum Subarray

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

Example:

1
2
3
Input: [-2,1,-3,4,-1,2,1,-5,4],
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.

这道题让求最大子数组之和,并且要用两种方法来解,分别是 O(n) 的解法,还有用分治法 Divide and Conquer Approach,这个解法的时间复杂度是 O(nlgn),那就先来看 O(n) 的解法,定义两个变量 res 和max_local,其中 res 保存最终要返回的结果,即最大的子数组之和,max_local 初始值为0,每遍历一个数字 num,比较 max_local + num 和 num 中的较大值存入 max_local,然后再把 res 和 max_local 中的较大值存入 res,以此类推直到遍历完整个数组,可得到最大子数组的值存在 res 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int res = INT_MIN, sum = 0;
int size = nums.size();
int max_local = 0;
for(int i = 0; i < size; i ++) {
max_local = max(max_local+nums[i], nums[i]);
res = max(max_local, res);
}
return res;
}
};

题目还要求我们用分治法 Divide and Conquer Approach 来解,这个分治法的思想就类似于二分搜索法,需要把数组一分为二,分别找出左边和右边的最大子数组之和,然后还要从中间开始向左右分别扫描,求出的最大值分别和左右两边得出的最大值相比较取最大的那一个。

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
class Solution {
public:
int maxSubArray(vector<int>& nums) {
/* int res = INT_MIN, sum = 0;
int size = nums.size();
int max_local = 0;
for(int i = 0; i < size; i ++) {
max_local = max(max_local+nums[i], nums[i]);
res = max(max_local, res);
}
return res;
*/
if (nums.empty())
return 0;
return helper(nums, 0, nums.size()-1);
}

int helper(vector<int>& nums, int left, int right) {
if(left >= right)
return nums[left];
int mid = (left + right) / 2;
int lmax = helper(nums, left, mid - 1);
int rmax = helper(nums, mid + 1, right);
int mmax = nums[mid], t = mmax;
for (int i = mid - 1; i >= left; --i) {
t += nums[i];
mmax = max(mmax, t);
}
t = mmax;
for (int i = mid + 1; i <= right; ++i) {
t += nums[i];
mmax = max(mmax, t);
}
return max(mmax, max(lmax, rmax));
}
};

Leetcode54. Spiral Matrix

Given a matrix of m x n elements (m rows, n columns), return all elements of the matrix in spiral order.

Example 1:

1
2
3
4
5
6
7
Input:
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
Output: [1,2,3,6,9,8,7,4,5]

Example 2:

1
2
3
4
5
6
7
Input:
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9,10,11,12]
]
Output: [1,2,3,4,8,12,11,10,9,5,6,7]

用顺时针的方式输出一个矩阵,有点烦人。

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
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> res;
if(matrix.size() == 0)
return res;
int count = 0;
int x = 0, y = 1, i = 0, j = 0;
int m = matrix.size(), n = matrix[0].size(), size = m*n;
int left = 0, right = 0, up = 0, down = 0;
while(1) {
for(int i = left; i < n - right; i ++)
res.push_back(matrix[up][i]);
up ++;

for(int i = up; i < m - down; i ++)
res.push_back(matrix[i][n-right-1]);
right ++;

if(up < m - down) {
for(int i = n - right - 1; i >= left; i --) {
res.push_back(matrix[m - down - 1][i]);
}
}
down ++;

if(left < n - right) {
for(int i = m - down - 1; i >= up; i --)
res.push_back(matrix[i][left]);
}
left ++;

if(res.size() >= size)
break;
}
return res;
}
};

Leetcode55. Jump Game

Given an array of non-negative integers, you are initially positioned at the first index of the array. Each element in the array represents your maximum jump length at that position. Determine if you are able to reach the last index.

Example 1:

1
2
3
Input: nums = [2,3,1,1,4]
Output: true
Explanation: Jump 1 step from index 0 to 1, then 3 steps to the last index.

Example 2:

1
2
3
Input: nums = [3,2,1,0,4]
Output: false
Explanation: You will always arrive at index 3 no matter what. Its maximum jump length is 0, which makes it impossible to reach the last index.

Constraints:

1 <= nums.length <= 3 * 10^4
0 <= nums[i][j] <= 10^5

给出一串n个非负数的序列nums,其中nums[i]表示你当前在第i个数时最多能前进num[i]步,初始时你在第1个数,问:最后能否到第n个数?举例说明:A = [2,3,1,1,4], return true. A = [3,2,1,0,4], return false.

解题思路:
根据题意可知,对于点i,在该点能到达的最远位置为i+nums[i],前提是,能到达点i。这样我们可以从左到右遍历,维护一个当前所能到达的最大边界reach,始终保持当前遍历的点i <= reach,这样点i都是可以到达的,再根据nums[i]+i来更新reach的大小。若最后能遍历到点n,返回true,否则返回false。

算法正确性:
每次遍历都是在已经能到达的范围内,接着计算的结果可以更新边界。保证走出的每一步都是可以到达的,故算法正确。算法复杂度O(n)。

1
2
3
4
5
6
7
8
9
10
class Solution {
public:
bool canJump(vector<int>& nums) {
int result = 0;
for(int i = 0; i < nums.size() && result >= i; i ++) {
result = max(nums[i] + i, result);
}
return result >= nums.size()-1;
}
};

Leetcode56. Merge Intervals

Given a collection of intervals, merge all overlapping intervals.
一些区间,要求合并重叠的区间,返回一个vector保存结果。

Example 1:

1
2
3
Input: [[1,3],[2,6],[8,10],[15,18]]
Output: [[1,6],[8,10],[15,18]]
Explanation: Since intervals [1,3] and [2,6] overlaps, merge them into [1,6].

Example 2:

1
2
3
Input: [[1,4],[4,5]]
Output: [[1,5]]
Explanation: Intervals [1,4] and [4,5] are considered overlapping.

贪心思路,将初始区间序列ins按照左端点的从小到大排序,接着遍历ins。 一开始将第一个区间ins[0]放入结果区间序列res,接着每次遍历到一个新的区间[l,r],将其与当前合并后的最后一个区间[L,R]比较:

若l <= R,说明新区间与当前最有一个区间有重叠,应该将这两个区间合并,也就需要修改当前最后一个区间为[L,max(r,R)]。
若l > R,说明新区间与当前最后一个区间没有重叠,所以不需要合并,直接将新区间加入结果序列res,成为新的最后一个区间。

算法正确性:

在上述贪心思路中,只考虑了新区间的左端点与最后一个区间的右端点的大小比较,最后只会对最后区间的右端点进行修改,却不会修改左端点。之所以不考虑左端点,是因为初始化时已经将ins按照左端点排序,保证后遍历的左端点l >= 之前遍历过的左端点L。 算法复杂度为O(nlogn)。

我的代码:

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
class Solution {
public:
static bool comp(const Interval &a, const Interval &b) {
return a.start < b.start;
}

vector<Interval> merge(vector<Interval>& intervals) {
vector<Interval> answer;
if(intervals.size() == 0)
return answer;
sort(intervals.begin(), intervals.end(), comp);
Interval ttt(intervals[0].start, intervals[0].end);
vector<Interval>::iterator it = intervals.begin();
it ++;
for(; it != intervals.end(); it++){
if(ttt.end >= it->start){
if(ttt.end < it->end)
ttt.end = it->end;
}
else if(ttt.end < it->start){
answer.push_back(ttt);
ttt.start = it->start;
ttt.end = it->end;
}
}
answer.push_back(ttt);
return answer;

}
};

题解的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
static bool cmp(const Interval &a, const Interval &b) {
return a.start < b.start;
}

vector<Interval> merge(vector<Interval>& ins) {
vector <Interval> res;
if (ins.empty()) return res;
sort(ins.begin(), ins.end(), cmp);
res.push_back(ins[0]);
int cnt = ins.size();
for (int i = 1; i < cnt; i++) {
if (ins[i].start <= res.back().end) {
res.back().end = max(res.back().end, ins[i].end);
}
else {
res.push_back(ins[i]);
}
}
return res;
}
};

Solution
Approach 1: Connected Components
Intuition

If we draw a graph (with intervals as nodes) that contains undirected edges between all pairs of intervals that overlap, then all intervals in each connected component of the graph can be merged into a single interval.

Algorithm

With the above intuition in mind, we can represent the graph as an adjacency list, inserting directed edges in both directions to simulate undirected edges. Then, to determine which connected component each node is it, we perform graph traversals from arbitrary unvisited nodes until all nodes have been visited. To do this efficiently, we store visited nodes in a Set, allowing for constant time containment checks and insertion. Finally, we consider each connected component, merging all of its intervals by constructing a new Interval with start equal to the minimum start among them and end equal to the maximum end.

This algorithm is correct simply because it is basically the brute force solution. We compare every interval to every other interval, so we know exactly which intervals overlap. The reason for the connected component search is that two intervals may not directly overlap, but might overlap indirectly via a third interval. See the example below to see this more clearly.

Components Example

Although (1, 5) and (6, 10) do not directly overlap, either would overlap with the other if first merged with (4, 7). There are two connected components, so if we merge their nodes, we expect to get the following two merged intervals:

(1, 10), (15, 20)

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
class Solution {
private Map<Interval, List<Interval> > graph;
private Map<Integer, List<Interval> > nodesInComp;
private Set<Interval> visited;

// return whether two intervals overlap (inclusive)
private boolean overlap(Interval a, Interval b) {
return a.start <= b.end && b.start <= a.end;
}

// build a graph where an undirected edge between intervals u and v exists
// iff u and v overlap.
private void buildGraph(List<Interval> intervals) {
graph = new HashMap<>();
for (Interval interval : intervals) {
graph.put(interval, new LinkedList<>());
}

for (Interval interval1 : intervals) {
for (Interval interval2 : intervals) {
if (overlap(interval1, interval2)) {
graph.get(interval1).add(interval2);
graph.get(interval2).add(interval1);
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution:
def merge(self, intervals):
intervals.sort(key=lambda x: x.start)

merged = []
for interval in intervals:
# if the list of merged intervals is empty or if the current
# interval does not overlap with the previous, simply append it.
if not merged or merged[-1].end < interval.start:
merged.append(interval)
else:
# otherwise, there is overlap, so we merge the current and previous
# intervals.
merged[-1].end = max(merged[-1].end, interval.end)

return merged

Leetcode57. Insert Interval

Given a set of non-overlapping intervals, insert a new interval into the intervals (merge if necessary). You may assume that the intervals were initially sorted according to their start times.

Example 1:

1
2
Input: intervals = [[1,3],[6,9]], newInterval = [2,5]
Output: [[1,5],[6,9]]

Example 2:

1
2
3
Input: intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]
Output: [[1,2],[3,10],[12,16]]
Explanation: Because the new interval [4,8] overlaps with [3,5],[6,7],[8,10].

NOTE: input types have been changed on April 15, 2019. Please reset to default code definition to get new method signature.

这道题让我们在一系列非重叠的区间中插入一个新的区间,可能还需要和原有的区间合并,可以对给定的区间集进行一个一个的遍历比较,那么会有两种情况,重叠或是不重叠,不重叠的情况最好,直接将新区间插入到对应的位置即可,重叠的情况比较复杂,有时候会有多个重叠,需要更新新区间的范围以便包含所有重叠,之后将新区间加入结果 res,最后将后面的区间再加入结果 res 即可。具体思路是,用一个变量 cur 来遍历区间,如果当前 cur 区间的结束位置小于要插入的区间的起始位置的话,说明没有重叠,则将 cur 区间加入结果 res 中,然后 cur 自增1。直到有 cur 越界或有重叠 while 循环退出,然后再用一个 while 循环处理所有重叠的区间,每次用取两个区间起始位置的较小值,和结束位置的较大值来更新要插入的区间,然后 cur 自增1。直到 cur 越界或者没有重叠时 while 循环退出。之后将更新好的新区间加入结果 res,然后将 cur 之后的区间再加入结果 res 中即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) {
int length = intervals.size();
vector<vector<int>> result;
int begin, end, last_end, i=0;
while(i < length && intervals[i][1] < newInterval[0]) {
result.push_back(intervals[i]);
i++;
}
while(i < length && intervals[i][0] <= newInterval[1]) {
newInterval[0] = min(intervals[i][0],newInterval[0]);
newInterval[1] = max(intervals[i][1],newInterval[1]);
i++;
}
result.push_back(newInterval);
while(i < length) {
result.push_back(intervals[i]);
i++;
}
return result;
}
};

Leetcode58. Length of Last Word

Given a string s consists of upper/lower-case alphabets and empty space characters ‘ ‘, return the length of last word (last word means the last appearing word if we loop from left to right) in the string.
If the last word does not exist, return 0.

Note: A word is defined as a maximal substring consisting of non-space characters only.

Example:

1
2
Input: "Hello World"
Output: 5

找到一个字符串里的最后一个单词,有很多的特殊样例:比如" "" ""a """等等吧,错了好几次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int lengthOfLastWord(string s) {
int size = s.length();
if(size == 0 || size == 1 && s[0] == ' ')
return 0;
int res = 0, i = size - 1;
while(i >= 0 && s[i] == ' ')
i--;
for(; i >= 0 && s[i] != ' '; i --) {
res ++;
}
return res;
}
};

Leetcode59. Spiral Matrix II

Given a positive integer n, generate a square matrix filled with elements from 1 to n^2 in spiral order.

Example:

1
2
3
4
5
6
7
Input: 3
Output:
[
[ 1, 2, 3 ],
[ 8, 9, 4 ],
[ 7, 6, 5 ]
]

先清空矩阵,把数字从小到大填进去,那么就是一直往前走的一条线,每次这条线到尽头或者到一个填过的点就右转(初始在[0,0]位置方向向右)。那么就可以直接拿x方向的增量和y方向的增量来模拟,每次试着从上一次的增量方向前进,如果到了边界外或者到过的点,就修正方向(右转),并继续前进,直至填的数字大于n*n。

如n=2的情况,初始化:

1
2
[0, 0],
[0, 0]

当前位置[0,0],x增量0,y增量1,填的数字是1
填充,前进一步:

1
2
[1, 0],
[0, 0]

当前位置[0,1],x增量0,y增量1,填的数字是2
填充,试着前进一步,发现出了边界,修正方向为向下。重新前进一步:

1
2
[1, 2],
[0, 0]

当前位置[1,1],x增量1,y增量0,填的数字是3
填充,试着前进一步,发现出了边界,修正方向为向左。重新前进一步:

1
2
[1, 2],
[0, 3]

当前位置[1,0],x增量0,y增量-1,填的数字是4
填充,填完后填的数字变成了5,大于2*2,结束。

1
2
[1, 2],
[4, 3]
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
class Solution {
public:
void turn(int &x, int &y) {
if(x==0 && y==1)
x=1,y=0;
else if(x==0 && y==-1)
x=-1,y=0;
else if(x==1 && y==0)
x=0,y=-1;
else if(x==-1 && y==0)
x=0,y=1;
}
vector<vector<int>> generateMatrix(int n) {
int x_dir = 0, y_dir = 1;
int x = 0, y = 0;
vector<vector<int>> result(n, vector<int>(n, 0));
for(int i = 1; i <= n*n; i ++) {
result[x][y] = i;
if(x+x_dir<0 || x+x_dir>=n || y+y_dir<0 || y+y_dir>=n || result[x+x_dir][y+y_dir])
turn(x_dir, y_dir);
x += x_dir;
y += y_dir;
}
return result;
}
};

Leetcode60. Permutation Sequence

The set [1,2,3,…,n] contains a total of n! unique permutations. By listing and labeling all of the permutations in order, we get the following sequence for n = 3:

1
2
3
4
5
6
"123"
"132"
"213"
"231"
"312"
"321"

Given n and k, return the kth permutation sequence.

Note:

  • Given n will be between 1 and 9 inclusive.
  • Given k will be between 1 and n! inclusive.

Example 1:

1
2
Input: n = 3, k = 3
Output: "213"

Example 2:

1
2
Input: n = 4, k = 9
Output: "2314"

这道题是让求出n个数字的第k个排列组合,由于其特殊性,我们不用将所有的排列组合的情况都求出来,然后返回其第k个,这里可以只求出第k个排列组合即可,那么难点就在于如何知道数字的排列顺序。首先要知道当 n = 3 时,其排列组合共有 3! = 6 种,当 n = 4 时,其排列组合共有 4! = 24 种,这里就以 n = 4, k = 17 的情况来分析,所有排列组合情况如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1234
1243
1324
1342
1423
1432
2134
2143
2314
2341
2413
2431
3124
3142
3214
3241
3412 <--- k = 17
3421
4123
4132
4213
4231
4312
4321

可以发现,每一位上 1,2,3,4 分别都出现了6次,当最高位上的数字确定了,第二高位每个数字都出现了2次,当第二高位也确定了,第三高位上的数字都只出现了1次,当第三高位确定了,那么第四高位上的数字也只能出现一次,下面来看 k = 17 这种情况的每位数字如何确定,由于 k = 17 是转化为数组下标为 16:

最高位可取 1,2,3,4 中的一个,每个数字出现 3!= 6 次(因为当最高位确定了,后面三位可以任意排列,所以是 3!,那么最高位的数字就会重复 3!次),所以 k = 16 的第一位数字的下标为 16 / 6 = 2,在 “1234” 中即3被取出。这里的k是要求的坐标为k的全排列序列,定义 k’ 为当最高位确定后,要求的全排序列在新范围中的位置,同理,k’’ 为当第二高为确定后,所要求的全排列序列在新范围中的位置,以此类推,下面来具体看看:

第二位此时从 1,2,4 中取一个,k = 16,则此时的 k’ = 16 % (3!) = 4,注意思考这里为何要取余,如果对这 24 个数以6个一组来分,那么 k=16 这个位置就是在第三组(k/6 = 2)中的第五个(k%6 = 4)数字。如下所示,而剩下的每个数字出现 2!= 2 次,所以第二数字的下标为 4 / 2 = 2,在 “124” 中即4被取出。

1
2
3
4
5
6
3124
3142
3214
3241
3412 <--- k' = 4
3421

第三位此时从 1,2 中去一个,k’ = 4,则此时的 k’’ = 4 % (2!) = 0,如下所示,而剩下的每个数字出现 1!= 1 次,所以第三个数字的下标为 0 / 1 = 0,在 “12” 中即1被取出。

1
2
3412 <--- k'' = 0
3421

第四位是从2中取一个,k’’ = 0,则此时的 k’’’ = 0 % (1!) = 0,如下所示,而剩下的每个数字出现 0!= 1 次,所以第四个数字的下标为 0 / 1= 0,在 “2” 中即2被取出。

1
3412 <--- k''' = 0

那么就可以找出规律了

1
2
3
4
5
6
7
8
9
10
11
12
a1 = k / (n - 1)!
k1 = k

a2 = k1 / (n - 2)!
k2 = k1 % (n - 2)!
...

an-1 = kn-2 / 1!
kn-1 = kn-2 % 1!

an = kn-1 / 0!
kn = kn-1 % 0!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
string getPermutation(int n, int k) {
string res;
string num = "123456789";
vector<int> f(n, 1);
k --;
for(int i = 1; i < n; i ++) {
f[i] = f[i-1] * i;
}
for(int i = n; i >= 1; i --) {
int j = k / f[i-1];
k %= f[i - 1];
res.push_back(num[j]);
num.erase(j, 1);
}
return res;
}
};

Leetcode61. Rotate List

Given a linked list, rotate the list to the right by k places, where k is non-negative.

Example 1:

1
2
3
4
5
Input: 1->2->3->4->5->NULL, k = 2
Output: 4->5->1->2->3->NULL
Explanation:
rotate 1 steps to the right: 5->1->2->3->4->NULL
rotate 2 steps to the right: 4->5->1->2->3->NULL

Example 2:

1
2
3
4
5
6
7
Input: 0->1->2->NULL, k = 4
Output: 2->0->1->NULL
Explanation:
rotate 1 steps to the right: 2->0->1->NULL
rotate 2 steps to the right: 1->2->0->NULL
rotate 3 steps to the right: 0->1->2->NULL
rotate 4 steps to the right: 2->0->1->NULL

先遍历一次链表,将尾部和头部相连,再进行移动。注意右移k步相当于prehead travel len - k步。

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
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if(head==NULL)
return head;
ListNode* cur_head = head, *tail;
int len = 1;
while(cur_head->next != NULL) {
cur_head = cur_head->next;
len ++;
}
tail = cur_head;
tail->next = head;

cur_head = head;
k =len - (len + (k % len)) % len;
while(k --) {
head = head->next;
}
while(cur_head->next != head) {
cur_head = cur_head->next;
}
cur_head->next = NULL;
return head;
}
};

Leetcode62. Unique Paths

A robot is located at the top-left corner of a m x n grid (marked ‘Start’ in the diagram below).

The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked ‘Finish’ in the diagram below).

How many possible unique paths are there?

Example 1:

1
2
Input: m = 3, n = 2
Output: 3

Explanation:
From the top-left corner, there are a total of 3 ways to reach the bottom-right corner:

  1. Right -> Right -> Down
  2. Right -> Down -> Right
  3. Down -> Right -> Right

Example 2:

1
2
Input: m = 7, n = 3
Output: 28
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
int uniquePaths(int m, int n) {
int dp[m+1][n+1];
for(int i=0;i<=m;i++)
dp[i][0] = 0;
for(int i=0;i<=n;i++)
dp[0][i] = 0;

for(int i = 1; i <= m; ++i)
{
for(int j = 1; j <= n; ++j)
{
if(i==1 && j==1)
dp[i][j]=1;
else
dp[i][j] = dp[i-1][j] + dp[i][j - 1];
}
}
return dp[m][n];
}
};

Leetcode63. Unique Paths II

A robot is located at the top-left corner of a m x n grid (marked ‘Start’ in the diagram below).

The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked ‘Finish’ in the diagram below).

Now consider if some obstacles are added to the grids. How many unique paths would there be?

Note: m and n will be at most 100.

Example 1:

1
2
3
4
5
6
7
Input:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
Output: 2

Explanation:
There is one obstacle in the middle of the 3x3 grid above.

There are two ways to reach the bottom-right corner:

  1. Right -> Right -> Down -> Down
  2. Down -> Down -> Right -> Right

第62题(Unique Paths)的升级版. 现在需要考虑如果表格中存在一些障碍,那么所要求的路径数还有多少条? 在表格表示中,1表示此位置有障碍,0表示没有. 例如在一个3 x 3的表格中存在一个障碍物,

[
[0,0,0],
[0,1,0],
[0,0,0]
]
求得最终的路径数为2. 注意:m 和 n 均不超过100.

题解
算法及复杂度(3 ms) 本题解法参考第62题(Unique Paths).本题和62题的唯一区别是存在了障碍物.但是这个对于算法是没有影响的. 在62题算法的基础上,在求解的过程中,每个点判断本点是否是障碍物,如果是则将dpi置0即可. 参考代码中与62题代码只添加了4行. 时间复杂度: O(mn),表格中每个位置进行一次计算即可. 代码参见本文件夹下solution.cpp

算法正确性
正确性证明 UniquePaths 举个例子

// 输入数据
obstacleGrid = [
[0,0,0],
[0,1,0],
[0,0,0]
]

//初始化m = 3, n = 3, p[0][0:n] = 0, dp[0:m][0] = 0, dp[1][1] = 1

//求解
dp[1][2] = dp[0][2] + dp[1][1] = 1
dp[1][3] = dp[0][3] + dp[1][2] = 1
dp[2][1] = dp[1][1] + dp[2][0] = 1
dp[2][2] = dp[1][2] + dp[2][1] = 2,由于obstacleGrid[1][1]位置为1,经过换算,也就是此位置为1,则dp[2][2] = 0
dp[2][3] = dp[1][3] + dp[2][2] = 1
dp[3][1] = dp[2][1] + dp[3][0] = 1
dp[3][2] = dp[2][2] + dp[3][1] = 1
dp[3][3] = dp[2][3] + dp[3][2] = 2

//return 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
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int m = obstacleGrid.size();
if(m == 0)
return 0;
int n = obstacleGrid[0].size();
if(n == 0)
return 0;
int dp[m+1][n+1];
for(int i = 0; i <= m; i ++)
for(int j = 0; j <= n; j ++)
dp[i][j] = 0;
for(int i = 0; i <= m; i ++) dp[i][0] = 0;
for(int i = 0; i <= n; i ++) dp[0][i] = 0;

for(int i = 1; i <= m; i ++) {
for(int j = 1; j <= n; j ++) {
if(i == 1 && j == 1)
dp[i][j] = 1;
else
dp[i][j] = dp[i][j-1] + dp[i-1][j];
if(obstacleGrid[i-1][j-1] == 1)
dp[i][j] = 0;
}
}
return dp[m][n];
}
};

Leetcode64. Minimum Path Sum

Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path.

Note: You can only move either down or right at any point in time.

Example:

1
2
3
4
5
6
7
8
Input:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
Output: 7
Explanation: Because the path 1→3→1→1→1 minimizes the sum.

本来用的是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
class Solution {
public:

void dfs(vector<vector<int>>& grid, int x, int y, int cur, int& res) {
int dir[2][2] = {{1, 0}, {0, 1}};
if(x == grid.size() - 1 && y == grid[0].size()-1) {
res = min(res, cur);
return;
}
for(int i = 0; i < 2; i ++) {
int tmp_x = x + dir[i][0];
int tmp_y = y + dir[i][1];
if(!(0 <= tmp_x && tmp_x < grid.size() && 0 <= tmp_y && tmp_y < grid[0].size() ))
continue;
cur += grid[tmp_x][tmp_y];
if(cur >= res) {
cur -= grid[tmp_x][tmp_y];
continue;
}
dfs(grid, tmp_x, tmp_y, cur, res);
cur -= grid[tmp_x][tmp_y];
}
}

int minPathSum(vector<vector<int>>& grid) {
int res = 999999;
dfs(grid, 0, 0, grid[0][0], res);
return res;
}
};

看这情况要上dp了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:

int minPathSum(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();

for(int i = 1; i < m; i ++)
grid[i][0] += grid[i-1][0];
for(int i = 1; i < n; i ++)
grid[0][i] += grid[0][i-1];

for(int i = 1; i < m; i ++)
for(int j = 1; j < n; j ++)
grid[i][j] = min(grid[i-1][j], grid[i][j-1]) + grid[i][j];
return grid[m-1][n-1];
}
};

Leetcode65. Valid Number

A valid number can be split up into these components (in order):

  • A decimal number or an integer.
  • (Optional) An ‘e’ or ‘E’, followed by an integer.

A decimal number can be split up into these components (in order):

  • (Optional) A sign character (either ‘+’ or ‘-‘).
  • One of the following formats:
    • One or more digits, followed by a dot ‘.’.
    • One or more digits, followed by a dot ‘.’, followed by one or more digits.
    • A dot ‘.’, followed by one or more digits.

An integer can be split up into these components (in order):

  • (Optional) A sign character (either ‘+’ or ‘-‘).
  • One or more digits.

For example, all the following are valid numbers: [“2”, “0089”, “-0.1”, “+3.14”, “4.”, “-.9”, “2e10”, “-90E3”, “3e+7”, “+6e-1”, “53.5e93”, “-123.456e789”], while the following are not valid numbers: [“abc”, “1a”, “1e”, “e3”, “99e2.5”, “–6”, “-+3”, “95a54e53”].

Given a string s, return true if s is a valid number.

corner case很多,慢慢调试。1488个样例,是有多齐全hhh。

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
class Solution {
public:

bool isnum(char c) {
return c >= '0' && c <= '9';
}

bool isNumber(string s) {
if (s.length() == 0)
return false;
int idx = 0;
if (s[0] == '+' || s[0] == '-')
idx ++;
int num_num = 0, e_num = 0, dot_num = 0;
for (int i = idx; i < s.length(); i ++) {
if (isnum(s[i]))
num_num ++;
else if (s[i] == '.') {
if (dot_num > 0 || e_num > 0)
return false;
dot_num ++;
}
else if (s[i] == 'e' || s[i] == 'E') {
if (e_num > 0 || num_num == 0)
return false;
e_num ++;
if (i+1 >= s.length())
return false;
if (s[i+1] == '+' || s[i+1] == '-') {
if (i+2 >= s.length())
return false;
i ++;
}
}
else
return false;
}
return num_num > 0;
}
};

自动状态机yyds:

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

class Solution {
public:
bool isNumber(const char *s) {
enum InputType {
INVALID, // 0 Include: Alphas, '(', '&' ans so on
SPACE, // 1
SIGN, // 2 '+','-'
DIGIT, // 3 numbers
DOT, // 4 '.'
EXPONENT, // 5 'e' 'E'
};
int transTable[][6] = {
//0INVA,1SPA,2SIG,3DI,4DO,5E
-1, 0, 3, 1, 2, -1,//0初始无输入或者只有space的状态
-1, 8, -1, 1, 4, 5,//1输入了数字之后的状态
-1, -1, -1, 4, -1, -1,//2前面无数字,只输入了Dot的状态
-1, -1, -1, 1, 2, -1,//3输入了符号状态
-1, 8, -1, 4, -1, 5,//4前面有数字和有dot的状态
-1, -1, 6, 7, -1, -1,//5'e' or 'E'输入后的状态
-1, -1, -1, 7, -1, -1,//6输入e之后输入Sign的状态
-1, 8, -1, 7, -1, -1,//7输入e后输入数字的状态
-1, 8, -1, -1, -1, -1,//8前面有有效数输入之后,输入space的状态
};
int state = 0;
while (*s)
{
InputType input = INVALID;
if (*s == ' ') input = SPACE;
else if (*s == '+' || *s == '-') input = SIGN;
else if (isdigit(*s)) input = DIGIT;
else if (*s == '.') input = DOT;
else if (*s == 'e' || *s == 'E') input = EXPONENT;
state = transTable[state][input];
if (state == -1) return false;
++s;
}
return state == 1 || state == 4 || state == 7 || state == 8;
}
};

Leetcode66. Plus One

Given a non-empty array of digits representing a non-negative integer, plus one to the integer.

The digits are stored such that the most significant digit is at the head of the list, and each element in the array contain a single digit. You may assume the integer does not contain any leading zero, except the number 0 itself.

Example 1:

1
2
3
Input: [1,2,3]
Output: [1,2,4]
Explanation: The array represents the integer 123.

Example 2:

1
2
3
Input: [4,3,2,1]
Output: [4,3,2,2]
Explanation: The array represents the integer 4321.

加一的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
digits[digits.size() - 1] += 1;
for(int i = digits.size() - 1; i >= 1; i --) {
if(digits[i] == 10) {
digits[i] = 0;
digits[i-1] ++;
}
}
if(digits[0] == 10) {
digits.insert(digits.begin(), 1);
digits[1] = 0;
}
return digits;
}
};

Leetcode67. Add Binary

Given two binary strings, return their sum (also a binary string). The input strings are both non-empty and contains only characters 1 or 0.

Example 1:

1
2
Input: a = "11", b = "1"
Output: "100"

Example 2:

1
2
Input: a = "1010", b = "1011"
Output: "10101"

设置一个进位标志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
string addBinary(string a, string b) {
string res = "";
int aa = a.length() - 1;
int bb = b.length() - 1;
int carry = 0;
int sum = 0;
while(aa >= 0 || bb >= 0 || carry) {
sum = carry;
if(aa >= 0)
sum += (a[aa--] - '0');
if(bb >= 0)
sum += (b[bb--] - '0');
res = to_string(sum%2) + res;
carry = sum / 2;
}
return res;
}
};

Leetcode68. Text Justification

Given an array of words and a width maxWidth, format the text such that each line has exactly maxWidth characters and is fully (left and right) justified.

You should pack your words in a greedy approach; that is, pack as many words as you can in each line. Pad extra spaces ‘ ‘ when necessary so that each line has exactly maxWidth characters.

Extra spaces between words should be distributed as evenly as possible. If the number of spaces on a line do not divide evenly between words, the empty slots on the left will be assigned more spaces than the slots on the right.

For the last line of text, it should be left justified and no extra space is inserted between words.

Note:

  • A word is defined as a character sequence consisting of non-space characters only.
  • Each word’s length is guaranteed to be greater than 0 and not exceed maxWidth.
  • The input array words contains at least one word.

Example 1:

1
2
3
4
5
6
7
Input: words = ["This", "is", "an", "example", "of", "text", "justification."], maxWidth = 16
Output:
[
"This is an",
"example of text",
"justification. "
]

Example 2:

1
2
3
4
5
6
7
8
9
Input: words = ["What","must","be","acknowledgment","shall","be"], maxWidth = 16
Output:
[
"What must be",
"acknowledgment ",
"shall be "
]
Explanation: Note that the last line is "shall be " instead of "shall be", because the last line must be left-justified instead of fully-justified.
Note that the second line is also left-justified becase it contains only one word.

Example 3:

1
2
3
4
5
6
7
8
9
10
Input: words = ["Science","is","what","we","understand","well","enough","to","explain","to","a","computer.","Art","is","everything","else","we","do"], maxWidth = 20
Output:
[
"Science is what we",
"understand well",
"enough to explain to",
"a computer. Art is",
"everything else we",
"do "
]

由于返回的结果是多行的,所以我们在处理的时候也要一行一行的来处理,首先要做的就是确定每一行能放下的单词数,这个不难,就是比较n个单词的长度和加上n - 1个空格的长度跟给定的长度L来比较即可,找到了一行能放下的单词个数,然后计算出这一行存在的空格的个数,是用给定的长度L减去这一行所有单词的长度和。得到了空格的个数之后,就要在每个单词后面插入这些空格,这里有两种情况,比如某一行有两个单词”to” 和 “a”,给定长度L为6,如果这行不是最后一行,那么应该输出”to a”,如果是最后一行,则应该输出 “to a “,所以这里需要分情况讨论,最后一行的处理方法和其他行之间略有不同。最后一个难点就是,如果一行有三个单词,这时候中间有两个空,如果空格数不是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
class Solution {
public:
vector<string> fullJustify(vector<string> &words, int L) {
vector<string> res;
int i = 0;
while (i < words.size()) {
int j = i, len = 0;
while (j < words.size() && len + words[j].size() + j - i <= L) {
len += words[j++].size();
}
string out;
int space = L - len;
for (int k = i; k < j; ++k) {
out += words[k];
if (space > 0) {
int tmp;
if (j == words.size()) {
if (j - k == 1) tmp = space;
else tmp = 1;
} else {
if (j - k - 1 > 0) {
if (space % (j - k - 1) == 0) tmp = space / (j - k - 1);
else tmp = space / (j - k - 1) + 1;
} else tmp = space;
}
out.append(tmp, ' ');
space -= tmp;
}
}
res.push_back(out);
i = j;
}
return res;
}
};

Leetcode69. Sqrt(x)

Implement int sqrt(int x). Compute and return the square root of x, where x is guaranteed to be a non-negative integer.

Since the return type is an integer, the decimal digits are truncated and only the integer part of the result is returned.

Example 1:

1
2
Input: 4
Output: 2

Example 2:

1
2
3
4
Input: 8
Output: 2
Explanation: The square root of 8 is 2.82842..., and since
the decimal part is truncated, 2 is returned.

二分求一个数的开方,做的恶心,垃圾题,浪费时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:

int mySqrt(int x) {
int low = 0, high = x, mid;
if(x<2) return x; // to avoid mid = 0
while(low<high)
{
mid = (low + high)/2;
if(x/mid >= mid) low = mid+1;
else high = mid;
}
return high-1;
}
};

Leetcode70. Climbing Stairs

You are climbing a stair case. It takes n steps to reach to the top.

Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?

Note: Given n will be a positive integer.

Example 1:

1
2
3
4
5
Input: 2
Output: 2
Explanation: There are two ways to climb to the top.
1. 1 step + 1 step
2. 2 steps

Example 2:

1
2
3
4
5
6
Input: 3
Output: 3
Explanation: There are three ways to climb to the top.
1. 1 step + 1 step + 1 step
2. 1 step + 2 steps
3. 2 steps + 1 step

试着倒推想一下,就能发现这个问题可以被分解为一些包含最优子结构的子问题,它的最优解可以从其子问题
的最优解来有效地构建,因此我们可以使用动态规划解决这个问题.

第 i 阶可以由以下两种方法得到:

  • 在第 (i - 1) 阶后向上爬 1 阶。
  • 在第 (i - 2) 阶后向上爬 2 阶
  • 所以到达第 i 阶的方法总数就是到第 (i - 1) 阶和第 (i - 2) 阶的方法数之和。

dp[i]表示能到达第 i 阶的方法总数,那么DP推导公式就是:

1
dp[i] = dp[i − 1] + dp[i − 2]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
int climbStairs(int n) {
int dp[n+1];
if (n==1 || n==2)
return n;
dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;i++) {
dp[i] = dp[i-1]+dp[i-2];
}
return dp[n];
}
};

进一步优化:根据推导公式不难发现,我们要求的结果就是数组的最后一项,而最后一项又是前面数值叠加起来的,那么我们只需要两个变量保存 i - 1 和 i - 2 的值就可以了.

1
2
3
4
5
6
7
8
9
10
11
12
var climbStairs = function(n) {
if (n == 1) {
return 1;
}
let first = 1, second = 2;
for (let i = 3; i <= n; i++) {
let third = first + second;
first = second;
second = third;
}
return second;
}

复杂度分析

  • 时间复杂度:O(n),单循环到 n。
  • 空间复杂度:O(1),用到了常量的空间。

Leetcode71. Simplify Path

Given an absolute path for a file (Unix-style), simplify it. Or in other words, convert it to the canonical path.

In a UNIX-style file system, a period . refers to the current directory. Furthermore, a double period .. moves the directory up a level.

Note that the returned canonical path must always begin with a slash /, and there must be only a single slash / between two directory names. The last directory name (if it exists) must not end with a trailing /. Also, the canonical path must be the shortest string representing the absolute path.

Example 1:

1
2
3
Input: "/home/"
Output: "/home"
Explanation: Note that there is no trailing slash after the last directory name.

Example 2:

1
2
3
Input: "/../"
Output: "/"
Explanation: Going one level up from the root directory is a no-op, as the root level is the highest level you can go.

Example 3:

1
2
3
Input: "/home//foo/"
Output: "/home/foo"
Explanation: In the canonical path, multiple consecutive slashes are replaced by a single one.

Example 4:

1
2
Input: "/a/./b/../../c/"
Output: "/c"

Example 5:

1
2
Input: "/a/../../b/../c//.//"
Output: "/c"

Example 6:

1
2
Input: "/a//b////c/d//././/.."
Output: "/a/b/c"

算法及复杂度 (6 ms) :本题主要的处理对象分为: “.”, “..”, “/“, 普通文件或目录名.其中”.”的作用是保持当前的目录,”..”的作用是退回上一级目录,”/“的作用的分隔符, 普通文件或目录名不需要进行特殊处理. 很容易的思路(模拟),根据”/“对所有字符串进行分割,得到不同的三类字符串: “.”, “..”, 普通文件或目录名.分割过程是比较容易实现的,就是简单的读取字符串,然后分割. 由于”..”有回退的作用,因此可以考虑使用stack进行实现.在上一段中叙述的分割的过程中进行处理:

  • 遇到普通目录名就进行压栈;
  • 遇到”.”就跳过不处理;
  • 遇到”..”就对栈进行弹出(保证栈不为空的情况下).

存在问题的几点:

  • 输入字符串为空字符串,则返回空字符串,而不是根目录”/“;
  • 输入字符串的第一个字符一定是’/‘,而不是任意的(leetcode的参考程序会报错);
  • 存在”…”, “….”这样的路径,在本题中被认为是普通的目录或文件名.

时间复杂度: O(n). n 表示输入字符串的长度,只需要一次遍历就可以完成,因此是 O(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
class Solution {
public:
string simplifyPath(string path) {
string temp, result;
int length = path.length();
stack<string> s;
for(int i = 0; i < length; i ++) {
temp = "";
while(i < length && path[i] != '/')
temp += path[i++];
if(temp == "." || temp == "")
continue;
else if(temp == "..") {
if(!s.empty())
s.pop();
else
continue;
}
else {
s.push(temp);
}
}
string ans = "";
while(!s.empty()) {
ans = "/" + s.top() + ans;
s.pop();
}
if(ans.length() == 0) {
ans = "/";
}
return ans;
}
};

Leetcode72. Edit Distance

Given two words word1 and word2, find the minimum number of operations required to convert word1 to word2.

You have the following 3 operations permitted on a word:

Insert a character
Delete a character
Replace a character
Example 1:

1
2
3
4
5
6
Input: word1 = "horse", word2 = "ros"
Output: 3
Explanation:
horse -> rorse (replace 'h' with 'r')
rorse -> rose (remove 'r')
rose -> ros (remove 'e')

Example 2:

1
2
3
4
5
6
7
8
Input: word1 = "intention", word2 = "execution"
Output: 5
Explanation:
intention -> inention (remove 't')
inention -> enention (replace 'i' with 'e')
enention -> exention (replace 'n' with 'x')
exention -> exection (replace 'n' with 'c')
exection -> execution (insert 'u')

一道好久不做的dp题,过段时间闲下来复习下dp

给你word1、word2两个字符串,问最少需要几步才能把word1变成word2,下面每种操作都是一步:a)添加一个字符;b)添加一个字符;c)把一个字符用另一个字符代替。

解题思路:

动态规划。用dp[i][j]表示把word1的前i个字符变成word2的前j个字符所需的步数,word1的前i个字符变成word2的前j个字符可以由三种方法得到:

  1. word1先删去最后一个字符,然后把word1的前i-1个字符变成word2的前j个字符;
  2. word1的前i个字符先变成word2的前j-1个字符;然后word2最后添上一个字符;
  3. word1的前i-1个字符先变成word2的前j-1个字符;然后word1的最后一个字符和word2的最后一个字符匹配上。

因此有状态转移方程:
当word1[i-1]==word2[j-1]时dp[i][j]=dp[i-1][j-1],
否则dp[i][j]=min(dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+1)。

另外边界需要初始化:dp[0][0]=0,dp[i][0]=i对于i从1到len1,dp[0][i]=i对于i从1到len2,。
最终答案为dp[len1][len2],算法复杂度为O(len1*len2)。

算法正确性:

算法的关键点在于是否可以用动态规划的思想把问题拆分成一个个子问题。可以这么考虑:当你能确定用了若干步把word1的前i个字母变成word2的前j个字母后,接下来就可以处理相邻的状态。
对于删除字母,可以把word1的第i+1个字母先删掉,然后再执行那若干步,这样可以得到把word1的前i+1个字母变成word2的前j个字母的步数;
对于添加字母,可以先执行那若干步,再加上word2的第j+1个字母,这样可以得到把word1的前i个字母变成word2的前j+1个字母的步数;
对于代替字母,可以先执行那若干步,再把word1的第i+1个字母和word2的第j+1个字母匹配上,这样可以得到把word1的前i+1个字母变成word2的前j+1个字母的步数。因此上述算法是正确的。

下面举一个简单例子走一遍算法帮助理解:word1=”ad”,word2=”abc”。
初始化:

  • dp[0][0]=0,dp[1][0]=1,dp[2][0]=2,dp[0][1]=1,dp[0][2]=2,dp[0][3]=3;
  • i=1,j=1,word1[0]==word2[0],dp[1][1]=min(dp[0][1]+1,dp[1][0]+1,dp[0][0])=0;
  • i=1,j=2,word1[0]!=word2[1],dp[1][2]=min(dp[0][2]+1,dp[1][1]+1,dp[0][1]+1)=2;
  • i=1,j=3,word1[0]!=word2[2],dp[1][3]=min(dp[0][3]+1,dp[1][2]+1,dp[0][2]+1)=3;
  • i=2,j=1,word1[1]!=word2[0],dp[2][1]=min(dp[1][1]+1,dp[2][0]+1,dp[1][0]+1)=1;
  • i=2,j=2,word1[1]!=word2[1],dp[2][2]=min(dp[1][2]+1,dp[2][1]+1,dp[1][1]+1)=1;
  • i=2,j=3,word1[1]!=word2[2],dp[2][3]=min(dp[1][3]+1,dp[2][2]+1,dp[1][2]+1)=2;

最终结果为dp[2][3]=3。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int minDistance(string word1, string word2) {
int len1=word1.size(),len2=word2.size();
int dp[len1+1][len2+1];
for(int i=0;i<len1+1;i++)
dp[i][0]=i;
for(int i=0;i<len2+1;i++)
dp[0][i]=i;
for(int i=1;i<len1+1;i++)
for(int j=1;j<len2+1;j++){
if(word1[i-1]==word2[j-1])
dp[i][j]=dp[i-1][j-1];//两个字符相等
else
dp[i][j]=min(dp[i-1][j]+1,min(dp[i][j-1]+1,dp[i-1][j-1]+1));//两个字符不相等
}
return dp[len1][len2];
}
};

Leetcode73. Set Matrix Zeroes

Given a m x n matrix, if an element is 0, set its entire row and column to 0. Do it in-place.

Example 1:

1
2
3
4
5
6
7
8
9
10
11
12
Input: 
[
[1,1,1],
[1,0,1],
[1,1,1]
]
Output:
[
[1,0,1],
[0,0,0],
[1,0,1]
]

Example 2:

1
2
3
4
5
6
7
8
9
10
11
12
Input: 
[
[0,1,2,0],
[3,4,5,2],
[1,3,1,5]
]
Output:
[
[0,0,0,0],
[0,4,5,0],
[0,3,1,0]
]

Follow up:

  • A straight forward solution using O(mn) space is probably a bad idea.
  • A simple improvement uses O(m + n) space, but still not the best solution.
  • Could you devise a constant space solution?

我的慢出屎来的代码,如果一行中有0,那么这一行就都是0,如果一列中有0,那么这一列就都是0:

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
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
vector<int> flags(m, 0);
for(int i = 0; i < m; i ++) {
for(int j = 0; j < n; j ++) {
if(matrix[i][j] == 0) {
flags[i] = 1;
break;
}
}
}
vector<int> flags2(n, 0);
for(int i = 0; i < n; i ++) {
for(int j = 0; j < m; j ++) {
if(matrix[j][i] == 0) {
flags2[i] = 1;
break;
}
}
}
for(int i = 0; i < m; i ++)
if(flags[i] == 1)
for(int j = 0; j < n; j ++)
matrix[i][j] = 0;
for(int i = 0; i < n; i ++)
if(flags2[i] == 1)
for(int j = 0; j < m; j ++)
matrix[j][i] = 0;
}
};

Leetcode74. Search a 2D Matrix

Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties:

  • Integers in each row are sorted from left to right.
  • The first integer of each row is greater than the last integer of the previous row.

Example 1:

1
2
3
4
5
6
7
8
Input:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 3
Output: true

Example 2:

1
2
3
4
5
6
7
8
Input:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 13
Output: false

这道题要求搜索一个二维矩阵,由于给的矩阵是有序的,所以很自然的想到要用二分查找法,可以在第一列上先用一次二分查找法找到目标值所在的行的位置,然后在该行上再用一次二分查找法来找是否存在目标值。如果是查找第一个不小于目标值的数,当 target 在第一列时,会返回 target 所在的行,但若 target 不在的话,有可能会返回下一行,不好统一。所以可以查找第一个大于目标值的数,这样只要回退一个,就一定是 target 所在的行。但需要注意的一点是,如果返回的是0,就不能回退了,以免越界,记得要判断一下。找到了 target 所在的行数,就可以再次使用二分搜索,此时就是总结帖中的第一类了,查找和 target 值相同的数,也是最简单的一类。

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
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
if (matrix.empty() || matrix[0].empty()) return false;
int m = matrix.size(), n = matrix[0].size();
int left = 0, right = m;
while(left < right) {
int mid = left + (right - left) / 2;
if(matrix[mid][0] == target)
return true;
else if(matrix[mid][0] >= target)
right = mid;
else
left = mid + 1;
}
int right1 = right > 0 ? right - 1 : right;
left = 0, right = n;
while(left < right) {
int mid = left + (right - left) / 2;
if(matrix[right1][mid] == target)
return true;
else if(matrix[right1][mid] >= target)
right = mid;
else
left = mid + 1;
}
return false;
}
};

Leetcode75. Sort Colors

Given an array with n objects colored red, white or blue, sort them in-place so that objects of the same color are adjacent, with the colors in the order red, white and blue.

Here, we will use the integers 0, 1, and 2 to represent the color red, white, and blue respectively.

Note: You are not suppose to use the library sort function for this problem.

Example:

1
2
Input: [2,0,2,1,1,0]
Output: [0,0,1,1,2,2]

Follow up:

  • A rather straight forward solution is a two-pass algorithm using counting sort.
  • First, iterate the array counting number of 0s, 1s, and 2s, then overwrite array with total number of 0s, then 1s and followed by 2s.
  • Could you come up with a one-pass algorithm using only constant space?

这道题的本质还是一道排序的题,题目中给出提示说可以用计数排序,需要遍历数组两遍,那么先来看这种方法,因为数组中只有三个不同的元素,所以实现起来很容易。

  • 首先遍历一遍原数组,分别记录 0,1,2 的个数。
  • 然后更新原数组,按个数分别赋上 0,1,2。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
void sortColors(vector<int>& nums) {
int f[3] = {0, 0, 0};
int n = nums.size();
for(int i = 0; i < n; i ++) {
f[nums[i]] ++;
}
nums.clear();
for(int i = 0; i < 3; i ++) {
for(int j = 0; j < f[i]; j ++)
nums.push_back(i);
}
}
};

题目中还要让只遍历一次数组来求解,那么就需要用双指针来做,分别从原数组的首尾往中心移动。

  • 定义 red 指针指向开头位置,blue 指针指向末尾位置。
  • 从头开始遍历原数组,如果遇到0,则交换该值和 red 指针指向的值,并将 red 指针后移一位。若遇到2,则交换该值和 blue 指针指向的值,并将 blue 指针前移一位。若遇到1,则继续遍历。
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
void sortColors(vector<int>& nums) {
int red = 0, blue = (int)nums.size() - 1;
for (int i = 0; i <= blue; ++i) {
if (nums[i] == 0) {
swap(nums[i], nums[red++]);
} else if (nums[i] == 2) {
swap(nums[i--], nums[blue--]);
}
}
}
};

Leetcode76. Minimum Window Substring

Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).

Example:

Input: S = “ADOBECODEBANC”, T = “ABC”
Output: “BANC”

这道题给了我们一个原字符串S,还有一个目标字符串T,让在S中找到一个最短的子串,使得其包含了T中的所有的字母,并且限制了时间复杂度为 O(n)。这道题的要求是要在 O(n) 的时间度里实现找到这个最小窗口字串,暴力搜索 Brute Force 肯定是不能用的,因为遍历所有的子串的时间复杂度是平方级的。那么来想一下,时间复杂度卡的这么严,说明必须在一次遍历中完成任务,当然遍历若干次也是 O(n),但不一定有这个必要,尝试就一次遍历拿下!那么再来想,既然要包含T中所有的字母,那么对于T中的每个字母,肯定要快速查找是否在子串中,既然总时间都卡在了 O(n),肯定不想在这里还浪费时间,就用空间换时间(也就算法题中可以这么干了,七老八十的富翁就算用大别野也换不来时间啊。依依东望,望的就是时间呐 T.T),使用 HashMap,建立T中每个字母与其出现次数之间的映射,那么你可能会有疑问,为啥不用 HashSet 呢,别急,讲到后面你就知道用 HashMap 有多妙,简直妙不可言~

目前在脑子一片浆糊的情况下,我们还是从简单的例子来分析吧,题目例子中的S有点长,换个短的 S = “ADBANC”,T = “ABC”,那么肉眼遍历一遍S呗,首先第一个是A,嗯很好,T中有,第二个是D,T中没有,不理它,第三个是B,嗯很好,T中有,第四个又是A,多了一个,礼多人不怪嘛,收下啦,第五个是N,一边凉快去,第六个终于是C了,那么貌似好像需要整个S串,其实不然,注意之前有多一个A,就算去掉第一个A,也没事,因为第四个A可以代替之,第二个D也可以去掉,因为不在T串中,第三个B就不能再去掉了,不然就没有B了。所以最终的答案就”BANC”了。通过上面的描述,你有没有发现一个有趣的现象,先扩展,再收缩,就好像一个窗口一样,先扩大右边界,然后再收缩左边界,上面的例子中右边界无法扩大了后才开始收缩左边界,实际上对于复杂的例子,有可能是扩大右边界,然后缩小一下左边界,然后再扩大右边界等等。这就很像一个不停滑动的窗口了,这就是大名鼎鼎的滑动窗口 Sliding Window 了,简直是神器啊,能解很多子串,子数组,子序列等等的问题,是必须要熟练掌握的啊!

下面来考虑用代码来实现,先来回答一下前面埋下的伏笔,为啥要用 HashMap,而不是 HashSet,现在应该很显而易见了吧,因为要统计T串中字母的个数,而不是仅仅看某个字母是否在T串中出现。统计好T串中字母的个数了之后,开始遍历S串,对于S中的每个遍历到的字母,都在 HashMap 中的映射值减1,如果减1后的映射值仍大于等于0,说明当前遍历到的字母是T串中的字母,使用一个计数器 cnt,使其自增1。当 cnt 和T串字母个数相等时,说明此时的窗口已经包含了T串中的所有字母,此时更新一个 minLen 和结果 res,这里的 minLen 是一个全局变量,用来记录出现过的包含T串所有字母的最短的子串的长度,结果 res 就是这个最短的子串。然后开始收缩左边界,由于遍历的时候,对映射值减了1,所以此时去除字母的时候,就要把减去的1加回来,此时如果加1后的值大于0了,说明此时少了一个T中的字母,那么 cnt 值就要减1了,然后移动左边界 left。你可能会疑问,对于不在T串中的字母的映射值也这么加呀减呀的,真的大丈夫(带胶布)吗?其实没啥事,因为对于不在T串中的字母,减1后,变-1,cnt 不会增加,之后收缩左边界的时候,映射值加1后为0,cnt 也不会减少,所以并没有什么影响啦,下面是具体的步骤啦:

  • 先扫描一遍T,把对应的字符及其出现的次数存到 HashMap 中。

  • 然后开始遍历S,就把遍历到的字母对应的 HashMap 中的 value 减一,如果减1后仍大于等于0,cnt 自增1。

  • 如果 cnt 等于T串长度时,开始循环,纪录一个字串并更新最小字串值。然后将子窗口的左边界向右移,如果某个移除掉的字母是T串中不可缺少的字母,那么 cnt 自减1,表示此时T串并没有完全匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
string minWindow(string s, string t) {
int left = 0, right = 0, minLen = INT_MAX;
string res = "";
unordered_map<char, int> letter;
for(char c : t)
letter[c] ++;
for(int i = 0; i < s.size(); i ++) {
if(--letter[s[i]] >= 0)
right ++;
while(right == t.size()) {
if(minLen > i + 1 - left) {
minLen = i + 1 - left;
res = s.substr(left, minLen);
}
if(++letter[s[left]] > 0)
right --;
left ++;
}
}
return res;
}
};

Leetcode77. Combinations

Given two integers n and k, return all possible combinations of k numbers out of 1 … n.

Example:

1
2
3
4
5
6
7
8
9
10
Input: n = 4, k = 2
Output:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

简单的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
class Solution {
public:
vector<vector<int>> res;

void dfs(int n, int k, int i, vector<int> temp) {
if(temp.size() == k) {
res.push_back(temp);
return;
}
printf("af");

for(int ii = i + 1; ii <= n; ii ++) {
int t = temp.size();
temp.push_back(ii);
dfs(n, k, ii, temp);
temp.erase(temp.begin()+t);
}
}
vector<vector<int>> combine(int n, int k) {
vector<int> temp(0);
dfs(n, k, 0, temp);
return res;
}
};

一样的做法,人家为啥就速度快。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
vector<vector<int>> combine(int n, int k) {
vector<vector<int>> result;
vector<int> buffer(k);
combineUtil(result,1,buffer,0,n);
return result;
}
void combineUtil(vector<vector<int>>& result,int startIndex,vector<int>& buffer,int bufferIndex,int n){

if(bufferIndex==buffer.size()){
result.push_back(buffer);
return;
}

for(int i=startIndex;i<=n;i++){
buffer[bufferIndex] = i;
combineUtil(result,i+1,buffer,bufferIndex+1,n);
}
return;
}
};

Leetcode78. Subsets

Given a set of distinct integers, nums, return all possible subsets (the power set).

Note: The solution set must not contain duplicate subsets.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
Input: nums = [1,2,3]
Output:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]

可以一位一位的叠加,比如对于题目中给的例子 [1,2,3] 来说,最开始是空集,那么我们现在要处理1,就在空集上加1,为 [1],现在我们有两个自己 [] 和 [1],下面我们来处理2,我们在之前的子集基础上,每个都加个2,可以分别得到 [2],[1, 2],那么现在所有的子集合为 [], [1], [2], [1, 2],同理处理3的情况可得 [3], [1, 3], [2, 3], [1, 2, 3], 再加上之前的子集就是所有的子集合了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int> > res;
res.push_back({});
for(int i = 0; i < nums.size(); i ++) {
int size = res.size();
for(int j = 0; j < size; j ++) {
vector<int> temp = res[j];
temp.push_back(nums[i]);
res.push_back(temp);
}
}
return res;
}
};

Leetcode79. Word Search

Given a 2D board and a word, find if the word exists in the grid.

The word can be constructed from letters of sequentially adjacent cell, where “adjacent” cells are those horizontally or vertically neighboring. The same letter cell may not be used more than once.

Example:

1
2
3
4
5
6
7
8
9
10
board =
[
['A','B','C','E'],
['S','F','C','S'],
['A','D','E','E']
]

Given word = "ABCCED", return true.
Given word = "SEE", return true.
Given word = "ABCB", return false.

Constraints:

  • board and word consists only of lowercase and uppercase English letters.
  • 1 <= board.length <= 200
  • 1 <= board[i].length <= 200
  • 1 <= word.length <= 10^3

典型的深度优先遍历 DFS 的应用,原二维数组就像是一个迷宫,可以上下左右四个方向行走,我们以二维数组中每一个数都作为起点和给定字符串做匹配,我们还需要一个和原数组等大小的 visited 数组,是 bool 型的,用来记录当前位置是否已经被访问过,因为题目要求一个 cell 只能被访问一次。如果二维数组 board 的当前字符和目标字符串 word 对应的字符相等,则对其上下左右四个邻字符分别调用 DFS 的递归函数,只要有一个返回 true,那么就表示可以找到对应的字符串,否则就不能找到。

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
class Solution {
public:
int m, n;
bool search(vector<vector<char>>& board, int i, int j, int k, string word, vector<vector<bool>>& visited) {
if(word.length() == k) {
return true;
}
int m = board.size(), n = board[0].size();
if (i < 0 || j < 0 || i >= m || j >= n || visited[i][j] || board[i][j] != word[k])
return false;
visited[i][j] = true;
bool res = search(board, i - 1, j, k + 1, word, visited)
|| search(board, i + 1, j, k + 1, word, visited)
|| search(board, i, j - 1, k + 1, word, visited)
|| search(board, i, j + 1, k + 1, word, visited);
visited[i][j] = false;
return res;
}

bool exist(vector<vector<char>>& board, string word) {
m = board.size(), n = board[0].size();
vector<vector<bool>> visited(m, vector<bool>(n));
for(int i = 0; i < m; i ++)
for(int j = 0; j < n; j ++)
visited[i][j] = false;
for(int i = 0; i < m; i ++)
for(int j = 0; j < n; j ++)
if(search(board, i, j, 0, word, visited))
return true;
return false;
}
};

Leetcode80. Remove Duplicates from Sorted Array II

Given a sorted array nums, remove the duplicates in-place such that duplicates appeared at most twice and return the new length.

Do not allocate extra space for another array, you must do this by modifying the input array in-place with O(1) extra memory.

Example 1: Given nums = [1,1,1,2,2,3], Your function should return length = 5, with the first five elements of nums being 1, 1, 2, 2 and 3 respectively. It doesn not matter what you leave beyond the returned length.

Example 2: Given nums = [0,0,1,1,1,1,2,3,3], Your function should return length = 7, with the first seven elements of nums being modified to 0, 0, 1, 1, 2, 3 and 3 respectively.

Clarification: Confused why the returned value is an integer but your answer is an array? Note that the input array is passed in by reference, which means modification to the input array will be known to the caller as well. Internally you can think of this:

1
2
3
4
5
6
7
8
// nums is passed in by reference. (i.e., without making a copy)
int len = removeDuplicates(nums);

// any modification to nums in your function would be known by the caller.
// using the length returned by your function, it prints the first len elements.
for (int i = 0; i < len; i++) {
print(nums[i]);
}

遍历一遍,记下来每次遍历开始的数,如果这个数的数量大于2了,一直i++。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if(nums.size() <= 2)
return nums.size();
int prev_i = 0, prev;
int count = 0;
for(int i = 0; i < nums.size();) {
count = 0;
nums[prev_i] = nums[i];
prev = nums[i];
while(count < 2 && i < nums.size() && prev == nums[i])
nums[prev_i++] = nums[i], count ++, i++;
while(i < nums.size() && prev == nums[i])
i ++;
}
return prev_i;
}
};

另一种方法是交换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if (nums.size() < 3) {
return nums.size();
}
int arrow = 2;
for (int i = 2; i < nums.size(); i++) {
if (nums[arrow-2] != nums[i]) {
swap(nums[arrow], nums[i]);
arrow++;
}
}
return arrow;
}
};

Leetcode81. Search in Rotated Sorted Array II

Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand.

(i.e., [0,0,1,2,2,5,6] might become [2,5,6,0,0,1,2]).

You are given a target value to search. If found in the array return true, otherwise return false.

Example 1:

1
2
Input: nums = [2,5,6,0,0,1,2], target = 0
Output: true

Example 2:

1
2
Input: nums = [2,5,6,0,0,1,2], target = 3
Output: false

本题采用二分法实现,但是比较挠头的是边界问题,而且元素有重复,相比纯粹递增的数组难度要大得多,要解决这个问题,首先要对所有可能情况进行分类,然后对每种可能的类别进行相应的处理。

暂且不考虑nums[mid] = nums[left]的情况,本题大致可以简化为两种情况,可能的情况划分出来,那么解决本题就比较容易了:

  • 当 nums[mid] = nums[left] 时,这时由于很难判断 target 会落在哪,那么只能采取 left++
  • 当 nums[mid] > nums[left] 时,这时可以分为两种情况,判断左半部比较简单(如果target不在左边这部分,那么我们是可以直接去掉左边这部分的)
  • 当 nums[mid] < nums[left] 时,这时可以分为两种情况,判断右半部比较简单(如果target不在右边这部分,那么我们也是可以直接去掉右边这部分的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
bool search(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while(left <= right) {
int mid = (left + right) / 2;
if(nums[mid] == target)
return true;
if(nums[mid] == nums[left])
left ++;
else if(nums[mid] > nums[left])
if(nums[left] <= target && target < nums[mid])
right = mid - 1;
else
left = mid + 1;
else
if(nums[mid] < target && target <= nums[right])
left = mid + 1;
else
right = mid - 1;
}
return false;
}
};

Leetcode82. Remove Duplicates from Sorted List II

Given a sorted linked list, delete all nodes that have duplicate numbers, leaving only distinct numbers from the original list.

Return the linked list sorted as well.

Example 1:

1
2
Input: 1->2->3->3->4->4->5
Output: 1->2->5

Example 2:

1
2
Input: 1->1->1->2->3
Output: 2->3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(head == NULL || head->next == NULL)
return head;
ListNode* res = new ListNode(-1);
res->next = head;
ListNode* temp = res, *tempn;
while(temp != NULL && temp->next != NULL) {
ListNode* tempn = temp->next;
if(tempn->next && tempn->next->val == tempn->val) {
ListNode* tempp;
while(tempn->next && tempn->next->val == tempn->val) {
tempp = tempn->next;
tempn->next = tempp->next;
}
temp->next = tempn->next;
}
else
temp = temp->next;
}
return res->next;
}
};

Leetcode83. Remove Duplicates from Sorted List

Given a sorted linked list, delete all duplicates such that each element appear only once.

Example 1:

1
2
Input: 1->1->2
Output: 1->2

Example 2:

1
2
Input: 1->1->2->3->3
Output: 1->2->3

删掉链表中重复多余的数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode* temp = head;
ListNode* res = new ListNode(-1);
ListNode* cur = res;
if(temp != NULL && temp->next == NULL)
return head;
while(temp != NULL && temp->next != NULL) {
while(temp->next != NULL && temp->val == temp->next->val)
temp = temp->next;
cur->next = temp;
cur = cur->next;
temp = temp->next;
}
return res->next;
}
};

Leetcode84. Largest Rectangle in Histogram

Given n non-negative integers representing the histogram’s bar height where the width of each bar is 1, find the area of largest rectangle in the histogram.

Above is a histogram where width of each bar is 1, given height = [2,1,5,6,2,3].

The largest rectangle is shown in the shaded area, which has area = 10 unit.

Example:

1
2
Input: [2,1,5,6,2,3]
Output: 10

这里维护一个栈,用来保存递增序列,相当于上面那种方法的找局部峰值。我们可以看到,直方图矩形面积要最大的话,需要尽可能的使得连续的矩形多,并且最低一块的高度要高。有点像木桶原理一样,总是最低的那块板子决定桶的装水量。那么既然需要用单调栈来做,首先要考虑到底用递增栈,还是用递减栈来做。我们想啊,递增栈是维护递增的顺序,当遇到小于栈顶元素的数就开始处理,而递减栈正好相反,维护递减的顺序,当遇到大于栈顶元素的数开始处理。那么根据这道题的特点,我们需要按从高板子到低板子的顺序处理,先处理最高的板子,宽度为1,然后再处理旁边矮一些的板子,此时长度为2,因为之前的高板子可组成矮板子的矩形 ,因此我们需要一个递增栈,当遇到大的数字直接进栈,而当遇到小于栈顶元素的数字时,就要取出栈顶元素进行处理了,那取出的顺序就是从高板子到矮板子了,于是乎遇到的较小的数字只是一个触发,表示现在需要开始计算矩形面积了,为了使得最后一块板子也被处理,这里用了个小 trick,在高度数组最后面加上一个0,这样原先的最后一个板子也可以被处理了。由于栈顶元素是矩形的高度,那么关键就是求出来宽度,那么跟之前那道 Trapping Rain Water 一样,单调栈中不能放高度,而是需要放坐标。由于我们先取出栈中最高的板子,那么就可以先算出长度为1的矩形面积了,然后再取下一个板子,此时根据矮板子的高度算长度为2的矩形面积,以此类推,知道数字大于栈顶元素为止,再次进栈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
stack<int> s;
int res = 0;
heights.push_back(0);
for(int i = 0; i < heights.size(); i ++) {
if(s.empty() || heights[s.top()] < heights[i])
s.push(i);
else {
int cur = s.top();
s.pop();
res = max(res, heights[cur]*(s.empty() ? i : (i - s.top() - 1)));
i --;
}
}
return res;
}
};

遍历数组,每找到一个局部峰值(只要当前的数字大于后面的一个数字,那么当前数字就看作一个局部峰值,跟前面的数字大小无关),然后向前遍历所有的值,算出共同的矩形面积,每次对比保留最大值。这里再说下为啥要从局部峰值处理,看题目中的例子,局部峰值为 2,6,3,我们只需在这些局部峰值出进行处理,为啥不用在非局部峰值处统计呢,这是因为非局部峰值处的情况,后面的局部峰值都可以包括,比如1和5,由于局部峰值6是高于1和5的,所有1和5能组成的矩形,到6这里都能组成,并且还可以加上6本身的一部分组成更大的矩形,那么就不用费力气去再统计一个1和5处能组成的矩形了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int res = 0;
for(int i = 0; i < heights.size(); i ++) {
if(i + 1 < heights.size() && heights[i] <= heights[i+1])
continue;
int minn = heights[i];
for(int k = i; k >= 0; k --) {
minn = min(heights[k], minn);
int area = minn * (i - k + 1);
res = max(res, area);
}
}
return res;
}
};

这种做法也是单调栈,不过是两遍单调栈,首先从左到右直到让这个柱子左边高的都弹出去,直到有比这个柱子矮的,那么此时这个矮柱子就是当前柱子能够生成最大矩形的限制条件。右边也是。

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
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int res = -1, len = heights.size();
stack<int> st;
vector<int> ans(len, 1);
for (int i = 0; i < len; i ++) {
while (!st.empty() && heights[st.top()] >= heights[i])
st.pop();
if (st.empty()) // 比我大的都弹出去了,所以我是最矮的一个
ans[i] += i;
else
ans[i] += (i - 1 - st.top());
st.push(i);
}

while(!st.empty()) st.pop();

for (int i = len-1; i >= 0; i --) {
while(!st.empty() && heights[st.top()] >= heights[i])
st.pop();
if (st.empty())// 比我大的都弹出去了,所以我是最矮的一个
ans[i] += (len - 1 - i);
else
ans[i] += (st.top() - 1 - i);
// 现在我这个柱子形成了一个山峰,当前区间我是最高的
st.push(i);
res = max(res, ans[i]*heights[i]);
}
return res;
}
};

Leetcode85. Maximal Rectangle

Given a rows x cols binary matrix filled with 0’s and 1’s, find the largest rectangle containing only 1’s and return its area.

Example:

1
2
3
4
5
6
7
8
Input:
[
["1","0","1","0","0"],
["1","0","1","1","1"],
["1","1","1","1","1"],
["1","0","0","1","0"]
]
Output: 6

这里我们统计每一行的连续1的个数,使用一个数组 h_max, 其中 h_max[i][j] 表示第i行,第j个位置水平方向连续1的个数,若 matrix[i][j] 为0,那对应的 h_max[i][j] 也一定为0。统计的过程跟建立累加和数组很类似,唯一不同的是遇到0了要将 h_max 置0。这个统计好了之后,只需要再次遍历每个位置,首先每个位置的 h_max 值都先用来更新结果 res,因为高度为1也可以看作是矩形,然后我们向上方遍历,上方 (i, j-1) 位置也会有 h_max 值,但是用二者之间的较小值才能构成矩形,用新的矩形面积来更新结果 res,这样一直向上遍历,直到遇到0,或者是越界的时候停止,这样就可以找出所有的矩形了

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
class Solution {
public:
int maximalRectangle(vector<vector<char>>& matrix) {
if (matrix.size() == 0)
return 0;
int res = 0;
int width = matrix.size();
int height = matrix[0].size();
vector<vector<int>> dp(width, vector<int>(height, 0));

for (int i = 0; i < width; i ++) {
for (int j = 0; j < height; j ++) {
if (matrix[i][j] == '1') {
dp[i][j] = (j == 0 ? 1 : dp[i][j-1]+1);
int length = dp[i][j];
for (int k = i; k >= 0; k --) {
length = min(length, dp[k][j]);
res = max(res, length*(i-k+1));
}
}
}
}
return res;

}
};

看一下这种方法,当成直方图来处理:

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
class Solution {
public:
int maximalRectangle(vector<vector<char> > &matrix) {
int res = 0;
vector<int> height;
for (int i = 0; i < matrix.size(); ++i) {
height.resize(matrix[i].size());
for (int j = 0; j < matrix[i].size(); ++j) {
height[j] = matrix[i][j] == '0' ? 0 : (1 + height[j]);
}
res = max(res, largestRectangleArea(height));
}
return res;
}
int largestRectangleArea(vector<int>& height) {
int res = 0;
stack<int> s;
height.push_back(0);
for (int i = 0; i < height.size(); ++i) {
if (s.empty() || height[s.top()] <= height[i]) s.push(i);
else {
int tmp = s.top(); s.pop();
res = max(res, height[tmp] * (s.empty() ? i : (i - s.top() - 1)));
--i;
}
}
return res;
}
};

考虑这样一个算法,对于每个点,我们先不断向上直到遇到零,然后向两边扩展,直到某列出现0。这种方式构建的矩形中必然存在最大矩形。

我们通过定义三个数组height,left,right来记录每个点的高度,左边界和右边界,问题转化为如何更新每个数组。

  • 对于height数组,new_height[j]=old_height[j]+1 if row[j]==‘1’。
  • 对于left数组,new_left[j]=max(old_left[j],cur_left),其中cur_left是遇到的最右边的0的序号加1,向左扩展矩形时不能超过点,否则会遇到0。
  • 对于right数组,new_right[j]=min(old_right[j],cur_right),cur_right表示我们遇到的最左边的0的序号。这里不减去1是因为这样就可以用height[j]*(right[j]-left[j])来计算矩形面积,也就是说矩形的底边由半开半闭区间[l,r)决定。为了记录正确的cur_right需要从右向左迭代,因此更新right时需要从右向左。C++代码如下:
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
//解法3:动态规划2:时间复杂度O(NM)[],空间复杂度O(N)
int maximalRectangle(vector<vector<char>>& matrix) {
if (matrix.size() == 0) return 0;
int m = matrix.size();
int n = matrix[0].size();
//分别存储每一行上元素对应的最大矩形的高度以及左右区间
vector<int> left(n, 0);//最大的左边界为左端
vector<int> right(n, n);//最大的有边界为右端
vector<int> height(n, 0);

int maxarea = 0;
for (int i = 0; i < m; i++) {
int cur_left = 0, cur_right = n;
//更新高度,最后存储在height数组的就是每一列‘1’的个数
for (int j = 0; j < n; j++) {
if (matrix[i][j] == '1')
height[j]++;
else
height[j] = 0;
}
//更新左边界,
for (int j = 0; j < n; j++) {
if (matrix[i][j] == '1')
left[j] = max(left[j], cur_left);
else {
left[j] = 0;
cur_left = j + 1;
}
}
//更新右边界
for (int j = n - 1; j >= 0; j--) {
if (matrix[i][j] == '1')
right[j] = min(right[j], cur_right);
else {
right[j] = n;
cur_right = j;
}
}
for (int j = 0; j < n; j++) {
maxarea = max(maxarea, (right[j] - left[j]) * height[j]);
}
}
return maxarea;
}

Leetcode86. Partition List

Given a linked list and a value x, partition it such that all nodes less than x come before nodes greater than or equal to x. You should preserve the original relative order of the nodes in each of the two partitions.

Example:

1
2
Input: head = 1->4->3->2->5->2, x = 3
Output: 1->2->2->4->3->5

把链表分成两部分,一部分都大于k,另一部分都小于k。

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
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
if(head==nullptr)
return head;

ListNode *prehead = new ListNode(-1, head);
ListNode *l = head, *lprev = prehead;
while(l && l->val < x) {
lprev = l;
l = l->next;
}

if(l == NULL)
return head;

ListNode *r = l->next, *rprev = l;
while(r) {
if(r && r->val >= x) {
rprev = r;
r = r->next;
}
else {
rprev->next = r->next;
r->next = l;
lprev->next = r;
lprev = r;
r = rprev->next;
}
}
return prehead->next;
}
};

Leetcode88. Merge Sorted Array

Given two sorted integer arrays nums1 and nums2, merge nums2 into nums1 as one sorted array.

Note:

The number of elements initialized in nums1 and nums2 are m and n respectively.
You may assume that nums1 has enough space (size that is greater or equal to m + n) to hold additional elements from nums2.
Example:

Input:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3

Output: [1,2,2,3,5,6]

给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。很简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
if(n==0)
return;
if(m==0) {
for(int i=0;i<n;i++)
nums1[i] = nums2[i];
return ;
}
int pointer = 0;
for(int i = 0; i < n; i ++) {
for(; pointer < m; pointer ++)
if(nums2[i] < nums1[pointer])
break;
for(int ii = m; ii > pointer; ii --)
nums1[ii] = nums1[ii - 1];
m++;
nums1[pointer] = nums2[i];
}
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int i = m - 1;
int j = n - 1;
int k = m + n - 1;
while(i >= 0 && j >= 0) {
if(nums1[i] >= nums2[j])
nums1[k--] = nums1[i--];
else
nums1[k--] = nums2[j--];
}
while(j >= 0)
nums1[k--] = nums2[j--];
}
};

Leetcode89. Gray Code

The gray code is a binary numeral system where two successive values differ in only one bit.

Given a non-negative integer n representing the total number of bits in the code, print the sequence of gray code. A gray code sequence must begin with 0.

Example 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Input: 2
Output: [0,1,3,2]
Explanation:
00 - 0
01 - 1
11 - 3
10 - 2

For a given n, a gray code sequence may not be uniquely defined.
For example, [0,2,3,1] is also a valid gray code sequence.

00 - 0
10 - 2
11 - 3
01 - 1

Example 2:

1
2
3
4
5
Input: 0
Output: [0]
Explanation: We define the gray code sequence to begin with 0.
A gray code sequence of n has size = 2n, which for n = 0 the size is 20 = 1.
Therefore, for n = 0 the gray code sequence is [0].


蓝色部分由于最高位加的是 0 ,所以它的数值和 n = 2 的所有解的情况一样。而橙色部分由于最高位加了 1,所以值的话,就是在其对应的值上加 4,也就是 [公式] ,即 [公式] ,也就是 1 << ( n - 1) 。所以我们的算法可以用迭代求出来了。

所以如果知道了 n = 2 的解的话,如果是 { 0, 1, 3, 2},那么 n = 3 的解就是 { 0, 1, 3, 2, 2 + 4, 3 + 4, 1 + 4, 0 + 4 },即 { 0 1 3 2 6 7 5 4 }。之前的解直接照搬过来,然后倒序把每个数加上 1 << ( n - 1) 添加到结果中即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
vector<int> grayCode(int n) {
vector<int> res;
res.push_back(0);
if(n == 0) {
return res;
}
for(int i = 0; i < n; i ++) {
int add = 1 << i;
for(int j = res.size()-1; j >= 0; j --) {
res.push_back(res[j] + add);
}
}
return res;
}
};

Leetcode90. Subsets II

Given a collection of integers that might contain duplicates, nums, return all possible subsets (the power set).

Note: The solution set must not contain duplicate subsets.

Example:

1
2
3
4
5
6
7
8
9
10
Input: [1,2,2]
Output:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]

由于数组事先已经过排序,因此不需要再用额外的unordered_set去判断重复元素,直接判断nums[i]和nums[i - 1]是否相等就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:

void search(int start, vector<int>& sub, vector<vector<int>>& res, vector<int>& nums) {
res.push_back(sub);
for(int i = start; i < nums.size(); i ++) {
if(i == start || nums[i] != nums[i - 1]){
sub.push_back(nums[i]);
search(i + 1, sub, res, nums);
sub.pop_back();
}
}
}

vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<vector<int>> res;
vector<int> sub;
sort(nums.begin(), nums.end());
search(0, sub, res, nums);
return res;
}
};

Leetcode91. Decode Ways

A message containing letters from A-Z is being encoded to numbers using the following mapping:

1
2
3
4
'A' -> 1
'B' -> 2
...
'Z' -> 26

Given a non-empty string containing only digits, determine the total number of ways to decode it.

Example 1:

1
2
3
Input: "12"
Output: 2
Explanation: It could be decoded as "AB" (1 2) or "L" (12).

Example 2:

1
2
3
Input: "226"
Output: 3
Explanation: It could be decoded as "BZ" (2 26), "VF" (22 6), or "BBF" (2 2 6).

这道题要求解码方法,跟之前那道 Climbing Stairs 非常的相似,但是还有一些其他的限制条件,比如说一位数时不能为0,两位数不能大于 26,其十位上的数也不能为0,除去这些限制条件,跟爬梯子基本没啥区别,也勉强算特殊的斐波那契数列,当然需要用动态规划 Dynamci Programming 来解。建立一维 dp 数组,其中 dp[i] 表示s中前i个字符组成的子串的解码方法的个数,长度比输入数组长多多1,并将 dp[0] 初始化为1。现在来找状态转移方程,dp[i] 的值跟之前的状态有着千丝万缕的联系,就拿题目中的例子2来分析吧,当 i=1 时,对应s中的字符是 s[0]=’2’,只有一种拆分方法,就是2,注意 s[0] 一定不能为0,这样的话无法拆分。当 i=2 时,对应s中的字符是 s[1]=’2’,由于 s[1] 不为0,那么其可以被单独拆分出来,就可以在之前 dp[i-1] 的每种情况下都加上一个单独的2,这样 dp[i] 至少可以有跟 dp[i-1] 一样多的拆分情况,接下来还要看其能否跟前一个数字拼起来,若拼起来的两位数小于等于26,并且大于等于 10(因为两位数的高位不能是0),那么就可以在之前 dp[i-2] 的每种情况下都加上这个二位数,所以最终 dp[i] = dp[i-1] + dp[i-2],是不是发现跟斐波那契数列的性质吻合了。所以0是个很特殊的存在,若当前位置是0,则一定无法单独拆分出来,即不能加上 dp[i-1],就只能看否跟前一个数字组成大于等于 10 且小于等于 26 的数,能的话可以加上 dp[i-2],否则就只能保持为0了。具体的操作步骤是,在遍历的过程中,对每个数字首先判断其是否为0,若是则将 dp[i] 赋为0,若不是,赋上 dp[i-1] 的值,然后看数组前一位是否存在,如果存在且满足前一位是1,或者和当前位一起组成的两位数不大于 26,则当前 dp[i] 值加上 dp[i - 2]。最终返回 dp 数组的最后一个值即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int numDecodings(string s) {
if (s.empty() || s[0] == '0')
return 0;
int len = s.length();
vector<int> dp(len+1, 0);
dp[0] = 1;
for(int i = 1; i < len+1; i ++){
dp[i] = s[i-1] == '0' ? 0 : dp[i-1];
if(i > 1 && (s[i-2] == '1' || (s[i-2] == '2' && s[i-1] <= '6')))
dp[i] += dp[i-2];
}
return dp[len];
}
};

Leetcode92. Reverse Linked List II

Reverse a linked list from position m to n. Do it in one-pass.

Note: 1 ≤ m ≤ n ≤ length of list.

Example:

1
2
Input: 1->2->3->4->5->NULL, m = 2, n = 4
Output: 1->4->3->2->5->NULL

对于链表的问题,根据以往的经验一般都是要建一个dummy node,连上原链表的头结点,这样的话就算头结点变动了,我们还可以通过dummy->next来获得新链表的头结点。这道题的要求是只通过一次遍历完成,就拿题目中的例子来说,变换的是2,3,4这三个点,我们需要找到第一个开始变换结点的前一个结点,只要让pre向后走m-1步即可,为啥要减1呢,因为题目中是从1开始计数的,这里只走了1步,就是结点1,用pre指向它。万一是结点1开始变换的怎么办,这就是我们为啥要用dummy结点了,pre也可以指向dummy结点。然后就要开始交换了,由于一次只能交换两个结点,所以我们按如下的交换顺序:

1 -> 2 -> 3 -> 4 -> 5 -> NULL

1 -> 3 -> 2 -> 4 -> 5 -> NULL

1 -> 4 -> 3 -> 2 -> 5 -> NULL

我们可以看出来,总共需要n-m步即可,第一步是将结点3放到结点1的后面,第二步将结点4放到结点1的后面。这是很有规律的操作,那么我们就说一个就行了,比如刚开始,pre指向结点1,cur指向结点2,然后我们建立一个临时的结点t,指向结点3(注意我们用临时变量保存某个结点就是为了首先断开该结点和前面结点之间的联系,这可以当作一个规律记下来),然后我们断开结点2和结点3,将结点2的next连到结点4上,也就是 cur->next = t->next,再把结点3连到结点1的后面结点(即结点2)的前面,即 t->next = pre->next,最后再将原来的结点1和结点2的连接断开,将结点1连到结点3,即 pre->next = t。这样我们就完成了将结点3取出,加入结点1的后方。第二步将结点4取出,加入结点1的后方,也是同样的操作,这里就不多说了,请大家自己尝试下吧,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int m, int n) {
int count = 0;
ListNode *res = new ListNode(-1, head);
ListNode *pre = res, *cur = pre;
for(count = 0; count < m; count ++) {
pre = cur;
cur = cur->next;
}
for(count = m; count < n; count ++) {
ListNode *temp = cur->next;
cur->next = temp->next;
temp->next = pre->next;
pre->next = temp;
}
return res->next;
}
};

Leetcode93. Restore IP Addresses

Given a string containing only digits, restore it by returning all possible valid IP address combinations. A valid IP address consists of exactly four integers (each integer is between 0 and 255) separated by single points.

Example:

1
2
Input: "25525511135"
Output: ["255.255.11.135", "255.255.111.35"]

这个题可以运用dfs,那么回溯算法的循环和终止条件是什么呢?IP地址由四部分构成,可以设置一个变量segment,当segment = 4时,可结束循环,将结果添加到列表中;每个部分数值均值0—255之间,因此每次回溯最多需要判断3个元素,即当前元素i—i+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
class Solution {
public:

void dfs(string s, int segment, vector<string>& res, string ip, int len) {
if(segment == 4) {
if(s == "" && ip.length() == len + 4)
res.push_back(ip.substr(0, ip.length()-1));
return;
}
int temp = 0;
string t = "";
for(int i = 0; i < s.length() && i < 3; i ++) {
temp = temp * 10 + (s[i] - '0');
t += s[i];
if(i > 0 && temp == 0)
return;
else if(temp <= 255)
dfs(s.substr(i+1), segment + 1, res, ip + to_string(temp) + ".", len);
}
}

vector<string> restoreIpAddresses(string s) {
vector<string> res;
string temp = "";
dfs(s, 0, res, temp, s.length());
return res;
}
};

Leetcode94. Binary Tree Inorder Traversal

Given a binary tree, return the inorder traversal of its nodes’ values.

Example:

1
2
3
4
5
6
7
Input: [1,null,2,3]
1
\
2
/
3
Output: [1,3,2]

需要用栈来做,思路是从根节点开始,先将根节点压入栈,然后再将其所有左子结点压入栈,然后取出栈顶节点,保存节点值,再将当前指针移到其右子节点上,若存在右子节点,则在下次循环时又可将其所有左子结点压入栈中。这样就保证了访问顺序为左-根-右。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
if(root == NULL)
return {};
vector<int> res;
stack<TreeNode*> q;
TreeNode* temp = root;
while(temp || !q.empty()) {
while(temp) {
q.push(temp);
temp = temp->left;
}
temp = q.top();
q.pop();
res.push_back(temp->val);
temp = temp->right;
}
return res;
}
};

Leetcode95. Unique Binary Search Trees II

Given an integer n, generate all structurally unique BST’s (binary search trees) that store values 1 … n.

Example:

1
2
3
4
5
6
7
8
9
Input: 3
Output:
[
[1,null,3,2],
[3,2,null,1],
[3,1,null,null,2],
[2,1,3],
[1,null,2,null,3]
]

Explanation: The above output corresponds to the 5 unique BST’s shown below:

1
2
3
4
5
1         3     3      2      1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3

这种建树问题一般来说都是用递归来解,这道题也不例外,划分左右子树,递归构造。这个其实是用到了大名鼎鼎的分治法 Divide and Conquer,类似的题目还有之前的那道 Different Ways to Add Parentheses 用的方法一样,用递归来解,划分左右两个子数组,递归构造。刚开始时,将区间 [1, n] 当作一个整体,然后需要将其中的每个数字都当作根结点,其划分开了左右两个子区间,然后分别调用递归函数,会得到两个结点数组,接下来要做的就是从这两个数组中每次各取一个结点,当作当前根结点的左右子结点,然后将根结点加入结果 res 数组中即可。

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
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:

vector<TreeNode*> dfs(int start, int end) {
if(start > end) {
return {NULL};
}
vector<TreeNode*> res;
for(int i = start; i <= end; i ++) {
vector<TreeNode*> a = dfs(start, i-1);
vector<TreeNode*> b = dfs(i+1, end);
for(auto aa : a)
for(auto bb : b) {
TreeNode *temp = new TreeNode(i);
temp->left = aa;
temp->right = bb;
res.push_back(temp);
}
}
return res;
}

vector<TreeNode*> generateTrees(int n) {
if(n == 0)
return {};
return dfs(1, n);
}
};

我们可以使用记忆数组来优化,保存计算过的中间结果,从而避免重复计算。注意这道题的标签有一个是动态规划 Dynamic Programming,其实带记忆数组的递归形式就是 DP 的一种,memo[i][j] 表示在区间 [i, j] 范围内可以生成的所有 BST 的根结点,所以 memo 必须是一个三维数组,这样在递归函数中,就可以去 memo 中查找当前的区间是否已经计算过了,是的话,直接返回 memo 中的数组,否则就按之前的方法去计算,最后计算好了之后要更新 memo 数组。

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
class Solution {
public:

vector<TreeNode*> dfs(int start, int end, vector<vector<vector<TreeNode*>>>& memo) {
if(start > end) {
return {NULL};
}
if(!memo[start - 1][end - 1].empty())
return memo[start - 1][end - 1];
vector<TreeNode*> res;
for(int i = start; i <= end; i ++) {
vector<TreeNode*> a = dfs(start, i-1, memo);
vector<TreeNode*> b = dfs(i+1, end, memo);
for(auto aa : a)
for(auto bb : b) {
TreeNode *temp = new TreeNode(i);
temp->left = aa;
temp->right = bb;
res.push_back(temp);
}
}
return memo[start - 1][end - 1] = res;
}

vector<TreeNode*> generateTrees(int n) {
if(n == 0)
return {};
vector<vector<vector<TreeNode*>>> memo(n, vector<vector<TreeNode*>>(n));
return dfs(1, n, memo);
}
};

Leetcode96. Unique Binary Search Trees

Given n, how many structurally unique BST’s (binary search trees) that store values 1 … n?

Example:

1
2
3
4
5
6
7
8
9
10
Input: 3
Output: 5
Explanation:
Given n = 3, there are a total of 5 unique BST's:

1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3

思路:对于选定结点的元素,左边的元素构成左子树,右边的元素构成右子树,左子树和右子树构成种树的乘积就是总的个数。考虑根节点,设对于任意根节点k,有f(k)种树的可能。比k小的k-1个元素构成k的左子树。则左子树有f(k-1)种情况。比k大的n-k个元素构成k的右子树。则右子树有f(n-k)种情况。

易知,左右子树相互独立,所以f(k)=f(k-1)*f(n-k)。综上,对于n,结果为k取1,2,3,…,n时,所有f(k)的和。

代码思路:根据上述思路可以用简单的递归方法快速解决。现在考虑动态规划求解算法,用数组记录每个f(i)的值,记f(0)=1,f(1)=1。根据公式:f(k)=f(k-1)*f(n-k),访问数组中的元素。循环求和,结果更新到数组中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int numTrees(int n) {
if(n <= 1)
return 1;
int dp[n + 1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i <= n ; i ++) {
dp[i] = 0;
for(int j = 1; j <= i; j ++)
dp[i] += dp[i - j] * dp[j - 1];
}
return dp[n];
}
};

Leetcode97. Interleaving String

Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2.

Example 1:

1
2
Input: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac"
Output: true

Example 2:

1
2
Input: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc"
Output: false

这道求交织相错的字符串,只要是遇到字符串的子序列或是匹配问题直接就上动态规划 Dynamic Programming,其他的都不要考虑,什么递归呀的都是浮云(当然带记忆数组的递归写法除外,因为这也可以算是 DP 的一种),千辛万苦的写了递归结果拿到 OJ 上妥妥 Time Limit Exceeded,能把人气昏了,所以还是直接就考虑 DP 解法省事些。一般来说字符串匹配问题都是更新一个二维 dp 数组,核心就在于找出状态转移方程。那么我们还是从题目中给的例子出发吧,手动写出二维数组 dp 如下:

1
2
3
4
5
6
7
Ø d b b c a
Ø T F F F F F
a T F F F F F
a T T T T T F
b F T T F T F
c F F T T T T
c F F F T F T

首先,这道题的大前提是字符串 s1 和 s2 的长度和必须等于 s3 的长度,如果不等于,肯定返回 false。那么当 s1 和 s2 是空串的时候,s3 必然是空串,则返回 true。所以直接给 dp[0][0] 赋值 true,然后若 s1 和 s2 其中的一个为空串的话,那么另一个肯定和 s3 的长度相等,则按位比较,若相同且上一个位置为 True,赋 True,其余情况都赋 False,这样的二维数组 dp 的边缘就初始化好了。下面只需要找出状态转移方程来更新整个数组即可,我们发现,在任意非边缘位置 dp[i][j] 时,它的左边或上边有可能为 True 或是 False,两边都可以更新过来,只要有一条路通着,那么这个点就可以为 True。那么我们得分别来看,如果左边的为 True,那么我们去除当前对应的 s2 中的字符串 s2[j - 1] 和 s3 中对应的位置的字符相比(计算对应位置时还要考虑已匹配的s1中的字符),为 s3[j - 1 + i], 如果相等,则赋 True,反之赋 False。 而上边为 True 的情况也类似,所以可以求出状态转移方程为:

1
dp[i][j] = (dp[i - 1][j] && s1[i - 1] == s3[i - 1 + j]) || (dp[i][j - 1] && s2[j - 1] == s3[j - 1 + i]);

其中 dp[i][j] 表示的是 s2 的前 i 个字符和 s1 的前 j 个字符是否匹配 s3 的前 i+j 个字符,根据以上分析,可写出代码如下:

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
int n1 = s1.size(), n2 = s2.size(), n3 = s3.size();
if(n1 + n2 != n3)
return false;
bool dp[n1+1][n2+1];
dp[0][0] = true;
for(int i = 1; i <= n1; i ++)
dp[i][0] = dp[i-1][0] && s1[i-1] == s3[i-1];
for(int i = 1; i <= n2; i ++)
dp[0][i] = dp[0][i-1] && s2[i-1] == s3[i-1];
for(int i = 1; i <= n1; i ++)
for(int j = 1; j <= n2; j ++)
dp[i][j] = (dp[i-1][j] && s1[i-1] == s3[i-1+j]) ||
(dp[i][j-1] && s2[j-1] == s3[j-1+i]);
return dp[n1][n2];
}
};

我们也可以把for循环合并到一起,用if条件来处理边界情况,整体思路和上面的解法没有太大的区别,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
if (s1.size() + s2.size() != s3.size()) return false;
int n1 = s1.size(), n2 = s2.size();
vector<vector<bool>> dp(n1 + 1, vector<bool> (n2 + 1, false));
for (int i = 0; i <= n1; ++i) {
for (int j = 0; j <= n2; ++j) {
if (i == 0 && j == 0) {
dp[i][j] = true;
} else if (i == 0) {
dp[i][j] = dp[i][j - 1] && s2[j - 1] == s3[i + j - 1];
} else if (j == 0) {
dp[i][j] = dp[i - 1][j] && s1[i - 1] == s3[i + j - 1];
} else {
dp[i][j] = (dp[i - 1][j] && s1[i - 1] == s3[i + j - 1]) || (dp[i][j - 1] && s2[j - 1] == s3[i + j - 1]);
}
}
}
return dp[n1][n2];
}
};

Leetcode98. Validate Binary Search Tree

Given a binary tree, determine if it is a valid binary search tree (BST).

Assume a BST is defined as follows:

The left subtree of a node contains only nodes with keys less than the node’s key.
The right subtree of a node contains only nodes with keys greater than the node’s key.
Both the left and right subtrees must also be binary search trees.

Example 1:

1
2
3
4
5
    2
/ \
1 3
Input: [2,1,3]
Output: true

Example 2:

1
2
3
4
5
6
7
8
     5
/ \
1 4
/ \
3 6
Input: [5,1,4,null,null,3,6]
Output: false
Explanation: The root node's value is 5 but its right child's value is 4.

可以利用它本身的性质来做,即左<根<右,也可以通过利用中序遍历结果为有序数列来做,下面我们先来看最简单的一种,就是利用其本身性质来做,初始化时带入系统最大值和最小值,在递归过程中换成它们自己的节点值,用long代替int就是为了包括int的边界条件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:

bool isvalid(TreeNode* root, long minn, long maxx) {
if(root == NULL)
return true;
if(!(minn < root->val && root->val < maxx))
return false;
return isvalid(root->left, minn, root->val) && isvalid(root->right, root->val, maxx);
}

bool isValidBST(TreeNode* root) {
if(root == NULL)
return true;
return isvalid(root, LONG_MIN, LONG_MAX);
}
};

Leetcode99. Recover Binary Search Tree

You are given the root of a binary search tree (BST), where exactly two nodes of the tree were swapped by mistake. Recover the tree without changing its structure.

Follow up: A solution using O(n) space is pretty straight forward. Could you devise a constant space solution?

Example 1:

1
2
3
Input: root = [1,3,null,null,2]
Output: [3,1,null,null,2]
Explanation: 3 cannot be a left child of 1 because 3 > 1. Swapping 1 and 3 makes the BST valid.

Example 2:

1
2
3
Input: root = [3,1,4,null,null,2]
Output: [2,1,4,null,null,3]
Explanation: 2 cannot be in the right subtree of 3 because 2 < 3. Swapping 2 and 3 makes the BST valid.

这道题要求我们复原一个二叉搜索树,说是其中有两个的顺序被调换了。用双指针来代替一维向量的,但是这种方法用到了递归,也不是 O(1) 空间复杂度的解法,这里需要三个指针,first,second 分别表示第一个和第二个错乱位置的节点,pre 指向当前节点的中序遍历的前一个节点。这里用传统的中序遍历递归来做,不过再应该输出节点值的地方,换成了判断 pre 和当前节点值的大小,如果 pre 的大,若 first 为空,则将 first 指向 pre 指的节点,把 second 指向当前节点。这样中序遍历完整个树,若 first 和 second 都存在,则交换它们的节点值即可。这个算法的空间复杂度仍为 O(n),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
class Solution {
public:
TreeNode *first, *second, *pre = NULL;
void recoverTree(TreeNode* root) {
helper(root);
swap(first->val, second->val);
}

void helper(TreeNode* root) {
if(root == NULL)
return ;
helper(root->left);

if (!pre)
pre = root;
else {
if (pre->val > root->val) {
if (!first)
first = pre;
second = root;
}
pre = root;
}

helper(root->right);
}
};

题目要求上说 O(n) 的解法很直观,这种解法需要用到递归,用中序遍历树,并将所有节点存到一个一维向量中,把所有节点值存到另一个一维向量中,然后对存节点值的一维向量排序,在将排好的数组按顺序赋给节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// O(n) space complexity
class Solution {
public:
void recoverTree(TreeNode* root) {
vector<TreeNode*> list;
vector<int> vals;
inorder(root, list, vals);
sort(vals.begin(), vals.end());
for (int i = 0; i < list.size(); ++i) {
list[i]->val = vals[i];
}
}
void inorder(TreeNode* root, vector<TreeNode*>& list, vector<int>& vals) {
if (!root) return;
inorder(root->left, list, vals);
list.push_back(root);
vals.push_back(root->val);
inorder(root->right, list, vals);
}
};

Leetcode100. Same Tree

Given two binary trees, write a function to check if they are the same or not. Two binary trees are considered the same if they are structurally identical and the nodes have the same value.

Example 1:

1
2
3
4
5
6
Input:     1         1
/ \ / \
2 3 2 3

[1,2,3], [1,2,3]
Output: true

Example 2:

1
2
3
4
5
6
Input:     1         1
/ \
2 2

[1,2], [1,null,2]
Output: false

Example 3:

1
2
3
4
5
6
Input:     1         1
/ \ / \
2 1 1 2

[1,2,1], [1,1,2]
Output: false

递归判断两个树是不是一样的,简单。

1
2
3
4
5
6
7
8
9
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
if(p == NULL && q == NULL) return true;
if(p == NULL || q == NULL) return false;
if(p->val != q->val) return false;
else return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
};

Leetcode802. Find Eventual Safe States

We start at some node in a directed graph, and every turn, we walk along a directed edge of the graph. If we reach a terminal node (that is, it has no outgoing directed edges), we stop.

We define a starting node to be safe if we must eventually walk to a terminal node. More specifically, there is a natural number k, so that we must have stopped at a terminal node in less than k steps for any choice of where to walk.

Return an array containing all the safe nodes of the graph. The answer should be sorted in ascending order.

The directed graph has n nodes with labels from 0 to n - 1, where n is the length of graph. The graph is given in the following form: graph[i] is a list of labels j such that (i, j) is a directed edge of the graph, going from node i to node j.

Example 1:

1
2
3
4
Illustration of graph
Input: graph = [[1,2],[2,3],[5],[0],[5],[],[]]
Output: [2,4,5,6]
Explanation: The given graph is shown above.

Example 2:

1
2
Input: graph = [[1,2,3,4],[1,2],[3,4],[0,4],[]]
Output: [4]

深度优先搜索。

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
class Solution {
public: // -1: unknow, 1: safe, 2: visiting, 3: unsafe
vector<int> eventualSafeNodes(vector<vector<int>>& graph) {
int size = graph.size();
vector<int> safe(size, -1);
vector<int> res;
for (int i = 0; i < size; i ++) {
if (helper(graph, safe, i) == 1)
res.push_back(i);
}
return res;
}

int helper(vector<vector<int>>& graph, vector<int>& safe, int cur) {
if (safe[cur] == 2)
return safe[cur] = 3;
if (safe[cur] != -1)
return safe[cur];
safe[cur] = 2;
for (int i = 0; i < graph[cur].size(); i ++)
if (helper(graph, safe, graph[cur][i]) == 3)
return safe[cur] = 3;
return safe[cur] = 1;
}
};

Leetcode804. Unique Morse Code Words

International Morse Code defines a standard encoding where each letter is mapped to a series of dots and dashes, as follows: “a” maps to “.-“, “b” maps to “-…”, “c” maps to “-.-.”, and so on.

For convenience, the full table for the 26 letters of the English alphabet is given below:

[“.-“,”-…”,”-.-.”,”-..”,”.”,”..-.”,”–.”,”….”,”..”,”.—“,”-.-“,”.-..”,”–”,”-.”,”—“,”.–.”,”–.-“,”.-.”,”…”,”-“,”..-“,”…-“,”.–”,”-..-“,”-.–”,”–..”]
Now, given a list of words, each word can be written as a concatenation of the Morse code of each letter. For example, “cba” can be written as “-.-..–…”, (which is the concatenation “-.-.” + “-…” + “.-“). We’ll call such a concatenation, the transformation of a word.

Return the number of different transformations among all words we have.

Example:

1
2
3
4
5
6
7
8
Input: words = ["gin", "zen", "gig", "msg"]
Output: 2
Explanation:
The transformation of each word is:
"gin" -> "--...-."
"zen" -> "--...-."
"gig" -> "--...--."
"msg" -> "--...--."

There are 2 different transformations, “–…-.” and “–…–.”.
Note:

  • The length of words will be at most 100.
  • Each words[i] will have length in range [1, 12].
  • words[i] will only consist of lowercase letters.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
int uniqueMorseRepresentations(vector<string>& words) {
string map[26] = {".-","-...","-.-.","-..",".","..-.","--.","....","..",".---","-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",".--","-..-","-.--","--.."};
set<string> result;
for(int i = 0; i < words.size(); i ++) {
string temp = "";
for(int j = 0; j < words[i].length(); j ++)
temp += map[words[i][j] - 'a'];
result.insert(temp);
}
return result.size();
}
};

借助set和数组辅助,遍历保存结果,最后统计哈希表的大小

Leetcode806. Number of Lines To Write String

We are to write the letters of a given string S, from left to right into lines. Each line has maximum width 100 units, and if writing a letter would cause the width of the line to exceed 100 units, it is written on the next line. We are given an array widths, an array where widths[0] is the width of ‘a’, widths[1] is the width of ‘b’, …, and widths[25] is the width of ‘z’.

Now answer two questions: how many lines have at least one character from S, and what is the width used by the last such line? Return your answer as an integer list of length 2.

Example :

1
2
3
4
5
6
7
Input: 
widths = [10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10]
S = "abcdefghijklmnopqrstuvwxyz"
Output: [3, 60]
Explanation:
All letters have the same length of 10. To write all 26 letters,
we need two full lines and one line with 60 units.

Example :

1
2
3
4
5
6
7
8
9
10
Input: 
widths = [4,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10]
S = "bbbcccdddaaa"
Output: [2, 4]
Explanation:
All letters except 'a' have the same length of 10, and
"bbbcccdddaa" will cover 9 * 10 + 2 * 4 = 98 units.
For the last 'a', it is written on the second line because
there is only 2 units left in the first line.
So the answer is 2 lines, plus 4 units in the second line.

Note:

  • The length of S will be in the range [1, 1000].
  • S will only contain lowercase letters.
  • widths is an array of length 26.
  • widths[i] will be in the range of [2, 10].

好坑啊,一个字母还不能拆开放。。。。现在的行长度是cur,如果cur加上当前的字母长度超过100了,则从下一行开始,cur变为当前的字母长度。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
vector<int> numberOfLines(vector<int>& widths, string S) {
int res=1;
int cur=0;
for(int i=0;i<S.length();i++){
int width = widths[S[i]-'a'];
res = cur + width > 100 ? res+1 : res;
cur = cur + width > 100 ? width : cur+width;
}
return {res,cur};
}
};

Leetcode807. Max Increase to Keep City Skyline

In a 2 dimensional array grid, each value grid[i][j] represents the height of a building located there. We are allowed to increase the height of any number of buildings, by any amount (the amounts can be different for different buildings). Height 0 is considered to be a building as well.

At the end, the “skyline” when viewed from all four directions of the grid, i.e. top, bottom, left, and right, must be the same as the skyline of the original grid. A city’s skyline is the outer contour of the rectangles formed by all the buildings when viewed from a distance. See the following example.

What is the maximum total sum that the height of the buildings can be increased?

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Input: grid = [[3,0,8,4],[2,4,5,7],[9,2,6,3],[0,3,1,0]]
Output: 35
Explanation:
The grid is:
[ [3, 0, 8, 4],
[2, 4, 5, 7],
[9, 2, 6, 3],
[0, 3, 1, 0] ]

The skyline viewed from top or bottom is: [9, 4, 8, 7]
The skyline viewed from left or right is: [8, 7, 9, 3]

The grid after increasing the height of buildings without affecting skylines is:

gridNew = [ [8, 4, 8, 7],
[7, 4, 7, 7],
[9, 4, 8, 7],
[3, 3, 3, 3] ]

Notes:

  1. 1 < grid.length = grid[0].length <= 50.
  2. All heights grid[i][j] are in the range [0, 100].
  3. All buildings in grid[i][j] occupy the entire grid cell: that is, they are a 1 x 1 x grid[i][j] rectangular prism.

这道题非常简单,首先找到每行每列的最大值,然后每个元素要小于对应的最大值中的小者,比如grid[0][0]要小于topmax[0]和leftmax[0]之中的最小值,grid[0][1]要小于topmax[0]和leftmax[1]之中的最小值。为什么花了这么长时间呢,是因为傻逼了,max数组设成了4爆了。。。煞笔。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
int maxIncreaseKeepingSkyline(vector<vector<int>>& grid) {
int length = grid[0].size();
int* topmax,*leftmax;
topmax = (int*)malloc(sizeof(int)*length);
leftmax = (int*)malloc(sizeof(int)*length);
for(int i = 0; i < length; i ++)
topmax[i] = leftmax[i] = 0;
for(int i = 0; i < length; i ++)
for(int j = 0; j < length; j ++){
if(grid[i][j] > topmax[i])
topmax[i] = grid[i][j];
if(grid[i][j] > leftmax[j])
leftmax[j] = grid[i][j];
}
int result = 0;
for(int i = 0; i < length; i ++)
for(int j = 0; j < length; j ++)
result += ((leftmax[j] > topmax[i] ? topmax[i] : leftmax[j]) - grid[i][j]);
return result;
}
};

Leetcode808. Soup Servings

There are two types of soup: type A and type B. Initially we have N ml of each type of soup. There are four kinds of operations:

  • Serve 100 ml of soup A and 0 ml of soup B
  • Serve 75 ml of soup A and 25 ml of soup B
  • Serve 50 ml of soup A and 50 ml of soup B
  • Serve 25 ml of soup A and 75 ml of soup B

When we serve some soup, we give it to someone and we no longer have it. Each turn, we will choose from the four operations with equal probability 0.25. If the remaining volume of soup is not enough to complete the operation, we will serve as much as we can. We stop once we no longer have some quantity of both types of soup.

Note that we do not have the operation where all 100 ml’s of soup B are used first.

Return the probability that soup A will be empty first, plus half the probability that A and B become empty at the same time.

Example:

1
2
3
4
Input: N = 50
Output: 0.625
Explanation:
If we choose the first two operations, A will become empty first. For the third operation, A and B will become empty at the same time. For the fourth operation, B will become empty first. So the total probability of A becoming empty first plus half the probability that A and B become empty at the same time, is 0.25 * (1 + 1 + 0.5 + 0) = 0.625.

Notes:

  • 0 <= N <= 10^9.
  • Answers within 10^-6 of the true value will be accepted as correct.

自己当初写的代码的思路是对的,但是细节实现上没考虑周全。这里还是参考了网上代码的总结:

我们在这里采用的方法严格来讲是DFS + memorization,也就是需要计算一个子问题的时候,我们首先在表格中查找,看看原来有没有被计算过,如果被计算过,则直接返回结果,否则就再重新计算,并将结果保存在表格中。这样的好处是没必要计算每个子问题,只计算递归过程中用到的子问题。如果我们定义f(a, b)表示有a毫升的A和b毫升的B时符合条件的概率,那么容易知道递推公式就是:f(a, b) = 0.25 * (f(a - 4, b) + f(a - 3, b - 1) + f(a - 2, b - 2) + f(a - 1, b - 3)),其中平凡条件是:

当a < 0 && b < 0时,f(a, b) = 0.5,表示A和B同时用完;

当a <= 0 && b > 0时,f(a, b) = 1.0,表示A先用完;

当a > 0 && b<= 0时,f(a, b) = 0.0,表示B先用完。

所以当遇到这三种情况的时候,我们直接返回对应的平凡值;否则就首先查表,看看原来有没有计算过,如果已经计算过了,就直接返回;否则才开始按照递推公式计算。

1)如果A或者B不足25ml,但是又大于0ml,那么我们需要把它当做完整的25ml来对待。另外,由于A和B serve的最小单位是25ml,所以我们在f(a, b)中约定a和b是25ml的倍数,具体在实现中,我们需要首先对n做n = ceil(N / 25.0)的处理。

2)题目中给出N的范围是[0, 10^9],这是一个特别大的数字了。另外又提到当我们返回的结果与真实误差小于10^6的时候,就算正确。直觉告诉我们,当N趋向于无穷大时,A先被serve完以及A和B同时被serve完的概率会无限接近于1。经过严格计算我们知道当N >= 4800之后,返回的概率值与1的差距就小于10^6了。所以当N >= 4800的时候,我们就直接返回1。如果不这样做的话,就会导致memo需要开辟的内容特别大,引起MLE。

代码:

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
class Solution {
public:
double soupServings(int N) {
int n = ceil(N / 25.0);
return N >= 4800 ? 1.0 : f(n, n);
}
private:
double f(int a, int b) {
if (a <= 0 && b <= 0) {
return 0.5;
}
if (a <= 0) {
return 1;
}
if (b <= 0) {
return 0;
}
if (memo[a][b] > 0) {
return memo[a][b];
}
memo[a][b] = 0.25 * (f(a - 4, b) + f(a - 3, b - 1) + f(a - 2, b - 2) + f(a - 1, b - 3));
return memo[a][b];
}
double memo[200][200];
};

Leetcode809. Expressive Words

Sometimes people repeat letters to represent extra feeling. For example:

1
2
"hello" -> "heeellooo"
"hi" -> "hiiii"

In these strings like “heeellooo”, we have groups of adjacent letters that are all the same: “h”, “eee”, “ll”, “ooo”.

You are given a string s and an array of query strings words. A query word is stretchy if it can be made to be equal to s by any number of applications of the following extension operation: choose a group consisting of characters c, and add some number of characters c to the group so that the size of the group is three or more.

For example, starting with “hello”, we could do an extension on the group “o” to get “hellooo”, but we cannot get “helloo” since the group “oo” has a size less than three. Also, we could do another extension like “ll” -> “lllll” to get “helllllooo”. If s = “helllllooo”, then the query word “hello” would be stretchy because of these two extension operations: query = “hello” -> “hellooo” -> “helllllooo” = s.
Return the number of query strings that are stretchy.

Example 1:

1
2
3
4
5
Input: s = "heeellooo", words = ["hello", "hi", "helo"]
Output: 1
Explanation:
We can extend "e" and "o" in the word "hello" to get "heeellooo".
We can't extend "helo" to get "heeellooo" because the group "ll" is not size 3 or more.

Example 2:

1
2
Input: s = "zzzzzyyyyy", words = ["zzyy","zy","zyy"]
Output: 3

依次按顺序统计s与各个单词的字母个数,如果相同顺序(连续相等字母算顺序中的一位)的字母不相等,则不符合条件,如果s的字母个数小于3且s与单词的字母个数不等,不符合条件,如果s的字母个数大于等于3且小于单词的字母个数,不符合条件,执行到某个字符串结束,如果没都达到最后,则不符合条件,否则满足条件。Version 2只统计s一次,然后再比对。

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
class Solution {
public:
int expressiveWords(string s, vector<string>& words) {
int res = 0;
bool legal = true;
int len = s.length();
for (string word : words) {
int len2 = word.length();
legal = true;
if (len < len2)
continue;
int p1 = 0, p2 = 0;
while (p1 < len && p2 < len2) {
if (s[p1] == word[p2]) {
int t1 = 1, t2 = 1;
while (p1 < len-1 && s[p1] == s[p1+1]) {
t1 ++; p1 ++;
}
while (p2 < len2-1 && word[p2] == word[p2+1]) {
t2 ++; p2 ++;
}
if (t1 == 2 && t1 - t2 == 1)
legal = false;
if ((t1 < 3 && t1 != t2) || t1 < t2)
break;
}
else
break;
p1 ++;
p2 ++;
}
if (p1 == len && p2 == word.size() && legal)
res ++;
}
return res;
}
};

Leetcode811. Subdomain Visit Count

A website domain like “discuss.leetcode.com” consists of various subdomains. At the top level, we have “com”, at the next level, we have “leetcode.com”, and at the lowest level, “discuss.leetcode.com”. When we visit a domain like “discuss.leetcode.com”, we will also visit the parent domains “leetcode.com” and “com” implicitly.

Now, call a “count-paired domain” to be a count (representing the number of visits this domain received), followed by a space, followed by the address. An example of a count-paired domain might be “9001 discuss.leetcode.com”.

We are given a list cpdomains of count-paired domains. We would like a list of count-paired domains, (in the same format as the input, and in any order), that explicitly counts the number of visits to each subdomain.

Example 1:

1
2
3
4
5
6
Input: 
["9001 discuss.leetcode.com"]
Output:
["9001 discuss.leetcode.com", "9001 leetcode.com", "9001 com"]
Explanation:
We only have one website domain: "discuss.leetcode.com". As discussed above, the subdomain "leetcode.com" and "com" will also be visited. So they will all be visited 9001 times.

Example 2:

1
2
3
4
5
6
Input: 
["900 google.mail.com", "50 yahoo.com", "1 intel.mail.com", "5 wiki.org"]
Output:
["901 mail.com","50 yahoo.com","900 google.mail.com","5 wiki.org","5 org","1 intel.mail.com","951 com"]
Explanation:
We will visit "google.mail.com" 900 times, "yahoo.com" 50 times, "intel.mail.com" once and "wiki.org" 5 times. For the subdomains, we will visit "mail.com" 900 + 1 = 901 times, "com" 900 + 50 + 1 = 951 times, and "org" 5 times.

这个题就是非常简单的统计字符串,如果用java和python做的话几分钟就出来了,但是用了原生C++,没有用高级功能,自己手写的一些函数,就当熟悉熟悉了。

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
class Solution {
public:
vector<string> subdomainVisits(vector<string>& cpdomains) {
vector<string> res;
unordered_map<string,int> m;

for(int i=0;i<cpdomains.size();i++){
int num = 0;
string domain=cpdomains[i];
int domain_len = domain.length();
int j=0;
while(domain[j]!=' ')
j++;

for(int i=0;i<j;i++)
num = num *10 + (domain[i]-'0');
vector<string> temp;
int part_end=j+1;
string tt;
for(int i=j+1;i<domain_len;i++){
if(domain[i]=='.'){
tt = domain.substr(part_end,part_end-i+1);
temp.push_back(tt);
part_end=i+1;
}
}
tt=domain.substr(part_end,domain_len-part_end);
temp.push_back(tt);

for(int i=0;i<temp.size();i++)
if(m.find(temp[i])==m.end()){
m.insert(pair<string,int>(temp[i],num));
}
else
m[temp[i]]+=num;
}
unordered_map<string,int>::iterator it;
for(it=m.begin();it!=m.end();it++){
string ddd = to_string(it->second)+" "+it->first;
res.push_back(ddd);
}
return res;
}
};

贴一下其他解法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
vector<string> subdomainVisits(vector<string>& cpdomains) {
vector<string> ans;
unordered_map<string, int> domains;
for(string s:cpdomains){
int count = stoi(s.substr(0, s.find(' ')));
domains[s.substr(s.find(' ') + 1)] += count;
domains[s.substr(s.find('.') + 1)] += count;
// try to find the second '.'
string sub = s.substr(s.find('.') + 1);
if(sub.find('.') != -1){
domains[sub.substr(sub.find('.') + 1)] += count;
}
}
for(auto it = domains.begin(); it!= domains.end(); ++it){
ans.push_back(to_string(it->second) + " " + it->first);
}
return ans;
}
};

python版本的:

1
2
3
4
5
6
7
8
9
10
class Solution(object):
def subdomainVisits(self, cpdomains):
ans = collections.Counter()
for domain in cpdomains:
count, domain = domain.split()
count = int(count)
frags = domain.split('.')
for i in xrange(len(frags)):
ans[".".join(frags[i:])] += count
return ["{} {}".format(ct, dom) for dom, ct in ans.items()]

Leetcode812. Largest Triangle Area

You have a list of points in the plane. Return the area of the largest triangle that can be formed by any 3 of the points.

Example:

1
2
Input: points = [[0,0],[0,1],[1,0],[0,2],[2,0]]
Output: 2

Explanation:
The five points are show in the figure below. The red triangle is the largest.

给定包含多个点的集合,从其中取三个点组成三角形,返回能组成的最大三角形的面积。

示例:输入: points = [[0,0],[0,1],[1,0],[0,2],[2,0]] 输出: 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
double getLength(int x1, int x2, int y1, int y2) {
return sqrt((double)(x1-x2)*(x1-x2)+(double)(y1-y2)*(y1-y2));
}
double largestTriangleArea(vector<vector<int>>& points) {
double result = 0.0;
double a, b, c, q;
int n = points.size();
for( int i = 0; i < n; i ++)
for( int j = 0; j < n; j ++)
for(int k = 0; k < n; k ++) {
double area = 0.5 * abs(points[i][0] * points[j][1] + points[j][0] * points[k][1] + points[k][0] * points[i][1] -points[i][0] * points[k][1] - points[k][0] * points[j][1] - points[j][0] * points[i][1]);
result = max(result, area);
}
return result;
}
};

Leetcode813. Largest Sum of Averages

We partition a row of numbers A into at most K adjacent (non-empty) groups, then our score is the sum of the average of each group. What is the largest score we can achieve?

Note that our partition must use every number in A, and that scores are not necessarily integers.

Example:

1
2
3
4
5
6
7
8
Input: 
A = [9,1,2,3,9]
K = 3
Output: 20
Explanation:
The best choice is to partition A into [9], [1, 2, 3], [9]. The answer is 9 + (1 + 2 + 3) / 3 + 9 = 20.
We could have also partitioned A into [9, 1], [2], [3, 9], for example.
That partition would lead to a score of 5 + 2 + 6 = 13, which is worse.

Note:

  • 1 <= A.length <= 100.
  • 1 <= A[i] <= 10000.
  • 1 <= K <= A.length.
  • Answers within 10^-6 of the correct answer will be accepted as correct.

首先来考虑dp数组的定义,dp数组不把K加进去的话就不知道当前要分几组,这个Hidden Information是解题的关键。这是DP中比较难的一类,有些DP题的隐藏信息藏的更深,不挖出来就无法解题。这道题的dp数组应该是个二维数组,其中dp[i][k]表示范围是[i, n-1]的子数组分成k组的最大得分。那么这里你就会纳闷了,为啥范围是[i, n-1]而不是[0, i],为啥要取后半段呢。由于把[i, n-1]范围内的子数组分成k组,那么suppose我们已经知道了任意范围内分成k-1组的最大分数,这是此类型题目的破题关键所在,要求状态k,一定要先求出所有的状态k-1,那么问题就转换成了从k-1组变成k组,即多分出一组,那么在范围[i, n-1]多分出一组,实际上就是将其分成两部分,一部分是一组,另一部分是k-1组,怎么分,就用一个变量j,遍历范围(i, n-1)中的每一个位置,那么分成的这两部分的分数如何计算呢?第一部分[i, j),由于是一组,那么直接求出平均值即可,另一部分由于是k-1组,由于我们已经知道了所有k-1的情况,可以直接从cache中读出来dp[j][k-1],二者相加即可 avg(i, j) + dp[j][k-1],所以我们可以得出状态转移方程如下:

1
dp[i][k] = max(avg(i, n) + max_{j > i} (avg(i, j) + dp[j][k-1]))

这里的avg(i, n)是其可能出现的情况,由于是至多分为k组,所以我们可以不分组,所以直接计算范围[i, n-1]内的平均值,然后用j来遍历区间(i, n-1)中的每一个位置,最终得到的dp[i][k]就即为所求。注意这里我们通过建立累加和数组sums来快速计算某个区间之和。前面提到了dp[i][k]表示的是范围[i, n-1]的子数组分成k组的最大得分,现在想想貌似定义为[0, i]范围内的子数组分成k组的最大得分应该也是可以的,那么此时j就是遍历(0, i)中的每个位置了,好像也没什么不妥的地方,有兴趣的童鞋可以尝试的写一下~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
double largestSumOfAverages(vector<int>& A, int K) {
int n = A.size();
vector<double> sums(n + 1);
vector<vector<double>> dp(n, vector<double>(K));
for (int i = 0; i < n; ++i) {
sums[i + 1] = sums[i] + A[i];
}
for (int i = 0; i < n; ++i) {
dp[i][0] = (sums[n] - sums[i]) / (n - i);
}
for (int k = 1; k < K; ++k) {
for (int i = 0; i < n - 1; ++i) {
for (int j = i + 1; j < n; ++j) {
dp[i][k] = max(dp[i][k], (sums[j] - sums[i]) / (j - i) + dp[j][k - 1]);
}
}
}
return dp[0][K - 1];
}
};

我们可以对空间进行优化,由于每次的状态k,只跟前一个状态k-1有关,所以我们不需要将所有的状态都保存起来,只需要保存前一个状态的值就行了,那么我们就用一个一维数组就可以了,不断的进行覆盖,从而达到了节省空间的目的,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
double largestSumOfAverages(vector<int>& A, int K) {
int n = A.size();
vector<double> sums(n + 1);
vector<double> dp(n);
for (int i = 0; i < n; ++i) {
sums[i + 1] = sums[i] + A[i];
}
for (int i = 0; i < n; ++i) {
dp[i] = (sums[n] - sums[i]) / (n - i);
}
for (int k = 1; k < K; ++k) {
for (int i = 0; i < n - 1; ++i) {
for (int j = i + 1; j < n; ++j) {
dp[i] = max(dp[i], (sums[j] - sums[i]) / (j - i) + dp[j]);
}
}
}
return dp[0];
}
};

我们也可以是用递归加记忆数组的方式来实现,记忆数组的运作原理和DP十分类似,也是一种cache,将已经计算过的结果保存起来,用的时候直接取即可,避免了大量的重复计算,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
double largestSumOfAverages(vector<int>& A, int K) {
int n = A.size();
vector<vector<double>> memo(101, vector<double>(101));
double cur = 0;
for (int i = 0; i < n; ++i) {
cur += A[i];
memo[i + 1][1] = cur / (i + 1);
}
return helper(A, K, n, memo);
}
double helper(vector<int>& A, int k, int j, vector<vector<double>>& memo) {
if (memo[j][k] > 0) return memo[j][k];
double cur = 0;
for (int i = j - 1; i > 0; --i) {
cur += A[i];
memo[j][k] = max(memo[j][k], helper(A, k - 1, i, memo) + cur / (j - i));
}
return memo[j][k];
}
};

Leetcode814. Binary Tree Pruning

We are given the head node root of a binary tree, where additionally every node’s value is either a 0 or a 1.

Return the same tree where every subtree (of the given tree) not containing a 1 has been removed.

(Recall that the subtree of a node X is X, plus every node that is a descendant of X.)

Example 1:

1
2
Input: [1,null,0,0,1]
Output: [1,null,0,null,1]

Explanation:
Only the red nodes satisfy the property “every subtree not containing a 1”.
The diagram on the right represents the answer.

Example 2:

1
2
Input: [1,0,1,0,0,0,1]
Output: [1,null,1,null,1]

Example 3:

1
2
Input: [1,1,0,1,1,0,1,0]
Output: [1,1,0,1,1,null,1]

删掉子树里没有1的,返回这棵树。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:

bool dele(TreeNode* root){
if(root == NULL)
return false;
bool a1 = dele(root->left);
bool a2 = dele(root->right);
if(!a1) root->left=NULL;
if(!a2) root->right=NULL;
return root->val==1 || a1 || a2;
}

TreeNode* pruneTree(TreeNode* root) {
return dele(root)?root:NULL;
}
};

Leetcode816. Ambiguous Coordinates

We had some 2-dimensional coordinates, like “(1, 3)” or “(2, 0.5)”. Then, we removed all commas, decimal points, and spaces, and ended up with the string S. Return a list of strings representing all possibilities for what our original coordinates could have been.

Our original representation never had extraneous zeroes, so we never started with numbers like “00”, “0.0”, “0.00”, “1.0”, “001”, “00.01”, or any other number that can be represented with less digits. Also, a decimal point within a number never occurs without at least one digit occuring before it, so we never started with numbers like “.1”.

The final answer list can be returned in any order. Also note that all coordinates in the final answer have exactly one space between them (occurring after the comma.)

Example 1:

1
2
Input: "(123)"
Output: ["(1, 23)", "(12, 3)", "(1.2, 3)", "(1, 2.3)"]

Example 2:

1
2
3
4
Input: "(00011)"
Output: ["(0.001, 1)", "(0, 0.011)"]
Explanation:
0.0, 00, 0001 or 00.01 are not allowed.

Example 3:

1
2
Input: "(0123)"
Output: ["(0, 123)", "(0, 12.3)", "(0, 1.23)", "(0.1, 23)", "(0.1, 2.3)", "(0.12, 3)"]

Example 4:

1
2
3
4
Input: "(100)"
Output: [(10, 0)]
Explanation:
1.0 is not allowed.

Note:

  • 4 <= S.length <= 12.
  • S[0] = “(“, S[S.length - 1] = “)”, and the other elements in S are digits.

这道题给了我们一个模糊坐标,括号里面很只有一个数字字符串,没有逗号也没有小数点,让我们自己添加逗号和小数点,让把所有可能的组合都返回。题目中给了很多例子,理解起题意来很容易。这道题的难点是如何合理的拆分,很多拆分是不合法的,题目举了很多不合法的例子,比如 “00”, “0.0”, “0.00”, “1.0”, “001”, “00.01”。那么我们需要归纳出所有不合法的corner case,然后剩下一般情况比如123,我们就按位加小数点即可。那么我们再来看一下那些非法的例子,我们发现一眼望去好多0,不错,0就是trouble maker,首先不能有0开头的长度大于1的整数,比如00, 001。其次,不能有0结尾的小数,比如0.0,0.00,1.0等。还有,小数的整数位上也不能有0开头的长度大于1的整数。那么我们来归纳一下吧,首先如果字符串为空,那么直接返回空集合。然后如果字符串长度大于1,且首尾字符都是0的话,那么不可分,比如 0xxx0,因为整数长度大于1的话不能以0开头,中间也没法加小数点,因为小数最后一位不能是0。如果长度大于1,第一位是0,但最后一位不是0,那我们可以在第一个0后面加个小数点返回,这时就必须要加小数点了,因为长度大于1的整数不能以0开头。再之后,如果最后一位是0,说明不能加小数点,直接把当前值返回即可。最后就是一般情况了,我们先把原数加入结果res,然后遍历中间的每个位置,都加个小数点,所有情况归纳如下:

1
2
3
4
5
6
if S == “”: return []
if S == “0”: return [S]
if S == “0XXX0”: return []
if S == “0XXX”: return [“0.XXX”]
if S == “XXX0”: return [S]
return [S, “X.XXX”, “XX.XX”, “XXX.X”…]
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
class Solution {
public:
vector<string> ambiguousCoordinates(string S) {
vector<string> res;
int n = S.size();
for (int i = 1; i < n - 2; ++i) {
vector<string> A = findAll(S.substr(1, i)), B = findAll(S.substr(i + 1, n - 2 - i));
for (auto &a : A) {
for (auto &b : B) {
res.push_back("(" + a + ", " + b + ")");
}
}
}
return res;
}
vector<string> findAll(string S) {
int n = S.size();
if (n == 0 || (n > 1 && S[0] == '0' && S[n - 1] == '0')) return {};
if (n > 1 && S[0] == '0') return {"0." + S.substr(1)};
if (S[n - 1] == '0') return {S};
vector<string> res{S};
for (int i = 1; i < n; ++i) res.push_back(S.substr(0, i) + "." + S.substr(i));
return res;
}
};

Leetcode817. Linked List Components

We are given head, the head node of a linked list containing unique integer values.

We are also given the list G, a subset of the values in the linked list.

Return the number of connected components in G, where two values are connected if they appear consecutively in the linked list.

Example 1:

1
2
3
4
5
6
Input: 
head: 0->1->2->3
G = [0, 1, 3]
Output: 2
Explanation:
0 and 1 are connected, so [0, 1] and [3] are the two connected components.

Example 2:

1
2
3
4
5
6
Input: 
head: 0->1->2->3->4
G = [0, 3, 1, 4]
Output: 2
Explanation:
0 and 1 are connected, 3 and 4 are connected, so [0, 1] and [3, 4] are the two connected components.

Note:

  • If N is the length of the linked list given by head, 1 <= N <= 10000.
  • The value of each node in the linked list will be in the range [0, N - 1].
  • 1 <= G.length <= 10000.
  • G is a subset of all values in the linked list.

这道题给了我们一个链表,又给了我们一个结点值数组,里面不一定包括了链表中所有的结点值。让我们返回结点值数组中有多少个相连的组件,因为缺失的结点值会将原链表断开,实际上就是让我们求有多少个相连的子链表,题目中给的例子很好的说明题意。这道题并不需要什么特别高深的技巧,难懂的算法,直接按题目的要求来找就可以了。首先,为了快速的在结点值数组中查找某个结点值是否存在,我们可以将所有的结点值放到一个HashSet中,这样我们就能在常数级的时间复杂度中查找。然后我们就可以来遍历链表了,对于遍历到的每个结点值,我们只有两种情况,在或者不在HashSet中。不在HashSet中的情况比较好办,说明此时断开了,而在HashSet中的结点,有可能是该连续子链表的起始点,或者是中间的某个点,而我们的计数器对该子链表只能自增1,所以我们需要想办法来hanlde这种情况。博主最先想到的办法是先处理不在HashSet中的结点,处理方法就是直接跳到下一个结点。那么对于在HashSet中的结点,我们首先将计数器res自增1,然后再来个循环,将之后所有在集合中的结点都遍历完,这样才不会对同一个子链表多次增加计数器,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int numComponents(ListNode* head, vector<int>& G) {
int res = 0;
unordered_set<int> nodeSet(G.begin(), G.end());
while (head) {
if (!nodeSet.count(head->val)) {
head = head->next;
continue;
}
++res;
while (head && nodeSet.count(head->val)) {
head = head->next;
}
}
return res;
}
};

Leetcode819. Most Common Word

Given a paragraph and a list of banned words, return the most frequent word that is not in the list of banned words. It is guaranteed there is at least one word that isn’t banned, and that the answer is unique.

Words in the list of banned words are given in lowercase, and free of punctuation. Words in the paragraph are not case sensitive. The answer is in lowercase.

Example:

1
2
3
4
Input: 
paragraph = "Bob hit a ball, the hit BALL flew far after it was hit."
banned = ["hit"]
Output: "ball"

Explanation:
“hit” occurs 3 times, but it is a banned word.
“ball” occurs twice (and no other word does), so it is the most frequent non-banned word in the paragraph.
Note that words in the paragraph are not case sensitive,
that punctuation is ignored (even if adjacent to words, such as “ball,”),
and that “hit” isn’t the answer even though it occurs more because it is banned.

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
class Solution {
public:
string mostCommonWord(string paragraph, vector<string>& banned) {
map<string, int> mapp;
string temp;
int length = paragraph.length();
for(int i = 0; i < length; i ++)
if(paragraph[i]>='A' && paragraph[i]<='Z')
paragraph[i] -= ('A'-'a');
for(int i = 0; i < length;i ++) {
if(paragraph[i]==' ') {
continue;
}
temp = "";
while(i<length && paragraph[i]>='a' && paragraph[i]<='z')
temp += paragraph[i++];
if (mapp.find(temp) == mapp.end()) {
mapp[temp] = 1;
}
else
mapp[temp] ++;
}
int maxx = 0;
string result = "";
for(map<string, int>::iterator iter = mapp.begin(); iter != mapp.end(); iter ++ ) {
if (iter->second > maxx && find(banned.begin(), banned.end(), iter->first) == banned.end()) {
result = iter->first;
maxx = iter->second;
}
}
return result;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
string mostCommonWord(string p, vector<string>& banned) {
unordered_set<string> ban(banned.begin(), banned.end());
unordered_map<string, int> count;
for (auto & c: p) c = isalpha(c) ? tolower(c) : ' ';
istringstream iss(p);
string w;
pair<string, int> res ("", 0);
while (iss >> w)
if (ban.find(w) == ban.end() && ++count[w] > res.second)
res = make_pair(w, count[w]);
return res.first;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
string mostCommonWord(string paragraph, vector<string>& banned) {
unordered_map<string, int>m;
for(int i = 0; i < paragraph.size();){
string s = "";
while(i < paragraph.size() && isalpha(paragraph[i])) s.push_back(tolower(paragraph[i++]));
while(i < paragraph.size() && !isalpha(paragraph[i])) i++;
m[s]++;
}
for(auto x: banned) m[x] = 0;
string res = "";
int count = 0;
for(auto x: m)
if(x.second > count) res = x.first, count = x.second;
return res;
}
};

Leetcode820. Short Encoding of Words

A valid encoding of an array of words is any reference string s and array of indices indices such that:

  • words.length == indices.length
  • The reference string s ends with the ‘#’ character.
  • For each index indices[i], the substring of s starting from indices[i] and up to (but not including) the next ‘#’ character is equal to words[i].

Given an array of words, return the length of the shortest reference string s possible of any valid encoding of words.

Example 1:

1
2
3
4
5
6
Input: words = ["time", "me", "bell"]
Output: 10
Explanation: A valid encoding would be s = "time#bell#" and indices = [0, 2, 5].
words[0] = "time", the substring of s starting from indices[0] = 0 to the next '#' is underlined in "time#bell#"
words[1] = "me", the substring of s starting from indices[1] = 2 to the next '#' is underlined in "time#bell#"
words[2] = "bell", the substring of s starting from indices[2] = 5 to the next '#' is underlined in "time#bell#"

Example 2:

1
2
3
Input: words = ["t"]
Output: 2
Explanation: A valid encoding would be s = "t#" and indices = [0].

这道题给了我们一个单词数组,让我们对其编码,不同的单词之间加入#号,每个单词的起点放在一个坐标数组内,终点就是#号,能合并的单词要进行合并,问输入字符串的最短长度。题意不难理解,难点在于如何合并单词,我们观察题目的那个例子,me和time是能够合并的,只要标清楚其实位置,time的起始位置是0,me的起始位置是2,那么根据#号位置的不同就可以顺利的取出me和time。需要注意的是,如果me换成im,或者tim的话,就不能合并了,因为我们是要从起始位置到#号之前所有的字符都要取出来。搞清楚了这一点之后,我们在接着观察,由于me是包含在time中的,所以我们处理的顺序应该是先有time#,然后再看能否包含me,而不是先生成了me#之后再处理time,所以我们可以得出结论,应该先处理长单词,那么就给单词数组按长度排序一下就行,自己重写一个comparator就行。然后我们遍历数组,对于每个单词,我们都在编码字符串查找一下,如果没有的话,直接加上这个单词,再加一个#号,如果有的话,就可以得到出现的位置。比如在time#中查找me,得到found=2,然后我们要验证该单词后面是否紧跟着一个#号,所以我们直接访问found+word.size()这个位置,如果不是#号,说明不能合并,我们还是要加上这个单词和#号。最后返回编码字符串的长度即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int minimumLengthEncoding(vector<string>& words) {
string res = "";
sort(words.begin(), words.end(), [](string &a, string &b){ return a.length() > b.length();});
for (string word : words) {
int t = 0, prev_t = -1;
while((t=res.find(word, t))!=string::npos) {
prev_t = t;
t++;
}
if (prev_t == -1 || res[prev_t + word.length()] != '#')
res += (word + '#');
}
return res.length();
}
};

Leetcode821. Shortest Distance to a Character

Given a string S and a character C, return an array of integers representing the shortest distance from the character C in the string.

Example 1:

1
2
Input: S = "loveleetcode", C = 'e'
Output: [3, 2, 1, 0, 1, 0, 0, 1, 2, 2, 1, 0]

简单题,这里是对于每个是字符C的位置,然后分别像左右两边扩散,不停是更新距离,这样当所有的字符C的点都扩散完成之后,每个非字符C位置上的数字就是到字符C的最短距离了,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
vector<int> shortestToChar(string S, char C) {
int len = S.length();
int signal[len];
vector<int> res(len,INT_MAX);
int i;
for(i=0;i<len;i++){
if(S[i] == C){
res[i]=0;

for(int j=i;j<len;j++)
res[j]=min(res[j],j-i);
for(int j=i;j>=0;j--)
res[j]=min(res[j],i-j);
}
}
return res;
}
};

下面这种方法也是建立距离场的思路,不过更加巧妙一些,只需要正反两次遍历就行。首先进行正向遍历,若当前位置是字符C,那么直接赋0,否则看如果不是首位置,那么当前位置的值等于前一个位置的值加1。这里不用和当前的值进行比较,因为这个算出来的值不会大于初始化的值。然后再进行反向遍历,要从倒数第二个值开始往前遍历,用后一个值加1来更新当前位置的值,此时就要和当前值做比较,取较小的那个,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
vector<int> shortestToChar(string S, char C) {
vector<int> res(S.size(), S.size());
for (int i = 0; i < S.size(); ++i) {
if (S[i] == C) res[i] = 0;
else if (i > 0) res[i] = res[i - 1] + 1;
}
for (int i = (int)S.size() - 2; i >= 0; --i) {
res[i] = min(res[i], res[i + 1] + 1);
}
return res;
}
};

Leetcode822. Card Flipping Game

On a table are N cards, with a positive integer printed on the front and back of each card (possibly different).

We flip any number of cards, and after we choose one card.

If the number X on the back of the chosen card is not on the front of any card, then this number X is good.

What is the smallest number that is good? If no number is good, output 0.

Here, fronts[i] and backs[i] represent the number on the front and back of card i.

A flip swaps the front and back numbers, so the value on the front is now on the back and vice versa.

Example:

1
2
3
4
Input: fronts = [1,2,4,4,7], backs = [1,3,4,1,3]
Output: 2
Explanation: If we flip the second card, the fronts are [1,3,4,4,7] and the backs are [1,2,4,1,3].
We choose the second card, which has number 2 on the back, and it isn't on the front of any card, so 2 is good.

Note:

  • 1 <= fronts.length == backs.length <= 1000.
  • 1 <= fronts[i] <= 2000.
  • 1 <= backs[i] <= 2000.

给了一些正反都有正数的卡片,可以翻面,让我们找到一个最小的数字,在卡的背面,且要求其他卡正面上均没有这个数字。简而言之,就是要在backs数组找一个最小数字,使其不在fronts数组中。我们想,既然不能在fronts数组中,说明卡片背面的数字肯定跟其正面的数字不相同,否则翻来翻去都是相同的数字,肯定会在fronts数组中。那么我们可以先把正反数字相同的卡片都找出来,将数字放入一个HashSet,也方便我们后面的快速查找。现在其实我们只需要在其他的数字中找到一个最小值即可,因为正反数字不同,就算fronts中其他卡片的正面还有这个最小值,我们可以将那张卡片翻面,使得相同的数字到backs数组,总能使得fronts数组不包含有这个最小值,就像题目中给的例子一样,数字2在第二张卡的背面,就算其他卡面也有数字2,只要其不是正反都是2,我们都可以将2翻到背面去,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int flipgame(vector<int>& fronts, vector<int>& backs) {
int res = INT_MAX, n = fronts.size();
unordered_set<int> same;
for (int i = 0; i < n; ++i) {
if (fronts[i] == backs[i]) same.insert(fronts[i]);
}
for (int front : fronts) {
if (!same.count(front)) res = min(res, front);
}
for (int back : backs) {
if (!same.count(back)) res = min(res, back);
}
return (res == INT_MAX) ? 0 : res;
}
};

Leetcode823. Binary Trees With Factors

Given an array of unique integers, each integer is strictly greater than 1.

We make a binary tree using these integers and each number may be used for any number of times.

Each non-leaf node’s value should be equal to the product of the values of it’s children.

How many binary trees can we make? Return the answer modulo 10 ** 9 + 7.

Example 1:

1
2
3
Input: A = [2, 4]
Output: 3
Explanation: We can make these trees: [2], [4], [4, 2, 2]

Example 2:

1
2
3
Input: A = [2, 4, 5, 10]
Output: 7
Explanation: We can make these trees: [2], [4], [5], [10], [4, 2, 2], [10, 2, 5], [10, 5, 2].

Note:

  • 1 <= A.length <= 1000.
  • 2 <= A[i] <= 10 ^ 9.

两个难点,定义dp表达式跟推导状态转移方程。怎么简单怎么来呗,我们用一个一维dp数组,其中dp[i]表示值为i的结点做根结点时,能够形成的符合题意的二叉树的个数。这样我们将数组A中每个结点的dp值都累加起来就是最终的结果了。好了,有了定义式,接下来就是最大的难点了,推导状态转移方程。题目中的要求是根结点的值必须是左右子结点值的乘积,那么根结点的dp值一定是跟左右子结点的dp值有关的,是要加上左右子结点的dp值的乘积的,为啥是乘呢,比如有两个球,一个有2种颜色,另一个有3种颜色,问两个球放一起总共能有多少种不同的颜色组合,当然是相乘啦。每个结点的dp值初始化为1,因为就算是当个光杆司令的叶结点,也是符合题意的,所以至少是1。然后就要找其左右两个子结点了,怎么找,有点像 Two Sum 的感觉,先确定一个,然后在HashMap中快速定位另一个,想到了这一层的话,我们的dp定义式就需要做个小修改,之前说的是用一个一维dp数组,现在看来就不太合适了,因为我们需要快速查找某个值,所以这里我们用一个HashMap来定义dp。好,继续,既然要先确定一个结点,由于都是大于1的正数,那么这个结点肯定要比根结点值小,为了遍历方便,我们想把小的放前面,那么我们就需要给数组A排个序,这样就可以遍历之前较小的数字了,那么如何快速定位另一个子结点呢,我们只要用根结点值对遍历值取余,若为0,说明可以整除,然后再在HashMap中查找这个商是否存在,在的话,说明存在这样的两个结点,其结点值之积等于结点A[i],然后我们将这两个结点值之积加到dp[A[i]]中即可,注意还要对超大数取余,防止溢出。最后当所有结点的dp值都更新完成了,将其和算出来返回即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int numFactoredBinaryTrees(vector<int>& A) {
long res = 0, M = 1e9 + 7;
unordered_map<int, long> dp;
sort(A.begin(), A.end());
for (int i = 0; i < A.size(); ++i) {
dp[A[i]] = 1;
for (int j = 0; j < i; ++j) {
if (A[i] % A[j] == 0 && dp.count(A[i] / A[j])) {
dp[A[i]] = (dp[A[i]] + dp[A[j]] * dp[A[i] / A[j]]) % M;
}
}
}
for (auto a : dp) res = (res + a.second) % M;
return res;
}
};

Leetcode824. Goat Latin

A sentence S is given, composed of words separated by spaces. Each word consists of lowercase and uppercase letters only.

We would like to convert the sentence to “Goat Latin” (a made-up language similar to Pig Latin.)

The rules of Goat Latin are as follows:

  • If a word begins with a vowel (a, e, i, o, or u), append “ma” to the end of the word.
    For example, the word ‘apple’ becomes ‘applema’.
  • If a word begins with a consonant (i.e. not a vowel), remove the first letter and append it to the end, then add “ma”.
    For example, the word “goat” becomes “oatgma”.
  • Add one letter ‘a’ to the end of each word per its word index in the sentence, starting with 1.
    For example, the first word gets “a” added to the end, the second word gets “aa” added to the end and so on.
    Return the final sentence representing the conversion from S to Goat Latin.

Example 1:

1
2
Input: "I speak Goat Latin"
Output: "Imaa peaksmaaa oatGmaaaa atinLmaaaaa"

Example 2:

1
2
Input: "The quick brown fox jumped over the lazy dog"
Output: "heTmaa uickqmaaa rownbmaaaa oxfmaaaaa umpedjmaaaaaa overmaaaaaaa hetmaaaaaaaa azylmaaaaaaaaa ogdmaaaaaaaaaa"

Notes:

  1. S contains only uppercase, lowercase and spaces. Exactly one space between each word.
  2. 1 <= S.length <= 150.
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
class Solution {
public:
bool isvowel(char c) {
if(c == 'A' || c == 'a' || c == 'E' || c == 'e' || c == 'I' || c == 'i' || c == 'O' || c == 'o' || c == 'U' || c == 'u')
return true;
return false;
}
string toGoatLatin(string S) {
vector<string> words;
string result, temp;
int length = S.length();
for(int i = 0; i < length; i ++) {
temp = "";
while(S[i] != ' ' && i < length) {
temp += S[i++];
}
words.push_back(temp);
}
for(int i = 0; i < words.size(); i ++) {
if(isvowel(words[i][0]))
result = result + words[i] + "ma";
else
result = result + words[i].substr(1) + words[i][0] + "ma";
for(int j = 0; j < i + 1; j ++)
result += 'a';
result += " ";
}
return result.substr(0, result.length()-1);
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
string toGoatLatin(string S) {
stringstream ss(S);
string word;
string res;
unordered_set<char> vowels{'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'};
int count = 0;
while (ss >> word) {
count += 1;
if (!vowels.count(word[0])) {
std::reverse(word.begin(), word.end());
std::reverse(word.begin(), word.end() - 1);
}
res += word + "ma" + string(count, 'a') + " ";
}
auto space_pos = res.find_last_of(' ');
res = res.substr(0, space_pos);
return res;
}
};

Leetcode825. Friends Of Appropriate Ages

Some people will make friend requests. The list of their ages is given and ages[i] is the age of the ith person.

Person A will NOT friend request person B (B != A) if any of the following conditions are true:

  • age[B] <= 0.5 * age[A] + 7
  • age[B] > age[A]
  • age[B] > 100 && age[A] < 100

Otherwise, A will friend request B.

Note that if A requests B, B does not necessarily request A. Also, people will not friend request themselves.

How many total friend requests are made?

Example 1:

1
2
3
Input: [16,16]
Output: 2
Explanation: 2 people friend request each other.

Example 2:

1
2
3
Input: [16,17,18]
Output: 2
Explanation: Friend requests are made 17 -> 16, 18 -> 17.

Example 3:

1
2
3
Input: [20,30,100,110,120]
Output:
Explanation: Friend requests are made 110 -> 100, 120 -> 110, 120 -> 100.

Notes:

  • 1 <= ages.length <= 20000.
  • 1 <= ages[i] <= 120.

这道题是关于好友申请的,说是若A想要加B的好友,下面三个条件一个都不能满足才行:

  1. B的年龄小于等于A的年龄的一半加7。
  2. B的年龄大于A的年龄。
  3. B大于100岁,且A小于100岁。

实际上如果你仔细看条件3,B要是大于100岁,A小于100岁,那么B一定大于A,这就跟条件2重复了。那么由于只能给比自己小的人发送好友请求,那么博主就想到我们可以献给所有人拍个序,然后从后往前遍历,对于每个遍历到的人,再遍历所有比他小的人,这样第二个条件就满足了,前面说了,第三个条件可以不用管了,那么只要看满足第一个条件就可以了,还有要注意的,假如两个人年龄相同,那么满足了前两个条件后,其实是可以互粉的,所以要额外的加1,这样才不会漏掉任何一个好友申请,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int numFriendRequests(vector<int>& ages) {
int res = 0, n = ages.size();
sort(ages.begin(), ages.end());
for (int i = n - 1; i >= 1; --i) {
for (int j = i - 1; j >= 0; --j) {
if (ages[j] <= 0.5 * ages[i] + 7) continue;
if (ages[i] == ages[j]) ++res;
++res;
}
}
return res;
}
};

这个方法会超时。

我们可以来优化一下上面的解法,根据上面的分析,其实题目给的这三个条件可以归纳成一个条件,若A想加B的好友,那么B的年龄必须在 (A*0.5+7, A] 这个范围内,由于区间要有意义的话,A0.5+7 < A 必须成立,解出 A > 14,那么A最小就只能取15了。意思说你不能加小于15岁的好友(青少年成长保护???)。我们的优化思路是对于每一个年龄,我们都只要求出上面那个区间范围内的个数,就是符合题意的。那么既然是求区域和,建立累加数组就是一个很好的选择了,首先我们先建立一个统计数组numInAge,范围是[0, 120],用来统计在各个年龄点上有多少人,然后再建立累加和数组sumInAge。这个都建立好了以后,我们就可以开始遍历,由于之前说了不能加小于15的好友,所以我们从15开始遍历,如果某个年龄点没有人,直接跳过。然后就是统计出 (A*0.5+7, A] 这个范围内有多少人,可以通过累计和数组来快速求的,由于当前时间点的人可以跟这个区间内的所有发好友申请,而当前时间点可能还有多人,所以二者相乘,但由于我们但区间里还包括但当前年龄点本身,所以还要减去当前年龄点上的人数,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int numFriendRequests(vector<int>& ages) {
int res = 0;
vector<int> numInAge(121), sumInAge(121);
for (int age : ages) ++numInAge[age];
for (int i = 1; i <= 120; ++i) {
sumInAge[i] = numInAge[i] + sumInAge[i - 1];
}
for (int i = 15; i <= 120; ++i) {
if (numInAge[i] == 0) continue;
int cnt = sumInAge[i] - sumInAge[i * 0.5 + 7];
res += cnt * numInAge[i] - numInAge[i];
}
return res;
}
};

Leetcode826. Most Profit Assigning Work

You have n jobs and m workers. You are given three arrays: difficulty, profit, and worker where:

difficulty[i]and profit[i] are the difficulty and the profit of the ith job, and worker[j] is the ability of jth worker (i.e., the jth worker can only complete a job with difficulty at most worker[j]).

Every worker can be assigned at most one job, but one job can be completed multiple times.

For example, if three workers attempt the same job that pays $1, then the total profit will be $3. If a worker cannot complete any job, their profit is $0. Return the maximum profit we can achieve after assigning the workers to the jobs.

Example 1:

1
2
3
Input: difficulty = [2,4,6,8,10], profit = [10,20,30,40,50], worker = [4,5,6,7]
Output: 100
Explanation: Workers are assigned jobs of difficulty [4,4,6,6] and they get a profit of [20,20,30,30] separately.

Example 2:

1
2
Input: difficulty = [85,47,57], profit = [24,66,99], worker = [40,25,25]
Output: 0

贪心的策略是给每个工人计算在他的能力范围内,他能获得的最大收益,把这样的工作分配给他。

做的方法是先把困难程度和收益压缩排序,然后对工人排序,再对每个工人,通过从左到右的遍历确定其能获得收益最大值。由于工作和工人都已经排好序了,每次只需要从上次停止的位置继续即可,因此各自只需要遍历一次。

你可能会想到,每个工作的收益和其困难程度可能不是正相关的,可能存在某个工作难度小,但是收益反而很大,这种怎么处理呢?其实这也就是这个算法妙的地方,curMax并不是在每个工人查找其满足条件的工作时初始化的,而是在一开始就初始化了,这样一直保持的是所有的工作难度小于工人能力的工作中,能获得的收益最大值。

也就是说在查找满足条件的工作的时候,curMax有可能不更新,其保存的是目前为止的最大。res加的也就是在满足工人能力情况下的最大收益了。

时间复杂度是O(M+N),空间复杂度是O(MN)。

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
class Solution {
public:
int maxProfitAssignment(vector<int>& difficulty, vector<int>& profit, vector<int>& worker) {
int res = 0, curmax = 0, curwork = 0;
int len = difficulty.size();
vector<pair<int, int>> v;
for (int i = 0; i < len; i ++)
v.push_back(make_pair(difficulty[i], profit[i]));
sort(v.begin(), v.end(), [](pair<int, int>& a,pair<int, int>& b){ return a.first < b.first; });
sort(worker.begin(), worker.end());

int j = 0;
for (int i = 0; i < worker.size(); i ++) {
while (j < len) {
if (worker[i] < v[j].first)
break;
curmax = max(curmax, v[j].second);
j ++;

}
res += curmax;
}
return res;
}
};

Leetcode830. Positions of Large Groups

In a string S of lowercase letters, these letters form consecutive groups of the same character. For example, a string like S = “abbxxxxzyy” has the groups “a”, “bb”, “xxxx”, “z” and “yy”.

Call a group large if it has 3 or more characters. We would like the starting and ending positions of every large group. The final answer should be in lexicographic order.

Example 1:

1
2
3
Input: "abbxxxxzzy"
Output: [[3,6]]
Explanation: "xxxx" is the single large group with starting 3 and ending positions 6.

Example 2:

1
2
3
Input: "abc"
Output: []
Explanation: We have "a","b" and "c" but no large group.

找到长度大于3的子串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
vector<vector<int>> largeGroupPositions(string S) {
vector<vector<int>> res;
int len = S.length();
int begin, end;
for(int i = 0; i < len; i ++) {
begin = i;
while(i < len-1 && S[i] == S[i+1])
i ++;
if(i - begin + 1 >= 3)
res.push_back({begin, i});
}
return res;
}
};

Leetcode831. Masking Personal Information

We are given a personal information string S, which may represent either an email address or a phone number.

We would like to mask this personal information according to the following rules:

  • Email address:
    • We define a name to be a string of length ≥ 2consisting of only lowercase letters a-z or uppercase letters A-Z.
    • An email address starts with a name, followed by the symbol ‘@’, followed by a name, followed by the dot ‘.’ and followed by a name.
    • All email addresses are guaranteed to be valid and in the format of “name1@name2.name3“.
    • To mask an email, all names must be converted to lowercase and all letters between the first and last letter of the first name must be replaced by 5 asterisks ‘*’.
  • Phone number:
    • A phone number is a string consisting of only the digits 0-9or the characters from the set {‘+’, ‘-‘, ‘(‘, ‘)’, ‘ ‘}. You may assume a phone number contains 10 to 13 digits.
    • The last 10 digits make up the local number, while the digits before those make up the country code. Note that the country code is optional. We want to expose only the last 4 digits and mask all other digits.
    • The local number should be formatted and masked as ***-***-1111, where 1 represents the exposed digits.
    • To mask a phone number with country code like +111 111 111 1111, we write it in the form +***-***-***-1111. The ‘+’ sign and the first ‘-‘ sign before the local number should only exist if there is a country code. For example, a 12 digit phone number mask should start with “+**-“.

Note that extraneous characters like “(“, “)”, “ “, as well as extra dashes or plus signs not part of the above formatting scheme should be removed.

Return the correct “mask” of the information provided.

Example 1:

1
2
3
4
5
Input: "LeetCode@LeetCode.com"
Output: "l*****e@leetcode.com"
Explanation: All names are converted to lowercase, and the letters between the
first and last letter of the first name is replaced by 5 asterisks.
Therefore, "leetcode" -> "l*****e".

Example 2:

1
2
3
4
Input: "AB@qq.com"
Output: "a*****b@qq.com"
Explanation: There must be 5 asterisks between the first and last letter
of the first name "ab". Therefore, "ab" -> "a*****b".

Example 3:

1
2
3
Input: "1(234)567-890"
Output: "***-***-7890"
Explanation: 10 digits in the phone number, which means all digits make up the local number.

Example 4:

1
2
3
Input: "86-(10)12345678"
Output: "+**-***-***-5678"
Explanation: 12 digits, 2 digits for country code and 10 digits for local number.

这道题让我们给个人信息打码。这里对邮箱和电话分别进行了不同的打码方式,对于邮箱来说,只保留用户名的首尾两个字符,然后中间固定加上五个星号,还有就是所有的字母转小写。对于电话来说,有两种方式,有和没有国家代号,有的话其前面必须有加号,跟后面的区域号用短杠隔开,后面的10个电话号分为三组,个数分别为3,3,4。每组之间还是用短杠隔开,除了最后一组的数字保留之外,其他的都用星号代替。弄清楚了题意,就开始解题吧。既然是字符串的题目,那么肯定要遍历这个字符串了,我们关心的主要是数字和字母,所以要用个变量str来保存遍历到的数字和字母,所以判断,如果是数字或者小写字母的话,直接加入str中,若是大写字母的话,转成小写字母再加入str,如果遇到了 ‘@’ 号,那么表示当前处理的是邮箱,而此时的用户已经全部读入str了,那直接就取出首尾字符,然后中间加五个星号,并再加上 ‘@’ 号存入结果res中,并把str清空。若遇到了点,说明此时是邮箱的后半段,因为题目中限制了用户名中不会有点存在,那么我们将str和点一起加入结果res,并将str清空。当遍历结束时,若此时结果res不为空,说明我们处理的是邮箱,那么返回结果res加上str,因为str中存的是 “com”,还没有来得及加入结果res。若res为空,说明处理的是电话号码,所有的数字都已经加入了str,由于国家代号可能有也可能没有,所以判断一下存入的数字的个数,如果超过10个了,说明有国家代号,那么将超过的部分取出来拼装一下,前面加 ‘+’ 号,后面加短杠。然后再将10位号码的前六位的星号格式加上,并加上最后四个数字即可,参见代码如下:

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
class Solution {
public:

char tolower(char c) {
if ('A' <= c && c <= 'Z')
return c + ('a'-'A');
else
return c;
}

string maskPII(string s) {
string t, res = "";
bool is_email = false;
int _size = 0;
int aa = 0, bb = -1;
for (int i = 0; i < s.length(); i ++) {
if ('@' == s[i]) {
is_email = true;
aa = i-1;
}
if ('-' == s[i])
_size ++;
if (_size == 1 && bb == -1 && '-' == s[i])
bb = i;
if ('+' == s[i] || '-' == s[i] || '(' == s[i] || ')' == s[i] || ' ' == s[i])
continue;
t.push_back(tolower(s[i]));
}

if (is_email) {
for (int i = 0; i < t.length(); i ++) {
if (i == 0) {
res += t[i];
res += "*****";
}
else if (i >= aa)
res += t[i];
}
}
else {
int length = t.length();
if (length > 10) {
res += "+";
res += string(length-10, '*');
res += "-";
}
res += "***-***-";
for (int i = length-4; i < length; i ++)
res += t[i];
}
return res;
}
};

Leetcode832. Flipping an Image

Given a binary matrix A, we want to flip the image horizontally, then invert it, and return the resulting image.

To flip an image horizontally means that each row of the image is reversed. For example, flipping [1, 1, 0] horizontally results in [0, 1, 1].

To invert an image means that each 0 is replaced by 1, and each 1 is replaced by 0. For example, inverting [0, 1, 1] results in [1, 0, 0].

Example 1:

1
2
3
4
Input: [[1,1,0],[1,0,1],[0,0,0]]
Output: [[1,0,0],[0,1,0],[1,1,1]]
Explanation: First reverse each row: [[0,1,1],[1,0,1],[0,0,0]].
Then, invert the image: [[1,0,0],[0,1,0],[1,1,1]]

Example 2:

1
2
3
4
Input: [[1,1,0,0],[1,0,0,1],[0,1,1,1],[1,0,1,0]]
Output: [[1,1,0,0],[0,1,1,0],[0,0,0,1],[1,0,1,0]]
Explanation: First reverse each row: [[0,0,1,1],[1,0,0,1],[1,1,1,0],[0,1,0,1]].
Then invert the image: [[1,1,0,0],[0,1,1,0],[0,0,0,1],[1,0,1,0]]

没啥好说的,普通的矩阵操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
vector<vector<int> > flipAndInvertImage(vector<vector<int> >& A) {
for(int i = 0; i < A.size(); i ++){
for(int j = 0, k = A[i].size()-1; j < A[i].size() / 2; j ++, k --){
int temp = A[i][j];
A[i][j] = A[i][k];
A[i][k] = temp;
}
for(int j = 0, k = A[i].size()-1; j < A[i].size(); j ++, k --){
A[i][j] = 1 - A[i][j];
}
}
return A;
}
};

Leetcode833. Find And Replace in String

To some string S, we will perform some replacement operations that replace groups of letters with new ones (not necessarily the same size).

Each replacement operation has 3 parameters: a starting index i, a source word x and a target word y. The rule is that if x starts at position i in the original string S, then we will replace that occurrence of x with y. If not, we do nothing.

For example, if we have S = “abcd” and we have some replacement operation i = 2, x = “cd”, y = “ffff”, then because “cd” starts at position 2 in the original string S, we will replace it with “ffff”.

Using another example on S = “abcd”, if we have both the replacement operation i = 0, x = “ab”, y = “eee”, as well as another replacement operation i = 2, x = “ec”, y = “ffff”, this second operation does nothing because in the original string S[2] = ‘c’, which doesn’t match x[0] = ‘e’.

All these operations occur simultaneously. It’s guaranteed that there won’t be any overlap in replacement: for example, S = “abc”, indexes = [0, 1], sources = [“ab”,”bc”] is not a valid test case.

Example 1:

1
2
3
4
Input: S = "abcd", indexes = [0,2], sources = ["a","cd"], targets = ["eee","ffff"]
Output: "eeebffff"
Explanation: "a" starts at index 0 in S, so it's replaced by "eee".
"cd" starts at index 2 in S, so it's replaced by "ffff".

Example 2:

1
2
3
4
Input: S = "abcd", indexes = [0,2], sources = ["ab","ec"], targets = ["eee","ffff"]
Output: "eeecd"
Explanation: "ab" starts at index 0 in S, so it's replaced by "eee".
"ec" doesn't starts at index 2 in the original S, so we do nothing.

Notes:

  • 0 <= indexes.length = sources.length = targets.length <= 100
  • 0 < indexes[i] < S.length <= 1000
  • All characters in given inputs are lowercase letters.

这道题给了我们一个字符串S,并给了一个坐标数组,还有一个源字符串数组,还有目标字符串数组,意思是若某个坐标位置起,源字符串数组中对应位置的字符串出现了,将其替换为目标字符串。此题的核心操作就两个,查找和替换,需要注意的是,由于替换操作会改变原字符串,但是我们查找始终是基于最初始的S。

首先我们需要给indexes数组排个序,因为可能不是有序的,但是却不能直接排序,这样会丢失和sources,targets数组的对应关系,这很麻烦。所以我们新建了一个保存pair对儿的数组,将indexes数组中的数字跟其位置坐标组成pair对儿,加入新数组v中,然后给这个新数组按从大到小的方式排序。

下面就要开始遍历新数组v了,对于遍历到的pair对儿,取出第一个数字,保存到i,表示S中需要查找的位置,取出第二个数字,然后根据这个位置分别到sources和targets数组中取出源字符串和目标字符串,然后我们在S中的i位置,向后取出和源字符串长度相同的子串,然后比较,若正好和源字符串相等,则将其替换为目标字符串即可,参见代码如下:

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
class Solution {
public:
string findReplaceString(string s, vector<int>& indices, vector<string>& sources, vector<string>& targets) {

vector<pair<int, int>> v;
for (int i = 0; i < indices.size(); ++i) {
v.push_back({indices[i], i});
}
sort(v.begin(), v.end());

int len = s.length();
string res = "";
int p1 = 0, p2 = 0, i = 0;
while(i < len && p1 < v.size()) {
int tmp = i, j = 0;
if (i > indices[v[p1].second])
p1 ++;
else if (i == indices[v[p1].second]) {
int len2 = sources[v[p1].second].length();
while(j < len2 && tmp < len) {
if (sources[v[p1].second][j] != s[tmp])
break;
j ++; tmp++;
}
if (j == len2) {
res += targets[v[p1].second];
p1 ++;
i += len2;
}
else
res += s[i++];
}
else if (i < len)
res += s[i++];

}
while(i < len)
res += s[i++];
return res;
}
};

Leetcode835. Image Overlap

You are given two images, img1 and img2, represented as binary, square matrices of size n x n. A binary matrix has only 0s and 1s as values.

We translate one image however we choose by sliding all the 1 bits left, right, up, and/or down any number of units. We then place it on top of the other image. We can then calculate the overlap by counting the number of positions that have a 1 in both images.

Note also that a translation does not include any kind of rotation. Any 1 bits that are translated outside of the matrix borders are erased.

Return the largest possible overlap.

Example 2:

1
2
Input: img1 = [[1]], img2 = [[1]]
Output: 1

Example 3:

1
2
Input: img1 = [[0]], img2 = [[0]]
Output: 0

经过观察

(0,0)->(1,1)->(0,0)-(1,1)=(-1,-1)

(0,1)->(1,2)->(0,1)-(1,2)=(-1,-1)

(1,1)->(2,2)->(1,1)-(2,2)=(-1,-1)

从A对应到B是把整个矩阵的坐标x轴-1, y轴-1;

变相就是在求所有A矩阵1的点到B矩阵1的点的X轴和Y轴距离;

所有坐标的取值范围在(-N—N)之间;

实际上就是把一个一维求距离的easy的题换成了二维的题目;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
int largestOverlap(vector<vector<int>>& A, vector<vector<int>>& B) {
int res = 0, n = A.size();
vector<vector<int>> m_map(2*n+1, vector<int>(2*n+1, 0));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (A[i][j] == 1) {
for (int ii = 0; ii < n; ++ii) {
for (int jj = 0; jj < n; ++jj) {
if (B[ii][jj] == 1)
++m_map[i-ii+n][j-jj+n];
}
}

}
}
}
for (int i = 0; i < m_map.size(); i ++)
for (int j = 0; j < m_map[0].size(); j++)
res = max(m_map[i][j], res);
return res;
}
};

Leetcode836. Rectangle Overlap

A rectangle is represented as a list [x1, y1, x2, y2], where (x1, y1) are the coordinates of its bottom-left corner, and (x2, y2) are the coordinates of its top-right corner.

Two rectangles overlap if the area of their intersection is positive. To be clear, two rectangles that only touch at the corner or edges do not overlap.

Given two (axis-aligned) rectangles, return whether they overlap.

Example 1:

1
2
Input: rec1 = [0,0,2,2], rec2 = [1,1,3,3]
Output: true

Example 2:

1
2
Input: rec1 = [0,0,1,1], rec2 = [1,0,2,1]
Output: false

这道题让我们求两个矩形是否是重叠,矩形的表示方法是用两个点,左下和右上点来定位的。下面的讲解是参见网友大神jayesch的帖子来的,首先,返璞归真,在玩 2D 之前,先看下 1D 上是如何运作的。对于两条线段,它们相交的话可以是如下情况:

1
2
3
4
          x3             x4
|--------------|
|--------------|
x1 x2

我们可以直观的看出一些关系: x1 < x3 < x2 && x3 < x2 < x4,可以稍微化简一下:x1 < x4 && x3 < x2。就算是调换个位置:

1
2
3
4
          x1             x2
|--------------|
|--------------|
x3 x4

还是能得到同样的关系:x3 < x2 && x1 < x4。好,下面我们进军 2D 的世界,实际上 2D 的重叠就是两个方向都同时满足 1D 的重叠条件即可。由于题目中说明了两个矩形的重合面积为正才算 overlap,就是说挨着边的不算重叠,那么两个矩形重叠主要有这四种情况:

1)两个矩形在矩形1的右上角重叠:

1
2
3
4
5
6
7
          ____________________x4,y4
| |
_______|______x2,y2 |
| |______|____________|
| x3,y3 |
|______________|
x1,y1

满足的条件为:x1 < x4 && x3 < x2 && y1 < y4 && y3 < y2

2)两个矩形在矩形1的左上角重叠:

1
2
3
4
5
6
7
   ___________________  x4,y4
| |
| _______|____________x2,y2
|___________|_______| |
x3,y3 | |
|___________________|
x1,y1

满足的条件为:x3 < x2 && x1 < x4 && y1 < y4 && y3 < y2

3)两个矩形在矩形1的左下角重叠:

1
2
3
4
5
6
7
          ____________________x2,y2
| |
_______|______x4,y4 |
| |______|____________|
| x1,y1 |
|______________|
x3,y3

满足的条件为:x3 < x2 && x1 < x4 && y3 < y2 && y1 < y4

4)两个矩形在矩形1的右下角重叠:

1
2
3
4
5
6
7
   ___________________  x2,y2
| |
| _______|____________x4,y4
|___________|_______| |
x1,y1 | |
|___________________|
x3,y3

满足的条件为:x1 < x4 && x3 < x2 && y3 < y2 && y1 < y4

仔细观察可以发现,上面四种情况的满足条件其实都是相同的,只不过顺序调换了位置,所以我们只要一行就可以解决问题了,碉堡了。。。

1
2
3
4
class Solution {
public:
bool isRectangleOverlap(vector<int>& rec1, vector<int>& rec2) {
return rec1[0] < rec2[2] && rec2[0] < rec1[2] && rec1[1] < rec2[3] && rec2[1] < rec1[3];

Leetcode838. Push Dominoes

There are N dominoes in a line, and we place each domino vertically upright.

In the beginning, we simultaneously push some of the dominoes either to the left or to the right.

After each second, each domino that is falling to the left pushes the adjacent domino on the left.

Similarly, the dominoes falling to the right push their adjacent dominoes standing on the right.

When a vertical domino has dominoes falling on it from both sides, it stays still due to the balance of the forces.

For the purposes of this question, we will consider that a falling domino expends no additional force to a falling or already fallen domino.

Given a string “S” representing the initial state. S[i] = ‘L’, if the i-th domino has been pushed to the left; S[i] = ‘R’, if the i-th domino has been pushed to the right; S[i] = ‘.’, if the i-th domino has not been pushed.

Return a string representing the final state.

Example 1:

1
2
Input: ".L.R...LR..L.."
Output: "LL.RR.LLRRLL.."

Example 2:

1
2
3
Input: "RR.L"
Output: "RR.L"
Explanation: The first domino expends no additional force on the second domino.

Note:

  • 0 <= N <= 10^5
  • String dominoes contains only ‘L‘, ‘R’ and ‘.’

这道题给我们摆好了一个多米诺骨牌阵列,但是与一般的玩法不同的是,这里没有从一头开始推,而是在很多不同的位置分别往两个方向推,结果是骨牌各自向不同的方向倒下了,而且有的骨牌由于左右两边受力均等,依然屹立不倒,这样的话骨牌就很难受了,能不能让哥安心的倒下去?!生而为骨牌,总是要倒下去啊,就像漫天飞舞的樱花,秒速五厘米的落下,回到最终归宿泥土里。喂,不要给骨牌强行加戏好么!~ 某个位置的骨牌会不会倒,并且朝哪个方向倒,是由左右两边受到的力的大小决定的,那么可以分为下列四种情况:

1)R….R -> RRRRRR

这是当两个向右推的操作连在一起时,那么中间的骨牌毫无悬念的都要向右边倒去。

2)L….L -> LLLLLL

同理,

当两个向左推的操作连在一起时,那么中间的骨牌毫无悬念的都要向左边倒去。

3)L….R -> L….R

当左边界的骨牌向左推,右边界的骨牌向右推,那么中间的骨牌不会收到力,所以依然保持坚挺。

4)R….L -> RRRLLL or R…..L -> RRR.LLL

当左边界的骨牌向右推,右边界的骨牌向左推时,就要看中间的骨牌个数了,若是偶数,那么对半分,若是奇数,那么最中间的骨牌保持站立,其余的对半分。

由于上述四种情况包含了所有的情况,所以我们的目标就是在字符串中找出中间是‘点’的小区间,为了便于我们一次遍历就处理完,我们在dominoes字符串左边加个L,右边加个R,这并不会影响骨牌倒下的情况。我们使用双指针来遍历,其中i初始化为0,j初始化为1,当j指向‘点’时,我们就跳过,目标是i指向小区间的左边界,j指向右边界,然后用 j-i-1 算出中间‘点’的个数,为0表示中间没有点。若此时 i>0,则将左边界加入结果res中。若左右边界相同,那么中间的点都填成左边界,这是上述的情况一和二;若左边界是L,右边界是R,则是上述的情况三,中间还是保持点不变;若左边界是R,右边界是L,则是情况四,那么先加 mid/2 个R,再加 mid%2 个点,最后加 mid/2 个L即可。然后i更新为j,继续循环即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
string pushDominoes(string dominoes) {
string res = "";
dominoes = "L" + dominoes + "R";
for (int i = 0, j = 1; j < dominoes.size(); ++j) {
if (dominoes[j] == '.') continue;
int mid = j - i - 1;
if (i > 0) res += dominoes[i];
if (dominoes[i] == dominoes[j]) res += string(mid, dominoes[i]);
else if (dominoes[i] == 'L' && dominoes[j] == 'R') res += string(mid, '.');
else res += string(mid / 2, 'R') + string(mid % 2, '.') + string(mid / 2, 'L');
i = j;
}
return res;
}
};

下面这种解法遍历了两次字符串,第一次遍历是先把R后面的点全变成R,同时累加一个cnt数组,其中cnt[i]表示在dominoes数组中i位置时R连续出现的个数,那么拿题目中的例子1来说,第一次遍历之后,原dominoes数组,修改后的dominoes数组,以及cnt数组分别为:

1
2
3
.L.R...LR..L..
.L.RRRRLRRRL..
00001230012000

我们可以发现cnt数字记录的是R连续出现的个数,第一次遍历只模拟了所有往右推倒的情况,很明显不是最终答案,因为还需要往左推,那么就要把某些点变成L,已经把某些R变成点或者L,这时我们的cnt数组就非常重要,因为它相当于记录了往右推的force的大小。第二次遍历是从右往左,我们找所有L前面的位置,若其为点,则直接变为L。若其为R,那么也有可能变L,此时就要计算往左的force,通过 cnt[i+1] + 1 获得,然后跟往右的force比较,若此位置往右的force大,说明当前骨牌应该往左倒,更新此时cnt[i]为往左的force。若此时左右force相等了,说明当前骨牌不会向任意一遍倒,改为点即可,最终修改后的dominoes数组和cnt数组分别为:

1
2
LL.RR.LLRRLL..
10001210011000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
string pushDominoes(string dominoes) {
int n = dominoes.size();
vector<int> cnt(n);
for (int i = 1; i < n; ++i) {
if (dominoes[i - 1] == 'R' && dominoes[i] == '.') {
dominoes[i] = 'R';
cnt[i] = cnt[i - 1] + 1;
}
}
for (int i = n - 2, cur = 0; i >= 0; --i) {
if (dominoes[i + 1] != 'L') continue;
cur = cnt[i + 1] + 1;
if (dominoes[i] == '.' || cnt[i] > cur) {
dominoes[i] = 'L';
cnt[i] = cur;
} else if (dominoes[i] == 'R' && cnt[i] == cur) {
dominoes[i] = '.';
}
}
return dominoes;
}
};

Leetcode840. Magic Squares In Grid

A 3 x 3 magic square is a 3 x 3 grid filled with distinct numbers from 1 to 9 such that each row, column, and both diagonals all have the same sum.

Given an grid of integers, how many 3 x 3 “magic square” subgrids are there? (Each subgrid is contiguous).

Example 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Input: [[4,3,8,4],
[9,5,1,9],
[2,7,6,2]]
Output: 1
Explanation:
The following subgrid is a 3 x 3 magic square:
438
951
276

while this one is not:
384
519
762

In total, there is only one magic square inside the given grid.

幻方的特点是,全体数和=3X幻和,幻和=3×中心数。在基本三阶幻方中,幻和=1+2+…+9=45/3=15,所以中心数=5。因此只要从[1,1]开始判断中间数为5,再进一步判断是否为幻方。进一步判断:首先可使用二进制数来判断是否该矩阵是由1~9的组成。再判断通过中心的4组对角值和为10,&& 第一行和第一列和为15。

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
class Solution {
public:
bool isMagic(int i, int j, vector<vector<int>>& grid){
int chknum = 0;
for(int a=i-1; a<=i+1; a++){
for(int b=j-1; b<=j+1; b++){
if(grid[a][b]>0 && grid[a][b]<10)
chknum |= 1<<(grid[a][b]-1);
}
}
if(chknum!=0b111111111) return false;
if(grid[i-1][j-1]+grid[i+1][j+1]==10 && grid[i][j-1]+grid[i][j+1]==10 &&
grid[i-1][j]+grid[i+1][j]==10 && grid[i-1][j+1]+grid[i+1][j-1]==10 &&
grid[i-1][j-1]+grid[i-1][j]+grid[i-1][j+1]==15 &&
grid[i-1][j-1]+grid[i][j-1]+grid[i+1][j-1]==15)
return true;
else return false;
}
int numMagicSquaresInside(vector<vector<int>>& grid) {
int N=grid.size();
int res=0;
for(int i=1; i<N-1; i++){
for(int j=1; j<N-1; j++){
if(grid[i][j]==5 && isMagic(i,j,grid)){
res++;
j++;
}
}
}
return res;
}
};

Leetcode841. Keys and Rooms

There are N rooms and you start in room 0. Each room has a distinct number in 0, 1, 2, …, N-1, and each room may have some keys to access the next room.

Formally, each room i has a list of keys rooms[i], and each key rooms[i][j] is an integer in [0, 1, …, N-1] where N = rooms.length. A key rooms[i][j] = v opens the room with number v.

Initially, all the rooms start locked (except for room 0).

You can walk back and forth between rooms freely.

Return true if and only if you can enter every room.

Example 1:

1
2
3
4
5
6
7
Input: [[1],[2],[3],[]]
Output: true
Explanation:
We start in room 0, and pick up key 1.
We then go to room 1, and pick up key 2.
We then go to room 2, and pick up key 3.
We then go to room 3. Since we were able to go to every room, we return true.

Example 2:

1
2
3
Input: [[1,3],[3,0,1],[2],[0]]
Output: false
Explanation: We can't enter the room with number 2.

Note:

  • 1 <= rooms.length <= 1000
  • 0 <= rooms[i].length <= 1000
  • The number of keys in all rooms combined is at most 3000.

这道题给了我们一些房间,房间里有一些钥匙,用钥匙可以打开对应的房间,说是起始时在房间0,问最终是否可以打开所有的房间。这不由得让博主想起了惊悚片《万能钥匙》,还真是头皮发麻啊。赶紧扯回来,这是一道典型的有向图的遍历的题,邻接链表都已经建立好了,这里直接遍历就好了,这里先用 BFS 来遍历。使用一个 HashSet 来记录访问过的房间,先把0放进去,然后使用 queue 来辅助遍历,同样将0放入。之后进行典型的 BFS 遍历,取出队首的房间,然后遍历其中的所有钥匙,若该钥匙对应的房间已经遍历过了,直接跳过,否则就将钥匙加入 HashSet。此时看若 HashSet 中的钥匙数已经等于房间总数了,直接返回 true,因为这表示所有房间已经访问过了,否则就将钥匙加入队列继续遍历。最后遍历结束后,就看 HashSet 中的钥匙数是否和房间总数相等即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
bool canVisitAllRooms(vector<vector<int>>& rooms) {
unordered_set<int> visited{{0}};
queue<int> q{{0}};
while (!q.empty()) {
int t = q.front(); q.pop();
for (int key : rooms[t]) {
if (visited.count(key)) continue;
visited.insert(key);
if (visited.size() == rooms.size()) return true;
q.push(key);
}
}
return visited.size() == rooms.size();
}
};

我自己的:

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
class Solution {
public:
bool canVisitAllRooms(vector<vector<int>>& rooms) {
int len = rooms.size();
vector<int> visited(len, 0);

queue<int> q;
q.push(0);

while(!q.empty()) {
int t = q.front();
q.pop();
visited[t] = true;
for (int i = 0; i < rooms[t].size(); i ++) {
if (visited[rooms[t][i]])
continue;
q.push(rooms[t][i]);
}
}
for (int i = 0; i < len; i ++)
if (!visited[i])
return false;
return true;
}
};

Leetcode844. Backspace String Compare

Given two strings S and T, return if they are equal when both are typed into empty text editors. # means a backspace character.

Note that after backspacing an empty text, the text will continue empty.

Example 1:

1
2
3
Input: S = "ab#c", T = "ad#c"
Output: true
Explanation: Both S and T become "ac".

Example 2:

1
2
3
Input: S = "ab##", T = "c#d#"
Output: true
Explanation: Both S and T become "".

Example 3:

1
2
3
Input: S = "a##c", T = "#a#c"
Output: true
Explanation: Both S and T become "c".

Example 4:

1
2
3
Input: S = "a#c", T = "b"
Output: false
Explanation: S becomes "c" while T becomes "b".
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
class Solution {
public:
bool backspaceCompare(string S, string T) {
stack<char> a, b;
for(int i = 0; i < S.length(); i ++) {
if(S[i] != '#')
a.push(S[i]);
else if(!a.empty())
a.pop();
}
string r1 = "";
while(!a.empty()) {
r1 += a.top();
a.pop();
}
for(int i = 0; i < T.length(); i ++) {
if(T[i] != '#')
a.push(T[i]);
else if(!a.empty())
a.pop();
}
string r2 = "";
while(!a.empty()) {
r2 += a.top();
a.pop();
}
return r1 == r2;
}
};

一种更好的做法:

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
class Solution {
public:
bool backspaceCompare(string s, string t) {
int k=0,p=0;
for(int i=0;i<s.size();i++)
{
if(s[i]=='#')
{
k--;
k=max(0,k);
}
else
{
s[k]=s[i];
k++;
}
}
for(int i=0;i<t.size();i++)
{
if(t[i]=='#')
{
p--;
p=max(0,p);
}
else
{
t[p]=t[i];
p++;
}
}
if(k!=p)
return false;
else
{
for(int i=0;i<k;i++)
{
if(s[i]!=t[i])
return false;
}
return true;
}

}
};

Leetcode845. Longest Mountain in Array 数组中最长的山

Let’s call any (contiguous) subarray B (of A) a mountain if the following properties hold:

1
B.length >= 3

There exists some 0 < i < B.length - 1 such that B[0] < B[1] < ... B[i-1] < B[i] > B[i+1] > ... > B[B.length - 1]
(Note that B could be any subarray of A, including the entire array A.)

Given an array A of integers, return the length of the longest mountain.

Return 0 if there is no mountain.

Example 1:

1
2
3
Input: [2,1,4,7,3,2,5]
Output: 5
Explanation: The largest mountain is [1,4,7,3,2] which has length 5.

Example 2:

1
2
3
Input: [2,2,2]
Output: 0
Explanation: There is no mountain.

Note:

  • 0 <= A.length <= 10000
  • 0 <= A[i] <= 10000

这道题给了我们一个数组,然后定义了一种像山一样的子数组,就是先递增再递减的子数组,注意这里是强行递增或者递减的,并不存在相等的情况。那么实际上这道题就是让在数组中寻找一个位置,使得以此位置为终点的递增数组和以此位置为起点的递减数组的长度最大。而以某个位置为起点的递减数组,如果反个方向来看,其实就是就该位置为终点的递增数列,那么既然都是求最长的递增数列,我们可以分别用两个 dp 数组 up 和 down,其中up[i]表示以i位置为终点的最长递增数列的个数,down[i]表示以i位置为起点的最长递减数列的个数,这样我们正向更新up数组,反向更新down数组即可。先反向更新好了down之后,在正向更新up数组的同时,也可以更新结果res,当某个位置的up[i]down[i]均大于0的时候,那么就可以用up[i] + down[i] + 1来更新结果res了,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int longestMountain(vector<int>& A) {
int res = 0, n = A.size();
vector<int> up(n), down(n);
for (int i = n - 2; i >= 0; --i) {
if (A[i] > A[i + 1]) down[i] = down[i + 1] + 1;
}
for (int i = 1; i < n; ++i) {
if (A[i] > A[i - 1]) up[i] = up[i - 1] + 1;
if (up[i] > 0 && down[i] > 0) res = max(res, up[i] + down[i] + 1);
}
return res;
}
};

我们可以对空间进行优化,不必使用两个数组来记录所有位置的信息,而是只用两个变量 up 和 down 来分别记录以当前位置为终点的最长递增数列的长度,和以当前位置为终点的最长递减数列的长度。 我们从 i=1 的位置开始遍历,因为山必须要有上坡和下坡,所以 i=0 的位置永远不可能成为 peak。此时再看,如果当前位置跟前面的位置相等了,那么当前位置的 up 和 down 都要重置为0,从当前位置开始找新的山,和之前的应该断开。或者是当 down 不为0,说明此时是在下坡,如果当前位置大于之前的了,突然变上坡了,那么之前的累计也需要重置为0。然后当前位置再进行判断,若大于前一个位置,则是上坡,up 自增1,若小于前一个位置,是下坡,down 自增1。当 up 和 down 同时为正数,则用 up+down+1 来更新结果 res 即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int longestMountain(vector<int>& A) {
int res = 0, up = 0, down = 0, n = A.size();
for (int i = 1; i < n; ++i) {
if ((down && A[i - 1] < A[i]) || (A[i - 1] == A[i])) {
up = down = 0;
}
if (A[i - 1] < A[i]) ++up;
if (A[i - 1] > A[i]) ++down;
if (up > 0 && down > 0) res = max(res, up + down + 1);
}
return res;
}
};

Leetcode848. Shifting Letters

You are given a string s of lowercase English letters and an integer array shifts of the same length.

Call the shift() of a letter, the next letter in the alphabet, (wrapping around so that ‘z’ becomes ‘a’).

For example, shift(‘a’) = ‘b’, shift(‘t’) = ‘u’, and shift(‘z’) = ‘a’.
Now for each shifts[i] = x, we want to shift the first i + 1 letters of s, x times.

Return the final string after all such shifts to s are applied.

Example 1:

1
2
3
4
5
6
Input: s = "abc", shifts = [3,5,9]
Output: "rpl"
Explanation: We start with "abc".
After shifting the first 1 letters of s by 3, we have "dbc".
After shifting the first 2 letters of s by 5, we have "igc".
After shifting the first 3 letters of s by 9, we have "rpl", the answer.

Example 2:

1
2
Input: s = "aaa", shifts = [1,2,3]
Output: "gfd"

题目大意:给定一组反转数,要求你将字母向前翻转(字母范围a~z)(z反转为a)

思路方法:这题用前缀和处理,一次性求出每个字母需要翻转的次数,否则会超时,当然要防止数据溢出,所以要将过程取余。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
string shiftingLetters(string s, vector<int>& shifts) {
int len = shifts.size();
if (shifts.size() == 0 || s == "")
return s;
vector<int> sums(len, 0);
for (int i = len-2; i >= 0; i --)
shifts[i] = (shifts[i+1]%26 + shifts[i]%26) %26;
for (int i = len-1; i >= 0; i --)
s[i] = 'a' + (s[i] - 'a' + shifts[i]%26) % 26;
return s;
}
};

Leetcode849. Maximize Distance to Closest Person

In a row of seats, 1 represents a person sitting in that seat, and 0 represents that the seat is empty.

There is at least one empty seat, and at least one person sitting.

Alex wants to sit in the seat such that the distance between him and the closest person to him is maximized.

Return that maximum distance to closest person.

Example 1:

1
2
3
4
5
6
Input: [1,0,0,0,1,0,1]
Output: 2
Explanation:
If Alex sits in the second open seat (seats[2]), then the closest person has distance 2.
If Alex sits in any other open seat, the closest person has distance 1.
Thus, the maximum distance to the closest person is 2.

Example 2:

1
2
3
4
5
Input: [1,0,0,0]
Output: 3
Explanation:
If Alex sits in the last seat, the closest person is 3 seats away.
This is the maximum distance possible, so the answer is 3.

Note:

  1. 1 <= seats.length <= 20000
  2. seats contains only 0s or 1s, at least one 0, and at least one 1.

这道题给了我们一个只有0和1且长度为n的数组,代表n个座位,其中0表示空座位,1表示有人座。现在说是爱丽丝想找个位置坐下,但是希望能离最近的人越远越好,这个不难理解,就是想左右两边尽量跟人保持距离,让我们求这个距离最近的人的最大距离。来看题目中的例子1,有三个空位连在一起,那么爱丽丝肯定是坐在中间的位置比较好,这样跟左右两边人的距离都是2。例子2有些特别,当空位连到了末尾的时候,这里可以想像成靠墙,那么靠墙坐肯定离最远啦,所以例子2中爱丽丝坐在最右边的位子上距离左边的人距离最远为3。那么不难发现,爱丽丝肯定需要先找出最大的连续空位长度,若连续空位靠着墙了,那么就直接挨着墙坐,若两边都有人,那么就坐到空位的中间位置。如何能快速知道连续空位的长度呢,只要知道了两边人的位置,相减就是中间连续空位的个数。所以博主最先使用的方法是用一个数组来保存所有1的位置,即有人坐的位置,然后用相邻的两个位置相减,就可以得到连续空位的长度。当然,靠墙这种特殊情况要另外处理一下。当把所有1位置存入数组 nums 之后,开始遍历 nums 数组,第一个人的位置有可能不靠墙,那么他的位置坐标就是他左边靠墙的连续空位个数,直接更新结果 res 即可,因为靠墙连续空位的个数就是离右边人的最远距离。然后对于其他的位置,我们减去前一个人的位置坐标,然后除以2,更新结果 res。还有最右边靠墙的情况也要处理一下,就用 n-1 减去最后一个人的位置坐标,然后更新结果 res 即可,参见代码如下:

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int maxDistToClosest(vector<int>& seats) {
int n = seats.size(), res = 0;
vector<int> nums;
for (int i = 0; i < n; ++i) {
if (seats[i] == 1) nums.push_back(i);
}
for (int i = 0; i < nums.size(); ++i) {
if (i == 0) res = max(res, nums[0]);
else res = max(res, (nums[i] - nums[i - 1]) / 2);
}
if (!nums.empty())
res = max(res, n - 1 - nums.back());
return res;
}
};

我们也可以只用一次遍历,那么就需要在遍历的过程中统计出连续空位的个数,即连续0的个数。那么采用双指针来做,start 指向连续0的起点,初始化为0,i为当前遍历到的位置。遍历 seats 数组,跳过0的位置,当遇到1的时候,此时先判断下 start 的值,若是0的话,表明当前这段连续的空位是靠着墙的,所以要用连续空位的长度 i-start 来直接更新结果 res,否则的话就是两头有人的中间的空位,那么用长度加1除以2来更新结果 res,此时 start 要更新为 i+1,指向下一段连续空位的起始位置。for 循环退出后,还是要处理最右边靠墙的位置,用 n-start 来更新结果 res 即可,参见代码如下:

解法二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
int maxDistToClosest(vector<int>& seats) {
int n = seats.size(), start = 0, res = 0;
for (int i = 0; i < n; ++i) {
if (seats[i] != 1) continue;
if (start == 0) res = max(res, i - start);
else res = max(res, (i - start + 1) / 2);
start = i + 1;
}
res = max(res, n - start);
return res;
}
};

讨论:这道题的一个很好的 follow up 是让我们返回爱丽丝坐下的位置,那么要在结果 res 可以被更新的时候,同时还应该记录下连续空位的起始位置 start,这样有了 start 和 最大距离 res,那么就可以定位出爱丽丝的座位了。

Leetcode851. Loud and Rich

There is a group of n people labeled from 0 to n - 1 where each person has a different amount of money and a different level of quietness.

You are given an array richer where richer[i] = [ai, bi] indicates that ai has more money than bi and an integer array quiet where quiet[i] is the quietness of the ith person. All the given data in richer are logically correct (i.e., the data will not lead you to a situation where x is richer than y and y is richer than x at the same time).

Return an integer array answer where answer[x] = y if y is the least quiet person (that is, the person y with the smallest value of quiet[y]) among all people who definitely have equal to or more money than the person x.

Example 1:

1
2
3
4
5
6
7
8
9
Input: richer = [[1,0],[2,1],[3,1],[3,7],[4,3],[5,3],[6,3]], quiet = [3,2,5,4,6,1,7,0]
Output: [5,5,2,5,4,5,6,7]
Explanation:
answer[0] = 5.
Person 5 has more money than 3, which has more money than 1, which has more money than 0.
The only person who is quieter (has lower quiet[x]) is person 7, but it is not clear if they have more money than person 0.
answer[7] = 7.
Among all people that definitely have equal to or more money than person 7 (which could be persons 3, 4, 5, 6, or 7), the person who is the quietest (has lower quiet[x]) is person 7.
The other answers can be filled out with similar reasoning.

Example 2:

1
2
Input: richer = [], quiet = [0]
Output: [0]

有n 个人,编号0 ∼ n − 1 ,它们有两个属性,财富和安静,给定两个数组r和q,r里面的元素都是数对,(a , b)表示a比b财富严格更多,而q qq存的是每个人的安静值。要求返回一个数组c,使得c[i]表示对于编号i的这个人,财富不少于他的所有人里安静值最小的人的编号。题目保证财富比较的传递关系没有环。

思路是记忆化搜索。先建图,将这些人看成若干个点,从财富少的到多的人连一条边,然后从每个点开始DFS。DFS到顶点u uu的时候,先DFS所有u的邻接点,这样就求出了u的邻接点的c值,接着在这些邻接点的c值里找到q值最小的(即找的安静值最小的)即可。可以做记忆化,当某个点的c值已经算出了,则不必重复算。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
vector<int> loudAndRich(vector<vector<int>>& richer, vector<int>& quiet) {
int len = quiet.size();
vector<int> res(len, -1);
unordered_map<int, vector<int>> m;
for (auto a : richer)
m[a[1]].push_back(a[0]);
for (int i = 0; i < len; i ++)
helper(m, quiet, i, res);
return res;
}

void helper(unordered_map<int, vector<int>>& m, vector<int>& quiet, int i, vector<int>& res) {
if (res[i] > 0)
return;
res[i] = i;
for (int ii : m[i]) {
helper(m, quiet, ii, res);
if (quiet[res[i]] > quiet[res[ii]])
res[i] = res[ii];
}
}
};

Leetcode852. Peak Index in a Mountain Array

Let’s call an array A a mountain if the following properties hold:

A.length >= 3
There exists some 0 < i < A.length - 1 such that A[0] < A[1] < … A[i-1] < A[i] > A[i+1] > … > A[A.length - 1]
Given an array that is definitely a mountain, return any i such that A[0] < A[1] < … A[i-1] < A[i] > A[i+1] > … > A[A.length - 1].

Example 1:

1
2
Input: [0,1,0]
Output: 1

Example 2:

1
2
Input: [0,2,1,0]
Output: 1

Note:

3 <= A.length <= 10000
0 <= A[i] <= 10^6
A is a mountain, as defined above.

判断一个“山峰”数组的山峰在哪里,本来以为还要判断这个是不是山峰数组的,所以多写了一些,对结果没影响,懒得删了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
int peakIndexInMountainArray(vector<int>& A) {
int res;
bool isreal=false;
if(A.size()<3)
return false;
for(int i=1;i<A.size();i++){
if(A[i]>A[i-1])
if(!isreal)
continue;
if(A[i]<A[i-1]){
if(!isreal){
res=i-1;
isreal=true;
}
}
}
return res;
}
};

我这个做法不好,可以用二分查找。

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public int peakIndexInMountainArray(int[] A) {
int lo = 0, hi = A.length - 1;
while (lo < hi) {
int mi = lo + (hi - lo) / 2;
if (A[mi] < A[mi + 1])
lo = mi + 1;
else
hi = mi;
}
return lo;
}

Leetcode853. Car Fleet

N cars are going to the same destination along a one lane road. The destination is target miles away.
Each car i has a constant speed speed[i] (in miles per hour), and initial position position[i] miles towards the target along the road.

A car can never pass another car ahead of it, but it can catch up to it, and drive bumper to bumper at the same speed.

The distance between these two cars is ignored - they are assumed to have the same position.

A car fleet is some non-empty set of cars driving at the same position and same speed. Note that a single car is also a car fleet.

If a car catches up to a car fleet right at the destination point, it will still be considered as one car fleet.
How many car fleets will arrive at the destination?

Example 1:

1
2
3
4
5
6
7
Input: target = 12, position = [10,8,0,5,3], speed = [2,4,1,1,3]
Output: 3
Explanation:
The cars starting at 10 and 8 become a fleet, meeting each other at 12.
The car starting at 0 doesn't catch up to any other car, so it is a fleet by itself.
The cars starting at 5 and 3 become a fleet, meeting each other at 6.
Note that no other cars meet these fleets before the destination, so the answer is 3.

Note:

  • 0 <= N <= 10 ^ 4
  • 0 < target <= 10 ^ 6
  • 0 < speed[i] <= 10 ^ 6
  • 0 <= position[i] < target
  • All initial positions are different.

这道题说是路上有一系列的车,车在不同的位置,且分别有着不同的速度,但行驶的方向都相同。如果后方的车在到达终点之前追上前面的车了,那么它就会如痴汉般尾随在其后,且速度降至和前面的车相同,可以看作是一个车队,当然,单独的一辆车也可以看作是一个车队,问我们共有多少个车队到达终点。这道题是小学时候的应用题的感觉,什么狗追人啊,人追狗啊之类的。这道题的正确解法的思路其实不太容易想,因为我们很容易把注意力都集中到每辆车,去计算其每个时刻所在的位置,以及跟前面的车相遇的位置,这其实把这道题想复杂了,其实并不需要知道车的相遇位置,只关心是否能组成车队一同经过终点线,那么如何才能知道是否能一起过线呢,最简单的方法就是看时间,假如车B在车A的后面,而车B到终点线的时间小于等于车A,那么就知道车A和B一定会组成车队一起过线。这样的话,就可以从离终点最近的一辆车开始,先算出其撞线的时间,然后再一次遍历身后的车,若后面的车撞线的时间小于等于前面的车的时间,则会组成车队。反之,若大于前面的车的时间,则说明无法追上前面的车,于是自己会形成一个新的车队,且是车头,则结果 res 自增1即可。
思路有了,就可以具体实现了,使用一个 TreeMap 来建立小车位置和其到达终点时间之间的映射,这里的时间使用 double 型,通过终点位置减去当前位置,并除以速度来获得。我们希望能从 position 大的小车开始处理,而 TreeMap 是把小的数字排在前面,这里使用了个小 trick,就是映射的时候使用的是 position 的负数,这样就能先处理原来 position 大的车,从而统计出正确的车队数量,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int carFleet(int target, vector<int>& position, vector<int>& speed) {
int res = 0; double cur = 0;
map<int, double> pos2time;
for (int i = 0; i < position.size(); ++i) {
pos2time[-position[i]] = (double)(target - position[i]) / speed[i];
}
for (auto a : pos2time) {
if (a.second <= cur) continue;
cur = a.second;
++res;
}
return res;
}
};

Leetcode855. Exam Room

In an exam room, there are N seats in a single row, numbered 0, 1, 2, …, N-1.

When a student enters the room, they must sit in the seat that maximizes the distance to the closest person. If there are multiple such seats, they sit in the seat with the lowest number. (Also, if no one is in the room, then the student sits at seat number 0.)

Return a class ExamRoom(int N) that exposes two functions: ExamRoom.seat() returning an int representing what seat the student sat in, and ExamRoom.leave(int p) representing that the student in seat number p now leaves the room. It is guaranteed that any calls to ExamRoom.leave(p) have a student sitting in seat p.

Example 1:

1
2
3
4
5
6
7
8
9
10
Input: ["ExamRoom","seat","seat","seat","seat","leave","seat"], [[10],[],[],[],[],[4],[]]
Output: [null,0,9,4,2,null,5]
Explanation:
ExamRoom(10) -> null
seat() -> 0, no one is in the room, then the student sits at seat number 0.
seat() -> 9, the student sits at the last seat number 9.
seat() -> 4, the student sits at the last seat number 4.
seat() -> 2, the student sits at the last seat number 2.
leave(4) -> null
seat() -> 5, the student sits at the last seat number 5.

Note:

  • 1 <= N <= 10^9
  • ExamRoom.seat() and ExamRoom.leave() will be called at most 10^4 times across all test cases.
  • Calls to ExamRoom.leave(p) are guaranteed to have a student currently sitting in seat number p.

有个考场,每个考生入座的时候都要尽可能的跟左右两边的人距离保持最大,当最大距离相同的时候,考生坐在座位编号较小的那个位置。对于墙的处理跟之前那道是一样的,能靠墙就尽量靠墙,这样肯定离别人最远。

最先想的方法是用一个大小为N的数组来表示所有的座位,初始化为0,表示没有一个人,若有人入座了,则将该位置变为1,离开则变为0,那么对于 leave() 函数就十分简单了,直接将对应位置改为0即可。重点就是 seat() 函数了,采用双指针来做,主要就是找连续的0进行处理,还是要分 start 是否为0的情况,因为空位从墙的位置开始,跟空位在两人之间的处理情况是不同的,若空位从墙开始,肯定是坐墙边,而若是在两人之间,则需要坐在最中间,还要记得更新 start 为下一个空座位。最后在处理末尾空位连到墙的时候,跟之前稍有些不同,因为题目要求当最大距离相同的时候,需要选择座位号小的位置,而当此时 start 为0的时候,说明所有的位置都是空的,那么我们不需要再更新 idx 了,就用其初始值0,表示就坐在第一个位置,是符合题意的。最后别忘了将 idx 位置赋值为1,表示有人坐了。

那么比较直接的改进方法就是去掉那些0,我们只保存有人坐的位置,即所有1的位置。这样省去了遍历0的时间,大大提高了效率,此时我们就可以使用 TreeSet 来保存1的位置,其余部分并不怎么需要改变,在确定了座位 idx 时,将其加入 TreeSet 中。在 leave() 中,直接移除离开人的座位位置即可,参见代码如下:

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
class ExamRoom {
public:

int n;
set<int> s;

ExamRoom(int N) {
n = N;
}

int seat() {
int start = 0, pos = 0, left = -1, dist = 0;
for (int i : s) {
if (start == 0) {
if (dist < i - start) {
dist = i - start;
pos = 0;
}
}
else {
if (dist < (i - start + 1) / 2) {
dist = (i - start + 1) / 2;
pos = start + dist - 1;
}
}
start = i + 1;
}

if (start > 0 && dist < n - start)
pos = n-1;

s.insert(pos);
return pos;
}

void leave(int p) {
s.erase(p);
}
};

Leetcode856. Score of Parentheses

Given a balanced parentheses string S, compute the score of the string based on the following rule:

  • () has score 1
  • AB has score A + B, where A and B are balanced parentheses strings.
  • (A) has score 2 * A, where A is a balanced parentheses string.

Example 1:

1
2
Input: "()"
Output: 1

Example 2:

1
2
Input: "(())"
Output: 2

Example 3:

1
2
Input: "()()"
Output: 2

Example 4:

1
2
Input: "(()(()))"
Output: 6

Note:

  • S is a balanced parentheses string, containing only ( and ).
  • 2 <= S.length <= 50

这道题给了我们一个只有括号的字符串,一个简单的括号值1分,并排的括号是分值是相加的关系,包含的括号是乘的关系,每包含一层,都要乘以个2。题目中给的例子很好的说明了题意,博主最先尝试的方法是递归来做,思路是先找出每一个最外层的括号,再对其中间的整体部分调用递归,比如对于 “()(())” 来说,第一个最外边的括号是 “()”,其中间为空,对空串调用递归返回0,但是结果 res 还是加1,这个特殊的处理后面会讲到。第二个最外边的括号是 “(())” 的外层括号,对其中间的 “()” 调用递归,返回1,再乘以2,则得到 “(())” 的值,然后把二者相加,就是最终需要的结果了。找部分合法的括号字符串的方法就是使用一个计数器,遇到左括号,计数器自增1,反之右括号计数器自减1,那么当计数器为0的时候,就是一个合法的字符串了,我们对除去最外层的括号的中间内容调用递归,然后把返回值乘以2,并和1比较,取二者间的较大值加到结果 res 中,这是因为假如中间是空串,那么返回值是0,乘以2还是0,但是 “()” 的分值应该是1,所以累加的时候要跟1做比较。之后记得要更新i都正确的位置,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int scoreOfParentheses(string S) {
int res = 0, n = S.size();
for (int i = 0; i < n; ++i) {
if (S[i] == ')') continue;
int pos = i + 1, cnt = 1;
while (cnt != 0) {
(S[pos++] == '(') ? ++cnt : --cnt;
}
int cur = scoreOfParentheses(S.substr(i + 1, pos - i - 2));
res += max(2 * cur, 1);
i = pos - 1;
}
return res;
}
};

我们也可以使用迭代来做,这里就要借助栈 stack 来做,因为递归在调用的时候,其实也是将当前状态压入栈中,等递归退出后再从栈中取出之前的状态。这里的实现思路是,遍历字符串S,当遇到左括号时,将当前的分数压入栈中,并把当前得分清0,若遇到的是右括号,说明此时已经形成了一个完整的合法的括号字符串了,而且除去外层的括号,内层的得分已经算出来了,就是当前的结果 res,此时就要乘以2,并且要跟1比较,取二者中的较大值,这样操作的原因已经在上面解法的讲解中解释过了。然后还要加上栈顶的值,因为栈顶的值是之前合法括号子串的值,跟当前的是并列关系,所以是相加的操作,最后不要忘了要将栈顶元素移除即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int scoreOfParentheses(string S) {
int res = 0;
stack<int> st;
for (char c : S) {
if (c == '(') {
st.push(res);
res = 0;
} else {
res = st.top() + max(res * 2, 1);
st.pop();
}
}
return res;
}
};

我们可以对空间复杂度进行进一步的优化,并不需要使用栈去保留所有的中间情况,可以只用一个变量 cnt 来记录当前在第几层括号之中,因为本题的括号累加值是有规律的,”()” 是1,因为最中间的括号在0层括号内,2^0 = 1。”(())” 是2,因为最中间的括号在1层括号内,2^1 = 2。”((()))” 是4,因为最中间的括号在2层括号内,2^2 = 4。因此类推,其实只需要统计出最中间那个括号外变有几个括号,就可以直接算出整个多重包含的括号字符串的值,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
int scoreOfParentheses(string S) {
int res = 0, cnt = 0, n = S.size();
for (int i = 0; i < n; ++i) {
(S[i] == '(') ? ++cnt : --cnt;
if (S[i] == ')' && S[i - 1] == '(') res += (1 << cnt);
}
return res;
}
};

Leetcode858. Mirror Reflection

There is a special square room with mirrors on each of the four walls. Except for the southwest corner, there are receptors on each of the remaining corners, numbered 0, 1, and 2.

The square room has walls of length p, and a laser ray from the southwest corner first meets the east wall at a distance q from the 0th receptor.

Return the number of the receptor that the ray meets first. (It is guaranteed that the ray will meet a receptor eventually.)

Example 1:

1
2
3
Input: p = 2, q = 1
Output: 2
Explanation: The ray meets receptor 2 the first time it gets reflected back to the left wall.

题目大意:

存在一个方形空间,如上图所示,一束光从左下角射出,方形空间的四条边都会反射,在0,1,2处存在3个接收器,问,给定方形空间的边长 p 和第一次到达右边界时距离0号接收器的距离,这束光最终会落到哪个接收器上?

解题思路

首先对于给定的p,q,如果我们把这两个数都同时放大N倍,光线的走法结果不会改变,因此,首先要找p,q得最大公约数,使得p,q互质;
然后,当p,q互质时,不可能两个都是偶数,因此分情况,当p为偶数,q为奇数时,光线会一直走右边界的奇数坐标(1,3,5,7….),然后再走左边的偶数坐标,因此最终必定会走到左边界,即2号接收器;若p为奇数,q为偶数,那么光线射到右边界时是偶数坐标,射到左边界时也是偶数坐标,而由于边长p是奇数,因此最终是不会走到左边界或右边界,即不会到接收器1和2,而是经过1,2之间的边界进行反向,然后往下走,而下面只有接收器0,因此最终必定会走到0;若p是奇数,且q也是奇数,那么光线到右边界时是奇数坐标,到左边界时是偶数坐标,因此最终一定走到接收器1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
int gcd(int m, int n) {
int temp;
while(n != 0) {
temp = m % n;
m = n;
n = temp;
}
return m;
}
int mirrorReflection(int p, int q) {
int temp = gcd(p, q);
p = p / temp;
q = q / temp;
if(p%2==0)
return 2;
else if(q%2==0)
return 0;
else
return 1;
}
};

Leetcode859. Buddy Strings

Given two strings A and B of lowercase letters, return true if and only if we can swap two letters in A so that the result equals B.

Example 1:

1
2
Input: A = "ab", B = "ba"
Output: true

Example 2:

1
2
Input: A = "ab", B = "ab"
Output: false

Example 3:

1
2
Input: A = "aa", B = "aa"
Output: true

Example 4:

1
2
Input: A = "aaaaaaabc", B = "aaaaaaacb"
Output: true

Example 5:

1
2
Input: A = "", B = "aa"
Output: false

Note:

  1. 0 <= A.length <= 20000
  2. 0 <= B.length <= 20000
  3. A and B consist only of lowercase letters.
  • 如果两个字符串长度不相等,则返回 false。
  • 如果两个字符串有超过两处位置,其对应字符不一致,返回 false。
  • 如果仅有两处位置字符不相同,则分别判断这两处位置交换后是否相同。
  • 如果仅有一处位置字符不相同,则返回 false。
  • 如果没有位置字符不相同,则根据题意,我们不得不找到两处位置交换。如果字符串中有两处位置字符相同,则返回 true;否则返回 false。
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
class Solution {
public:
bool buddyStrings(string A, string B) {
int lengtha = A.length(), lengthb = B.length();
if(lengtha != lengthb)
return false;
int flag1 = -1, flag2 = -1;
for(int i = 0; i < lengtha; i ++) {
if(A[i] != B[i]) {
if(flag1 != -1) {
if(flag2 != -1)
return false;
else
flag2 = i;
}
else
flag1 = i;
}
}
if(flag1 == -1 && flag2 == -1) {
vector<int> aa = vector<int>(26, 0);
for(int i = 0; i < lengtha; i ++) {
aa[A[i] - 'a'] ++;
if(aa[A[i] - 'a'] >= 2)
return true;
}
return false;
}
if(A[flag1] != B[flag2] || A[flag2] != B[flag1])
return false;
if(flag1 != -1 && flag2 == -1)
return false;

return true;
}
};

Leetcode860. Lemonade Change

At a lemonade stand, each lemonade costs $5.

Customers are standing in a queue to buy from you, and order one at a time (in the order specified by bills).

Each customer will only buy one lemonade and pay with either a $5, $10, or $20 bill. You must provide the correct change to each customer, so that the net transaction is that the customer pays $5.

Note that you don’t have any change in hand at first.

Return true if and only if you can provide every customer with correct change.

Example 1:

1
2
Input: [5,5,5,10,20]
Output: true

Explanation:

  • From the first 3 customers, we collect three $5 bills in order.
  • From the fourth customer, we collect a $10 bill and give back a $5.
  • From the fifth customer, we give a $10 bill and a $5 bill.
  • Since all customers got correct change, we output true.

Example 2:

1
2
Input: [5,5,10]
Output: true

Example 3:

1
2
Input: [10,10]
Output: false

Example 4:

1
2
Input: [5,5,10,10,20]
Output: false

Explanation:

  • From the first two customers in order, we collect two $5 bills.
  • For the next two customers in order, we collect a $10 bill and give back a $5 bill.
  • For the last customer, we can’t give change of $15 back because we only have two $10 bills.
  • Since not every customer received correct change, the answer is false.

Note:

  1. 0 <= bills.length <= 10000
  2. bills[i] will be either 5, 10, or 20.

题目大意:卖lemon,5块钱一个,现在有3种面值的钞票分别为10,20,5,开始的时候没有找零的钱,问能否让每个人都有找零。

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:
bool lemonadeChange(vector<int>& bills) {
int length = bills.size();
int cnt5 = 0, cnt10 = 0, cnt20 = 0;
for(int i = 0; i < length; i ++) {
if(bills[i] == 5)
cnt5 ++;
else if(bills[i] == 10) {
if(cnt5 <= 0)
return false;
else {
cnt5 --;
cnt10 ++;
}
}
else if(bills[i] == 20) {
if(cnt10 <= 0) {
if(cnt5<3)
return false;
else {
cnt5 -= 3;
}
}
else {
if(cnt5 < 1)
return false;
cnt5 --;
cnt10 --;
}
}
}
return true;
}
};

Leetcode861. Score After Flipping Matrix

We have a two dimensional matrix A where each value is 0 or 1.

A move consists of choosing any row or column, and toggling each value in that row or column: changing all 0s to 1s, and all 1s to 0s.

After making any number of moves, every row of this matrix is interpreted as a binary number, and the score of the matrix is the sum of these numbers.

Return the highest possible score.

Example 1:

1
2
3
4
5
Input: [[0,0,1,1],[1,0,1,0],[1,1,0,0]]
Output: 39
Explanation:
Toggled to [[1,1,1,1],[1,0,0,1],[1,1,1,1]].
0b1111 + 0b1001 + 0b1111 = 15 + 9 + 15 = 39

Note:

1 <= A.length <= 20
1 <= A[0].length <= 20
A[i][j] is 0 or 1.

思路:对一个只有0和1的二维矩阵,移动是指选择任一行或任一列,将所有的0变成1,所有的1变成0,在作出任意次数的移动后,将该矩阵中的每一行都按照二进制数来解释,输出和的最大值。要注意的是行和列任意次移动,达到最大值。即以求出最优解为目标(可重复移动)。按二进制数来解释,注意数组[0-n]对应二进制“高位-低位”。

首先对行移动求最优解:二进制数,高位的有效值“1”大于后面所有位数之和,举个例子:10000=16 01010=10 00111=7。所以我们需要判断A[i]0是否为“1”,将为“0”的进行移动操作,本行即达到最优。重复每行即为所有行最优

再对列移动求最优解:本题为矩阵,所以同一列的数字在二进制解释中位于相同的位置(2^n),当一列中”1”的数量最大时,结果值最大。即为列最优解,同理求出所有列最优解。

最后计算矩阵的总和,注意从低位(数组尾部开始计算)。

  1. 若行最高位(数组行首元素)不为“1”,移动行。
  2. 若列“0”数量多于“1”,移动列。
  3. 从低位(行数组尾部)开始计算数组行值。
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
class Solution {
public:
int matrixScore(vector<vector<int>>& A) {
for(int i=0;i<A.size();i++)
if(A[i][0]==0)
for(int j=0;j<A[i].size();j++)
A[i][j]=1-A[i][j];

for(int j=0;j<A[0].size();j++){
int zero=0,one=0;
for(int i=0;i<A.size();i++){
if(A[i][j]==0)
zero++;
else
one++;
}
if(zero>one)
for(int i=0;i<A.size();i++)
A[i][j]=1-A[i][j];
}

int sum=0;
for(int i=0;i<A.size();i++){
int temp = 0,in=1;
for(int j=A[i].size()-1;j>=0;j--){
temp+=A[i][j]*in;
in*=2;
}
sum+=temp;
}
return sum;
}
};

附上Solution:

Notice that a 1 in the i’th column from the right, contributes 2^i to the score.

Say we are finished toggling the rows in some configuration. Then for each column, (to maximize the score), we’ll toggle the column if it would increase the number of 1s.

We can brute force over every possible way to toggle rows.

Say the matrix has R rows and C columns.

For each state, the transition trans = state ^ (state-1) represents the rows that must be toggled to get into the state of toggled rows represented by (the bits of) state.

We’ll toggle them, and also maintain the correct column sums of the matrix on the side.

Afterwards, we’ll calculate the score. If for example the last column has a column sum of 3, then the score is max(3, R-3), where R-3 represents the score we get from toggling the last column.

In general, the score is increased by max(col_sum, R - col_sum) * (1 << (C-1-c)), where the factor (1 << (C-1-c)) is the power of 2 that each 1 contributes.

Note that this approach may not run in the time allotted.

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
class Solution {
public int matrixScore(int[][] A) {
int R = A.length, C = A[0].length;
int[] colsums = new int[C];
for (int r = 0; r < R; ++r)
for (int c = 0; c < C; ++c)
colsums[c] += A[r][c];

int ans = 0;
for (int state = 0; state < (1<<R); ++state) {
// Toggle the rows so that after, 'state' represents
// the toggled rows.
if (state > 0) {
int trans = state ^ (state-1);
for (int r = 0; r < R; ++r) {
if (((trans >> r) & 1) > 0) {
for (int c = 0; c < C; ++c) {
colsums[c] += A[r][c] == 1 ? -1 : 1;
A[r][c] ^= 1;
}
}
}
}

// Calculate the score with the rows toggled by 'state'
int score = 0;
for (int c = 0; c < C; ++c)
score += Math.max(colsums[c], R - colsums[c]) * (1 << (C-1-c));
ans = Math.max(ans, score);
}
return ans;
}
}

Leetcode863. All Nodes Distance K in Binary Tree

Given the root of a binary tree, the value of a target node target, and an integer k, return an array of the values of all nodes that have a distance k from the target node.

You can return the answer in any order.

Example 1:

1
2
3
Input: root = [3,5,1,6,2,0,8,null,null,7,4], target = 5, k = 2
Output: [7,4,1]
Explanation: The nodes that are a distance 2 from the target node (with value 5) have values 7, 4, and 1.

Example 2:

1
2
Input: root = [1], target = 1, k = 3
Output: []

这道题给了我们一棵二叉树,一个目标结点 target,还有一个整数K,让返回所有跟目标结点 target 相距K的结点。我们知道在子树中寻找距离为K的结点很容易,因为只需要一层一层的向下遍历即可,难点就在于符合题意的结点有可能是祖先结点,或者是在旁边的兄弟子树中,这就比较麻烦了,因为二叉树只有从父结点到子结点的路径,反过来就不行。既然没有,我们就手动创建这样的反向连接即可,这样树的遍历问题就转为了图的遍历(其实树也是一种特殊的图)。建立反向连接就是用一个 HashMap 来来建立每个结点和其父结点之间的映射,使用先序遍历建立好所有的反向连接,然后再开始查找和目标结点距离K的所有结点,这里需要一个 HashSet 来记录所有已经访问过了的结点。

在递归函数中,首先判断当前结点是否已经访问过,是的话直接返回,否则就加入到 visited 中。再判断此时K是否为0,是的话说明当前结点已经是距离目标结点为K的点了,将其加入结果 res 中,然后直接返回。否则分别对当前结点的左右子结点调用递归函数,注意此时带入 K-1,这两步是对子树进行查找。之前说了,还得对父结点,以及兄弟子树进行查找,这是就体现出建立的反向连接 HashMap 的作用了,若当前结点的父结点存在,我们也要对其父结点调用递归函数,并同样带入 K-1,这样就能正确的找到所有满足题意的点了,参见代码如下:

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
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> distanceK(TreeNode* root, TreeNode* target, int k) {
if (!root)
return {};
unordered_map<TreeNode*, TreeNode*> parents;
unordered_set<TreeNode*> visited;
vector<int> res;
build_parent(root, parents);
find_res(target, k, visited, parents, res);
return res;
}

void find_res(TreeNode* root, int k, unordered_set<TreeNode*> visited, unordered_map<TreeNode*, TreeNode*> parents, vector<int>& res) {
if (visited.count(root))
return;
visited.insert(root);
if (k == 0) {
res.push_back(root->val);
return;
}
if (root->left) find_res(root->left, k-1, visited, parents, res);
if (root->right) find_res(root->right, k-1, visited, parents, res);
if (parents[root]) find_res(parents[root], k-1, visited, parents,res);
}

void build_parent(TreeNode* root, unordered_map<TreeNode*, TreeNode*>& parents) {
if (root == NULL)
return ;
if (root->left) parents[root->left] = root;
if (root->right) parents[root->right] = root;
build_parent(root->left, parents);
build_parent(root->right, parents);
}
};

既然是图的遍历,那就也可以使用 BFS 来做,为了方便起见,我们直接建立一个邻接链表,即每个结点最多有三个跟其相连的结点,左右子结点和父结点,使用一个 HashMap 来建立每个结点和其相邻的结点数组之间的映射,这样就几乎完全将其当作图来对待了,建立好邻接链表之后,原来的树的结构都不需要用了。既然是 BFS 进行层序遍历,就要使用队列 queue,还要一个 HashSet 来记录访问过的结点。在 while 循环中,若K为0了,说明当前这层的结点都是符合题意的,就把当前队列中所有的结点加入结果 res,并返回即可。否则就进行层序遍历,取出当前层的每个结点,并在邻接链表中找到和其相邻的结点,若没有访问过,就加入 visited 和 queue 中即可。记得每层遍历完成之后,K要自减1,参见代码如下:

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
class Solution {
public:
vector<int> distanceK(TreeNode* root, TreeNode* target, int K) {
if (!root) return {};
vector<int> res;
unordered_map<TreeNode*, vector<TreeNode*>> m;
queue<TreeNode*> q{{target}};
unordered_set<TreeNode*> visited{{target}};
findParent(root, NULL, m);
while (!q.empty()) {
if (K == 0) {
for (int i = q.size(); i > 0; --i) {
res.push_back(q.front()->val); q.pop();
}
return res;
}
for (int i = q.size(); i > 0; --i) {
TreeNode *t = q.front(); q.pop();
for (TreeNode *node : m[t]) {
if (visited.count(node)) continue;
visited.insert(node);
q.push(node);
}
}
--K;
}
return res;
}
void findParent(TreeNode* node, TreeNode* pre, unordered_map<TreeNode*, vector<TreeNode*>>& m) {
if (!node) return;
if (m.count(node)) return;
if (pre) {
m[node].push_back(pre);
m[pre].push_back(node);
}
findParent(node->left, node, m);
findParent(node->right, node, m);
}
};

其实这道题也可以不用 HashMap,不建立邻接链表,直接在递归中完成所有的需求,真正体现了递归的博大精深。在进行递归之前,我们要先判断一个 corner case,那就是当 K==0 时,此时要返回的就是目标结点值本身,可以直接返回。否则就要进行递归了。这里的递归函数跟之前的有所不同,是需要返回值的,这个返回值表示的含义比较复杂,若为0,表示当前结点为空或者当前结点就是距离目标结点为K的点,此时返回值为0,是为了进行剪枝,使得不用对其左右子结点再次进行递归。当目标结点正好是当前结点的时候,递归函数返回值为1,其他的返回值为当前结点离目标结点的距离加1。还需要一个参数 dist,其含义为离目标结点的距离,注意和递归的返回值区别,这里不用加1,且其为0时候不是为了剪枝,而是真不知道离目标结点的距离。

在递归函数中,首先判断若当前结点为空,则直接返回0。然后判断 dist 是否为k,是的话,说目标结点距离当前结点的距离为K,是符合题意的,需要加入结果 res 中,并返回0,注意这里返回0是为了剪枝。否则判断,若当前结点正好就是目标结点,或者已经遍历过了目标结点(表现为 dist 大于0),那么对左右子树分别调用递归函数,并将返回值分别存入 left 和 right 两个变量中。注意此时应带入 dist+1,因为是先序遍历,若目标结点之前被遍历到了,那么说明目标结点肯定不在当前结点的子树中,当前要往子树遍历的话,肯定离目标结点又远了一些,需要加1。若当前结点不是目标结点,也还没见到目标结点时,同样也需要对左右子结点调用递归函数,但此时 dist 不加1,因为不确定目标结点的位置。若 left 或者 right 值等于K,则说明目标结点在子树中,且距离当前结点为K(为啥呢?因为目标结点本身是返回1,所以当左右子结点返回K时,和当前结点距离是K)。接下来判断,若当前结点是目标结点,直接返回1,这个前面解释过了。然后再看 left 和 right 的值是否大于0,若 left 值大于0,说明目标结点在左子树中,我们此时就要对右子结点再调用一次递归,并且 dist 带入 left+1,同理,若 right 值大于0,说明目标结点在右子树中,我们此时就要对左子结点再调用一次递归,并且 dist 带入 right+1。这两步很重要,是之所以能不建立邻接链表的关键所在。若 left 大于0,则返回 left+1,若 right 大于0,则返回 right+1,否则就返回0,参见代码如下:

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
class Solution {
public:
vector<int> distanceK(TreeNode* root, TreeNode* target, int K) {
if (K == 0) return {target->val};
vector<int> res;
helper(root, target, K, 0, res);
return res;
}
int helper(TreeNode* node, TreeNode* target, int k, int dist, vector<int>& res) {
if (!node) return 0;
if (dist == k) {res.push_back(node->val); return 0;}
int left = 0, right = 0;
if (node->val == target->val || dist > 0) {
left = helper(node->left, target, k, dist + 1, res);
right = helper(node->right, target, k, dist + 1, res);
} else {
left = helper(node->left, target, k, dist, res);
right = helper(node->right, target, k, dist, res);
}
if (left == k || right == k) {res.push_back(node->val); return 0;}
if (node->val == target->val) return 1;
if (left > 0) helper(node->right, target, k, left + 1, res);
if (right > 0) helper(node->left, target, k, right + 1, res);
if (left > 0 || right > 0) return left > 0 ? left + 1 : right + 1;
return 0;
}
};

Leetcode865. Smallest Subtree with all the Deepest Nodes

Given the root of a binary tree, the depth of each node is the shortest distance to the root.

Return the smallest subtree such that it contains all the deepest nodes in the original tree.

A node is called the deepest if it has the largest depth possible among any node in the entire tree.

The subtree of a node is tree consisting of that node, plus the set of all descendants of that node.

Example 1:

1
2
3
4
5
Input: root = [3,5,1,6,2,0,8,null,null,7,4]
Output: [2,7,4]
Explanation: We return the node with value 2, colored in yellow in the diagram.
The nodes coloured in blue are the deepest nodes of the tree.
Notice that nodes 5, 3 and 2 contain the deepest nodes in the tree but node 2 is the smallest subtree among them, so we return it.

Example 2:

1
2
3
Input: root = [1]
Output: [1]
Explanation: The root is the deepest node in the tree.

Example 3:

1
2
3
Input: root = [0,1,3,null,2]
Output: [2]
Explanation: The deepest node in the tree is 2, the valid subtrees are the subtrees of nodes 2, 1 and 0 but the subtree of node 2 is the smallest.

题目的意思是:给定一个根为 root 的二叉树,每个结点的深度是它到根的最短距离。如果一个结点在整个树的任意结点之间具有最大的深度,则该结点是最深的。一个结点的子树是该结点加上它的所有后代的集合。
返回能满足“以该结点为根的子树中包含所有最深的结点”这一条件的具有最大深度的结点。

二叉树一般就是递归,思路很直接,代码很简洁,用到pair把深度和root回传。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
TreeNode* subtreeWithAllDeepest(TreeNode* root) {
return solve(root).second;
}

pair<int, TreeNode*> solve(TreeNode* root) {
if (root == NULL) return make_pair(0, root);
pair<int, TreeNode*> left = solve(root->left);
pair<int, TreeNode*> right = solve(root->right);
if (left.first == right.first) return make_pair(left.first+1, root);
else if (left.first > right.first) return make_pair(left.first+1, left.second);
else return make_pair(right.first+1, right.second);
}
};

Leetcode867. Transpose Matrix

Given a matrix A, return the transpose of A.

The transpose of a matrix is the matrix flipped over it’s main diagonal, switching the row and column indices of the matrix.

Example 1:

1
2
Input: [[1,2,3],[4,5,6],[7,8,9]]
Output: [[1,4,7],[2,5,8],[3,6,9]]

Example 2:

1
2
Input: [[1,2,3],[4,5,6]]
Output: [[1,4],[2,5],[3,6]]

Note:

  1. 1 <= A.length <= 1000
  2. 1 <= A[0].length <= 1000

Easy题目,矩阵转置基本操作,i,j下标对调

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
vector<vector<int>> transpose(vector<vector<int>>& A) {
int m = A.size(), n = A[0].size();
vector<vector<int>> result(n, vector<int>(m, 0));
for(int i = 0; i < m; i ++)
for(int j = 0; j < n; j ++)
result[j][i] = A[i][j];
return result;
}
};

Leetcode868. Binary Gap

Given a positive integer N, find and return the longest distance between two consecutive 1’s in the binary representation of N.

If there aren’t two consecutive 1’s, return 0.

Example 1:

1
2
3
4
5
6
7
8
Input: 22
Output: 2
Explanation:
22 in binary is 0b10110.
In the binary representation of 22, there are three ones, and two consecutive pairs of 1's.
The first consecutive pair of 1's have distance 2.
The second consecutive pair of 1's have distance 1.
The answer is the largest of these two distances, which is 2.

Example 2:

1
2
3
4
Input: 5
Output: 2
Explanation:
5 in binary is 0b101.

Example 3:

1
2
3
4
Input: 6
Output: 1
Explanation:
6 in binary is 0b110.

Example 4:

1
2
3
4
5
Input: 8
Output: 0
Explanation:
8 in binary is 0b1000.
There aren't any consecutive pairs of 1's in the binary representation of 8, so we return 0.

找到一个数的二进制表示中最远的两个1的距离。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int binaryGap(int N) {
int res = 0, prev = -1, cur = -1;
int count = 0;
while(N) {
int temp = N & 1;
if(temp == 1) {
prev = cur;
cur = count;
if(prev != -1 && cur != -1)
res = max(res, cur - prev);
}
count ++;
N = N >> 1;
}
return res;
}
};

LeetCode869. Reordered Power of 2

Starting with a positive integer N, we reorder the digits in any order (including the original order) such that the leading digit is not zero.
Return true if and only if we can do this in a way such that the resulting number is a power of 2.

Example 1:

1
2
Input: 1
Output: true

Example 2:

1
2
Input: 10
Output: false

Example 3:

1
2
Input: 16
Output: true

Example 4:

1
2
Input: 24
Output: false

Example 5:

1
2
Input: 46
Output: true

Note:

  • 1 <= N <= 10^9

这道题说是给了我们一个正整数N,让对各位上的数字进行重新排序,但是要保证最高位上不是0,问能否变为2的指数。刚开始的时候博主理解错了,以为是对N的二进制数的各位进行重排序,但除了2的指数本身,其他数字怎么也组不成2的指数啊,因为2的指数的二进制数只有最高位是1,其余都是0。后来才发现,是让对N的十进制数的各位上的数字进行重排序,比如 N=46,那么换个位置,变成 64,就是2的指数了。搞清了题意后,就可以开始解题了,由于N给定了范围,在 [1, 1e9] 之间,所以其调换位数能组成的二进制数也是有范围的,为 [2^0, 2^30] 之间,这样的话,一个比较直接的解法就是,现将整数N转为字符串,然后对字符串进行排序。然后遍历所有可能的2的指数,将每个2的指数也转为字符串并排序,这样只要某个排序后的字符串跟之前由N生成的字符串相等的话,则表明整数N是符合题意的,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
bool reorderedPowerOf2(int N) {
string str = to_string(N);
sort(str.begin(), str.end());
for (int i = 0; i < 31; ++i) {
string t = to_string(1 << i);
sort(t.begin(), t.end());
if (t == str) return true;
}
return false;
}
};

下面这种方法没有将数字转为字符串并排序,而是使用了另一种比较巧妙的方法来实现类似的功能,是通过对N的每位上的数字都变为10的倍数,并相加,这样相当于将N的各位的上的数字都加码到了10的指数空间,而对于所有的2的指数,进行相同的操作,只要某个加码后的数字跟之前整数N的处理后的数字相同,则说明N是符合题意的。需要注意的是,为了防止整型移除,加码后的数字用长整型来表示即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
bool reorderedPowerOf2(int N) {
long sum = helper(N);
for (int i = 0; i < 31; i++) {
if (helper(1 << i) == sum) return true;
}
return false;
}
long helper(int N) {
long res = 0;
for (; N; N /= 10) res += pow(10, N % 10);
return res;
}
};

Leetcode870. Advantage Shuffle

Given two arrays A and B of equal size, the advantage of A with respect to B is the number of indices i for which A[i] > B[i].

Return any permutation of A that maximizes its advantage with respect to B.

Example 1:

1
2
Input: A = [2,7,11,15], B = [1,10,4,11]
Output: [2,11,7,15]

Example 2:

1
2
Input: A = [12,24,8,32], B = [13,25,32,11]
Output: [24,32,8,12]

Note:

  • 1 <= A.length = B.length <= 10000
  • 0 <= A[i] <= 10^9
  • 0 <= B[i] <= 10^9

这道题给了我们两个数组A和B,让对A进行重排序,使得每个对应对位置上A中的数字尽可能的大于B。这不就是大名鼎鼎的田忌赛马么,但想出高招并不是田忌,而是孙膑,就是孙子兵法的作者,但这 credit 好像都给了田忌,让人误以为是田忌的智慧,不禁想起了高富帅重金买科研成果的冠名权的故事。孙子原话是,“今以君之下驷与彼上驷,取君上驷与彼中驷,取君中驷与彼下驷”。就是自己的下马跟人上马比,稳输不用管,上马跟其中马跑,稳赢,中马跟其下马跑,还是稳赢。那我还全马跟其半马跑,能赢否?不过说的,今天博主所在的城市还真有马拉松比赛,而且博主还报了半马,但是由于身不由己的原因无法去跑,实在是可惜,没事,来日方长,总是有机会的。扯了这么久的犊子,赶紧拉回来做题吧。其实这道题的思路还真是田忌赛马的智慧一样,既然要想办法大过B中的数,那么对于B中的每个数(可以看作每匹马),先在A中找刚好大于该数的数字(这就是为啥中马跟其下马比,而不是上马跟其下马比),用太大的数字就浪费了,而如果A中没有比之大的数字,就用A中最小的数字(用下马跟其上马比,不过略有不同的是此时我们没有上马)。就用这种贪婪算法的思路就可以成功解题了,为了方便起见,就是用一个 MultiSet 来做,相当于一个允许重复的 TreeSet,既允许重复又自带排序功能,岂不美哉!那么遍历B中每个数字,在A进行二分搜索第一个大于的数字,这里使用了 STL 自带的 upper_bound 来做,当然想自己写二分也没问题。然后看,若不存在,则将A中最小的数字加到结果 res 中,否则就将第一个大于的数字加入结果 res 中,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
vector advantageCount(vector& A, vector& B) {
vector<int> res;
multiset<int> st(A.begin(), A.end());
for (int i = 0; i < B.size(); ++i) {
auto it = (*st.rbegin() <= B[i]) ? st.begin() : st.upper_bound(B[i]);
res.push_back(*it);
st.erase(it);
}
return res;
}
};

当两个数组都是有序的时候,我们就能快速的直到各自的最大值与最小值,问题就变得容易很多了。比如可以先从B的最大值开始,这是就看A的最大值能否大过B,能的话,就移动到对应位置,不能的话就用最小值,然后再看B的次大值,这样双指针就可以解决问题。所以可以先给A按从小到大的顺序,对于B的话,不能直接排序,因为这样的话原来的顺序就完全丢失了,所以将B中每个数字和其原始坐标位置组成一个 pair 对儿,加入到一个最大堆中,这样B中的最大值就会最先被取出来,再进行上述的操作,这时候就可以发现保存的原始坐标就发挥用处了,根据其坐标就可以直接更新结果 res 中对应的位置了,参见代码如下:
解法二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
vector advantageCount(vector& A, vector& B) {
int n = A.size(), left = 0, right = n - 1;
vector<int> res(n);
sort(A.begin(), A.end());
priority_queue<pair<int, int>> q;
for (int i = 0; i < n; ++i)
q.push({B[i], i});
while (!q.empty()) {
int val = q.top().first, idx = q.top().second; q.pop();
if (A[right] > val) res[idx] = A[right--];
else res[idx] = A[left++];
}
return res;
}
};

Leetcode871. Minimum Number of Refueling Stops

A car travels from a starting position to a destination which is target miles east of the starting position.

Along the way, there are gas stations. Each station[i] represents a gas station that is station[i][0] miles east of the starting position, and has station[i][1] liters of gas.

The car starts with an infinite tank of gas, which initially has startFuel liters of fuel in it. It uses 1 liter of gas per 1 mile that it drives.

When the car reaches a gas station, it may stop and refuel, transferring all the gas from the station into the car.

What is the least number of refueling stops the car must make in order to reach its destination? If it cannot reach the destination, return -1.

Note that if the car reaches a gas station with 0 fuel left, the car can still refuel there. If the car reaches the destination with 0 fuel left, it is still considered to have arrived.

Example 1:

1
2
3
Input: target = 1, startFuel = 1, stations = []
Output: 0
Explanation: We can reach the target without refueling.

Example 2:

1
2
3
Input: target = 100, startFuel = 1, stations = [[10,100]]
Output: -1
Explanation: We can't reach the target (or even the first gas station).

Example 3:

1
2
3
4
5
6
7
8
Input: target = 100, startFuel = 10, stations = [[10,60],[20,30],[30,30],[60,40]]
Output: 2
Explanation:
We start with 10 liters of fuel.
We drive to position 10, expending 10 liters of fuel. We refuel from 0 liters to 60 liters of gas.
Then, we drive from position 10 to position 60 (expending 50 liters of fuel),
and refuel from 10 liters to 50 liters of gas. We then drive to and reach the target.
We made 2 refueling stops along the way, so we return 2.

Note:

  • 1 <= target, startFuel, stations[i][1] <= 10^9
  • 0 <= stations.length <= 500
  • 0 < stations[0][0] < stations[1][0] < … < stations[stations.length-1][0] < target

这道题说有一辆小车,需要向东行驶 target 的距离,路上有许多加油站,每个加油站有两个信息,一个是距离起点的距离,另一个是可以加的油量,问我们到达 target 位置最少需要加的油量。我们可以从第三个例子来分析,开始时有 10 升油,可以到达第一个加油站,此时花掉了 10 升,但是可以补充 60 升,当前的油可以到达其他所有的加油站,由于已经开了 10 迈,所以到达后面的加油站的距离分别为 10,20,和 50。若我们到最后一个加油站,那离起始位置就有 60 迈了,再加上此加油站提供的 40 升油,直接就可以到达 100 位置,不用再加油了,所以总共只需要加2次油。由此可以看出来其实我们希望到达尽可能远的加油站的位置,同时最好该加油站中的油也比较多,这样下一次就能到达更远的位置。像这种求极值的问题,十有八九要用动态规划 Dynamic Programming 来做,但是这道题的 dp 定义式并不是直接来定义需要的最少加油站的个数,那样定义的话不太好推导出状态转移方程。正确的定义应该是根据加油次数能到达的最远距离,我们就用一个一维的 dp 数组,其中 dp[i] 表示加了i次油能到达的最远距离,那么最后只要找第一个i值使得 dp[i] 大于等于 target 即可。dp 数组的大小初始化为加油站的个数加1,值均初始化为 startFuel 即可,因为初始的油量能到达的距离是确定的。现在来推导状态转移方程了,遍历每一个加油站,对于每个遍历到的加油站k,需要再次遍历其之前的所有的加油站i,能到达当前加油站k的条件是当前的 dp[i] 值大于等于加油站k距起点的距离,若大于等于的话,我们可以更新 dp[i+1] 为 dp[i]+stations[k][1],这样就可以得到最远能到达的距离。当 dp 数组更新完成后,需要再遍历一遍,找到第一个大于等于 target 的 dp[i] 值,并返回i即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int minRefuelStops(int target, int startFuel, vector<vector>& stations) {
int n = stations.size();
vector<long> dp(n + 1, startFuel);
for (int k = 0; k < n; ++k) {
for (int i = k; i >= 0 && dp[i] >= stations[k][0]; --i) {
dp[i + 1] = max(dp[i + 1], dp[i] + stations[k][1]);
}
}
for (int i = 0; i <= n; ++i) {
if (dp[i] >= target) return i;
}
return -1;
}
};

这道题还有一个标签是 Heap,说明还可以用堆来做,这里是用最大堆。因为之前也分析了,我们关心的是在最小的加油次数下能达到的最远距离,那么每个加油站的油量就是关键因素,可以将所有能到达的加油站根据油量的多少放入最大堆,这样每一次都选择油量最多的加油站去加油,才能尽可能的到达最远的地方(如果骄傲没被现实大海冷冷拍下,又怎会懂得要多努力,才走得到远方。。。打住打住,要唱起来了 ^o^)。这里需要一个变量i来记录当前遍历到的加油站的位置,外层循环的终止条件是 startFuel 小于 target,然后在内部也进行循环,若当前加油站的距离小于等于 startFuel,说明可以到达,则把该加油站油量存入最大堆,这个 while 循环的作用就是把所有当前能到达的加油站的油量都加到最大堆中。这样取出的堆顶元素就是最大的油量,也是我们下一步需要去的地方(最想要去的地方,怎么能在半路就返航?!),假如此时堆为空,则直接返回 -1,表示无法到达 target。否则就把堆顶元素加到 startFuel 上,此时的startFuel 就表示当前能到的最远距离,是不是跟上面的 DP 解法核心思想很类似。由于每次只能去一个加油站,此时结果 res 也自增1,当 startFuel 到达 target 时,结果 res 就是最小的加油次数,参见代码如下:
解法二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int minRefuelStops(int target, int startFuel, vector<vector>& stations) {

int res = 0, i = 0, n = stations.size();
priority_queue<int> pq;
for (; startFuel < target; ++res) {
while (i < n && stations[i][0] <= startFuel) {
pq.push(stations[i++][1]);
}
if (pq.empty()) return -1;
startFuel += pq.top(); pq.pop();
}
return res;
}
};

Leetcode872. Leaf-Similar Trees

Consider all the leaves of a binary tree. From left to right order, the values of those leaves form a leaf value sequence.

For example, in the given tree above, the leaf value sequence is (6, 7, 4, 9, 8). Two binary trees are considered leaf-similar if their leaf value sequence is the same. Return true if and only if the two given trees with head nodes root1 and root2 are leaf-similar.

题目并不是很难,只不过利用dfs的方法对树进行遍历,并利用vector对叶子节点进行记录,最后再比较两个得到的vector是否相同就可以得到结果了。

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
class Solution {
public:
bool leafSimilar(TreeNode* root1, TreeNode* root2) {
vector<int> v1, v2;
dfs(root1, v1);
dfs(root2, v2);
return issim(v1, v2);
}

bool issim(vector<int> v1, vector<int> v2) {
if(v1.size() != v2.size())
return false;
for(int i = 0; i < v1.size(); i ++) {
if(v1[i] != v2[i])
return false;
}
return true;
}

void dfs(TreeNode* root, vector<int> &v) {
if(root == NULL)
return;
if(root->left == nullptr && root->right == nullptr)
v.push_back(root->val);
dfs(root->left, v);
dfs(root->right, v);
}

};

Leetcode873. Length of Longest Fibonacci Subsequence

A sequence x1, x2, …, xn is Fibonacci-like if:

1
2
n >= 3
xi + xi+1 == xi+2 for all i + 2 <= n

Given a strictly increasing array arr of positive integers forming a sequence, return the length of the longest Fibonacci-like subsequence of arr. If one does not exist, return 0.

A subsequence is derived from another sequence arr by deleting any number of elements (including none) from arr, without changing the order of the remaining elements. For example, [3, 5, 8] is a subsequence of [3, 4, 5, 6, 7, 8].

Example 1:

1
2
3
Input: arr = [1,2,3,4,5,6,7,8]
Output: 5
Explanation: The longest subsequence that is fibonacci-like: [1,2,3,5,8].

Example 2:

1
2
3
Input: arr = [1,3,7,11,12,14,18]
Output: 3
Explanation: The longest subsequence that is fibonacci-like: [1,11,12], [3,11,14] or [7,11,18].

这道题的 DP 定义式也是难点之一,一般来说,对于子数组子序列的问题,我们都会使用一个二维的 dp 数组,其中dp[i][j]表示范围 [i, j] 内的极值,但是在这道题不行,就算你知道了子区间 [i, j] 内的最长斐波那契数列的长度,还是无法更新其他区间。再回过头来看一下斐波那契数列的定义,从第三个数开始,每个数都是前两个数之和,所以若想增加数列的长度,这个条件一定要一直保持,比如对于数组 [1, 2, 3, 4, 7],在子序列 [1, 2, 3] 中以3结尾的斐氏数列长度为3,虽然 [3, 4, 7] 也可以组成斐氏数列,但是以7结尾的斐氏数列长度更新的时候不能用以3结尾的斐氏数列长度的信息,因为 [1, 2, 3, 4, 7] 不是一个正确的斐氏数列,虽然 1+2=3, 3+4=7,但是 2+3!=4。所以每次只能增加一个长度,而且必须要知道前两个数字,正确的dp[i][j]应该是表示以A[i]A[j]结尾的斐氏数列的长度。

接下来看该怎么更新 dp 数组,我们还是要确定两个数字,跟之前的解法不同的是,先确定一个数字,然后遍历之前比其小的所有数字,这样A[i]A[j]两个数字确定了,此时要找一个比A[i]A[j]都小的数,即A[i]-A[j],若这个数字存在的话,说明斐氏数列存在,因为[A[i]-A[j], A[j], A[i]]是满足斐氏数列要求的。这样状态转移就有了,dp[j][i] = dp[indexOf(A[i]-A[j])][j] + 1,可能看的比较晕,但其实就是A[i]加到了以A[j]A[i]-A[j]结尾的斐氏数列的后面,使得长度增加了1。不过前提是A[i]-A[j]必须要在原数组中存在,而且还需要知道某个数字在原数组中的坐标,那么就用 HashMap 来建立数字跟其坐标之间的映射。可以事先把所有数字都存在 HashMap 中,也可以在遍历i的时候建立,因为我们只关心位置i之前的数字。这样在算出A[i]-A[j]之后,在 HashMap 查找差值是否存在,不存在的话赋值为 -1。在更新dp[j][i]的时候,我们看A[i]-A[j] < A[j]且 k>=0 是否成立,因为A[i]-A[j]是斐氏数列中最小的数,且其位置k必须要存在才能更新。否则的话更新为2。最后还是要注意,若 res 小于3的时候,要返回0,因为斐波那契数列的最低消费是3个,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int lenLongestFibSubseq(vector<int>& arr) {
int len = arr.size(), res = 0;
vector<vector<int>> dp(len, vector<int>(len, 0));
unordered_map<int, int> m;
for (int i = 0; i < len; i ++)
m[arr[i]] = i;
for (int i = 0; i < len; i ++) {
for (int j = 0; j < i; j ++) {
if (arr[i] - arr[j] < arr[j] && m.count(arr[i]-arr[j]))
dp[j][i] = dp[m[arr[i]-arr[j]]][j] + 1;
else
dp[j][i] = 2;
res = max(res, dp[j][i]);
}
}
return res > 2 ? res : 0;
}
};

Leetcode874. Walking Robot Simulation

A robot on an infinite grid starts at point (0, 0) and faces north. The robot can receive one of three possible types of commands:

  • -2: turn left 90 degrees
  • -1: turn right 90 degrees
  • 1 <= x <= 9: move forward x units
  • Some of the grid squares are obstacles.

The i-th obstacle is at grid point (obstacles[i][0], obstacles[i][1])

If the robot would try to move onto them, the robot stays on the previous grid square instead (but still continues following the rest of the route.)

Return the square of the maximum Euclidean distance that the robot will be from the origin.

Example 1:

1
2
3
Input: commands = [4,-1,3], obstacles = []
Output: 25
Explanation: robot will go to (3, 4)

Example 2:

1
2
3
Input: commands = [4,-1,4,-2,4], obstacles = [[2,4]]
Output: 65
Explanation: robot will be stuck at (1, 4) before turning left and going to (1, 8)

Note:

  1. 0 <= commands.length <= 10000
  2. 0 <= obstacles.length <= 10000
  3. -30000 <= obstacle[i][0] <= 30000
  4. -30000 <= obstacle[i][1] <= 30000
  5. The answer is guaranteed to be less than 2 ^ 31.

把障碍存在一个set里,方便以后查找判断。每次计算目前最大的x^2 + y^2,用一个大小为2的数组来表示X、Y坐标,之后只需要用axis=0来表示X,axis=1来表示Y即可,不需要知道到底是哪个轴。在进行移动的时候,每次只走一格,计算max_square。值得注意的是,max_square的初始值为0,因为如果被障碍遮挡或者根本没有移动指令导致原地不动的情况下,最大值是0。

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
class Solution {
public:
int robotSim(vector<int>& commands, vector<vector<int>>& obstacles) {
set<pair<int, int>> obs;
for(int i = 0; i < obstacles.size(); i++)
obs.insert(make_pair(obstacles[i][0], obstacles[i][1]));

int coord[2] = {0, 0}, dir = 1; // x-y coordinate 0: x, 1: y, 2: -x, 3: -y
int result = 0;
for(int i = 0; i < commands.size(); i ++) {
if(commands[i] == -2)
dir = (dir+1) % 4;
else if(commands[i] == -1)
dir = (dir+3) % 4;
else {
int axis, forward;
switch(dir)
{
case 0:
axis = 0; forward = 1;
break;
case 1:
axis = 1; forward = 1;
break;
case 2:
axis = 0; forward = -1;
break;
case 3:
axis = 1; forward = -1;
break;
default:
break;
}
for(int m = 0; m < commands[i]; m ++) {
coord[axis] += forward;
if(obs.find(make_pair(coord[0], coord[1])) != obs.end()) {
coord[axis] -= forward;
break;
}
result = max(result, coord[0]*coord[0] + coord[1]*coord[1]);
}
}
}
return result;
}
};

Leetcode875. Koko Eating Bananas

Koko loves to eat bananas. There are N piles of bananas, the i-th pile has piles[i] bananas. The guards have gone and will come back in H hours.
Koko can decide her bananas-per-hour eating speed of K. Each hour, she chooses some pile of bananas, and eats K bananas from that pile. If the pile has less than K bananas, she eats all of them instead, and won’t eat any more bananas during this hour.

Koko likes to eat slowly, but still wants to finish eating all the bananas before the guards come back.

Return the minimum integer K such that she can eat all the bananas within Hhours.

Example 1:

1
2
Input: piles = [3,6,7,11], H = 8
Output: 4

Example 2:

1
2
Input: piles = [30,11,23,4,20], H = 5
Output: 30

Example 3:

1
2
Input: piles = [30,11,23,4,20], H = 6
Output: 23

Note:

  • 1 <= piles.length <= 10^4
  • piles.length <= H <= 10^9
  • 1 <= piles[i] <= 10^9

这道题说有一只叫科科的猩猩,非常的喜欢吃香蕉,现在有N堆香蕉,每堆的个数可能不同,科科有H小时的时间来吃。要求是,每个小时内,科科只能选某一堆香蕉开始吃,若科科的吃速固定为K,即便在一小时内科科已经吃完了该堆的香蕉,也不能换堆,直到下一个小时才可以去另一堆吃。为了健康,科科想尽可能的吃慢一些,但同时也想在H小时内吃完所有的N堆香蕉,让我们找出一个最小的吃速K值。那么首先来想,既然每个小时只能吃一堆,总共要在H小时内吃完N堆,那么H一定要大于等于N,不然一定没法吃完N堆,这个条件题目中给了,所以就不用再 check 了。我们想一下K的可能的取值范围,当H无穷大的时候,科科有充足的时间去吃,那么就可以每小时只吃一根,也可以吃完,所以K的最小取值是1。那么当H最小,等于N时,那么一个小时内必须吃完任意一堆,那么K值就应该是香蕉最多的那一堆的个数,题目中限定了不超过 1e9,这就是最大值。所以要求的K值的范围就是 [1, 1e9],固定的范围内查找数字,当然,最暴力的方法就是一个一个的试,凭博主多年与 OJ 抗衡的经验来说,基本可以不用考虑的。那么二分查找法就是不二之选了,我们知道经典的二分查找法,是要求数组有序的,而这里香蕉个数数组又不一定是有序的。这是一个很好的观察,但是要弄清楚到底是什么应该是有序的,要查找的K是吃速,跟香蕉堆的个数并没有直接的关系,而K所在的数组其实应该是 [1, 1e9] 这个数组,其本身就是有序的,所以二分查找没有问题。当求出了 mid 之后,需要统计用该速度吃完所有的香蕉堆所需要的时间,统计的方法就是遍历每堆的香蕉个数,然后算吃完该堆要的时间。比如 K=4,那么假如有3个香蕉,需要1个小时,有4香蕉,还是1个小时,有5个香蕉,就需要两个小时,如果将三种情况融合为一个式子呢,就是用吃速加上香蕉个数减去1,再除以吃速即可,即 (pile+mid-1)/mid,大家可以自行带数字检验,是没有问题的。算出需要的总时间后去跟H比较,若小于H,说明吃的速度慢了,需要加快速度,所以 left 更新为 mid+1,否则 right 更新为 mid,最后返回 right 即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int minEatingSpeed(vector<int>& piles, int H) {
int left = 1, right = 1e9;
while (left < right) {
int mid = left + (right - left) / 2, cnt = 0;
for (int pile : piles) cnt += (pile + mid - 1) / mid;
if (cnt > H) left = mid + 1;
else right = mid;
}
return right;
}
};

Leetcode876. Middle of the Linked List

Given a non-empty, singly linked list with head node head, return a middle node of linked list.

If there are two middle nodes, return the second middle node.

Example 1:

1
2
3
4
5
Input: [1,2,3,4,5]
Output: Node 3 from this list (Serialization: [3,4,5])
The returned node has value 3. (The judge's serialization of this node is [3,4,5]).
Note that we returned a ListNode object ans, such that:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, and ans.next.next.next = NULL.

Example 2:

1
2
3
Input: [1,2,3,4,5,6]
Output: Node 4 from this list (Serialization: [4,5,6])
Since the list has two middle nodes with values 3 and 4, we return the second one.

Note: The number of nodes in the given list will be between 1 and 100.

题目大意:求链表的中间节点。思路:构造两个节点,遍历链接,一个每次走一步,另一个每次走两步,一个遍历完链表,另一个恰好在中间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
ListNode* middleNode(ListNode* head) {
if(head == NULL || head->next == NULL)
return head;
ListNode* p = head, *pp = head;
while(pp != NULL && pp->next != NULL){
p = p->next;
pp = pp->next->next;
}
printf("%d\n", p->val);
return p;
}
};

久仰大名的快慢指针做法,但是这里有坑,一定要注意while里的判断条件是两个,保证在有奇数个数和偶数个数的链表都不会访问空指针。

Leetcode877. Stone Game

Alex and Lee play a game with piles of stones. There are an even number of piles arranged in a row, and each pile has a positive integer number of stones piles[i].

The objective of the game is to end with the most stones. The total number of stones is odd, so there are no ties.

Alex and Lee take turns, with Alex starting first. Each turn, a player takes the entire pile of stones from either the beginning or the end of the row. This continues until there are no more piles left, at which point the person with the most stones wins.

Assuming Alex and Lee play optimally, return True if and only if Alex wins the game.

Example 1:

1
2
3
4
5
6
7
8
Input: [5,3,4,5]
Output: true
Explanation:
Alex starts first, and can only take the first 5 or the last 5.
Say he takes the first 5, so that the row becomes [3, 4, 5].
If Lee takes 3, then the board is [4, 5], and Alex takes 5 to win with 10 points.
If Lee takes the last 5, then the board is [3, 4], and Alex takes 4 to win with 9 points.
This demonstrated that taking the first 5 was a winning move for Alex, so we return true.

Note:

  • 2 <= piles.length <= 500
  • piles.length is even.
  • 1 <= piles[i] <= 500
  • sum(piles) is odd.

这道题说是有偶数堆的石子,每堆的石子个数可能不同,但石子总数是奇数个。现在 Alex 和 Lee (不应该是 Alice 和 Bob 么??)两个人轮流选石子堆,规则是每次只能选开头和末尾中的一堆,最终获得石子总数多的人获胜。若 Alex 先选,两个人都会一直做最优选择,问我们最终 Alex 是否能获胜。博主最先想到的方法是像 Predict the Winner 中的那样,用个 player 变量来记录当前是哪个玩家在操作,若为0,表示 Alex 在选,那么他只有两种选择,要么拿首堆,要么拿尾堆,两种情况分别调用递归,两个递归函数只要有一个能返回 true,则表示 Alex 可以获胜,还需要用个变量 cur0 来记录当前 Alex 的石子总数。同理,若 Lee 在选,即 player 为1的时候,也是只有两种选择,分别调用递归,两个递归函数只要有一个能返回 true,则表示 Lee 可以获胜,用 cur1 来记录当前 Lee 的石子总数。需要注意的是,当首堆或尾堆被选走了后,我们需要标记,这里就有两种方法,一种是从原 piles 中删除选走的堆(或者是新建一个不包含选走堆的数组),但是这种方法会包括大量的拷贝运算,无法通过 OJ。另一种方法是用两个指针 left 和 right,分别指向首尾的位置。当选取了首堆时,则 left 自增1,若选了尾堆时,则 right 自减1。这样就不用执行删除操作,或是拷贝数组了,大大的提高了运行效率,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
bool stoneGame(vector<int>& piles) {
return helper(piles, 0, 0, 0, (int)piles.size() - 1, 0);
}
bool helper(vector<int>& piles, int cur0, int cur1, int left, int right, int player) {
if (left > right) return cur0 > cur1;
if (player == 0) {
return helper(piles, cur0 + piles[left], cur1, left + 1, right, 1) || helper(piles, cur0 + piles[right], cur1, left + 1, right, 1);
} else {
return helper(piles, cur0, cur1 + piles[left], left, right - 1, 0) || helper(piles, cur0, cur1 + piles[right], left, right - 1, 0);
}
}
};

这道题也可以使用动态规划 Dynamic Programming 来做,由于玩家获胜的规则是拿到的石子数多,那么多的石子数就可以量化为 dp 值。所以我们用一个二维数组,其中dp[i][j]表示在区间[i, j]内 Alex 比 Lee 多拿的石子数,若为正数,说明 Alex 拿得多,若为负数,则表示 Lee 拿得多。则最终只要看dp[0][n-1]的值,若为正数,则 Alex 能获胜。现在就要找状态转移方程了,我们想,在区间[i, j]内要计算 Alex 比 Lee 多拿的石子数,在这个区间内,Alex 只能拿i或者j位置上的石子,那么当 Alex 拿了piles[i]的话,等于 Alex 多了piles[i]个石子,此时区间缩小成了[i+1, j],此时应该 Lee 拿了,此时根据我们以往的 DP 经验,应该调用子区间的 dp 值,没错,但这里dp[i+1][j]表示是在区间[i+1, j]内 Alex 多拿的石子数,但是若区间[i+1, j]内 Lee 先拿的话,其多拿的石子数也应该是dp[i+1][j],因为两个人都要最优化拿,那么dp[i][j]的值其实可以被piles[i] - dp[i+1][j]更新,因为 Alex 拿了piles[i],减去 Lee 多出的dp[i+1][j],就是区间[i, j]中 Alex 多拿的石子数。同理,假如 Alex 先拿piles[j],那么就用piles[j] - dp[i][j-1]来更新dp[i][j],则我们用二者的较大值来更新即可。注意开始的时候要把dp[i][i]都初始化为piles[i],还需要注意的是,这里的更新顺序很重要,是从小区间开始更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
bool stoneGame(vector<int>& piles) {
int n = piles.size();
vector<vector<int>> dp(n, vector<int>(n));
for (int i = 0; i < n; ++i) dp[i][i] = piles[i];
for (int len = 1; len < n; ++len) {
for (int i = 0; i < n - len; ++i) {
int j = i + len;
dp[i][j] = max(piles[i] - dp[i + 1][j], piles[j] - dp[i][j - 1]);
}
}
return dp[0][n - 1] > 0;
}
};

其实这道题是一道脑筋急转弯题,跟之前那道 Nim Game 有些像。原因就在于题目中的一个条件,那就是总共有偶数堆,那么就是可以分为堆数相等的两堆,比如我们按奇偶分为两堆。题目还说了石子总数为奇数个,那么分出的这两堆的石子总数一定是不相等的,那么我们只要每次一直取石子总数多的奇数堆或者偶数堆,Alex 就一定可以躺赢,所以最叼的方法就是直接返回 true。

1
2
3
4
5
6
class Solution {
public:
bool stoneGame(vector<int>& piles) {
return true;
}
};

Leetcode881. Boats to Save People

You are given an array people where people[i] is the weight of the ith person, and an infinite number of boats where each boat can carry a maximum weight of limit. Each boat carries at most two people at the same time, provided the sum of the weight of those people is at most limit.

Return the minimum number of boats to carry every given person.

Example 1:

1
2
3
Input: people = [1,2], limit = 3
Output: 1
Explanation: 1 boat (1, 2)

Example 2:

1
2
3
Input: people = [3,2,2,1], limit = 3
Output: 3
Explanation: 3 boats (1, 2), (2) and (3)

Example 3:

1
2
3
Input: people = [3,5,3,4], limit = 5
Output: 4
Explanation: 4 boats (3), (3), (4), (5)

这道题让我们载人过河,说是每个人的体重不同,每条船承重有个限度 limit(限定了这个载重大于等于最重人的体重),同时要求每条船不能超过两人,问我们将所有人载到对岸最少需要多少条船。从题目中的例子2可以看出,最肥的人有可能一人占一条船,当然如果船的载量够大的话,可能还能挤上一个瘦子,那么最瘦的人是最可能挤上去的,所以策略就是胖子加瘦子的上船组合。那么这就是典型的贪婪算法的适用场景啊,首先要给所有人按体重排个序,从瘦子到胖子,这样我们才能快速的知道当前最重和最轻的人。然后使用双指针,left 指向最瘦的人,right 指向最胖的人,当 left 小于等于 right 的时候,进行 while 循环。在循环中,胖子是一定要上船的,所以 right 自减1是肯定有的,但是还是要看能否再带上一个瘦子,能的话 left 自增1。然后结果 res 一定要自增1,因为每次都要用一条船,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int numRescueBoats(vector<int>& people, int limit) {
int n = people.size(), res = 0;
int left = 0, right = n-1;
sort(people.begin(), people.end());
while(left <= right) {
if (people[left] + people[right] > limit)
right --;
else {
right --;
left ++;
}
res ++;
}
return res;
}
};

Leetcode883. Projection Area of 3D Shapes

On a N * N grid, we place some 1 * 1 * 1 cubes that are axis-aligned with the x, y, and z axes. Each value v = grid[i][j] represents a tower of v cubes placed on top of grid cell (i, j). Now we view the projection of these cubes onto the xy, yz, and zx planes. A projection is like a shadow, that maps our 3 dimensional figure to a 2 dimensional plane.

Here, we are viewing the “shadow” when looking at the cubes from the top, the front, and the side. Return the total area of all three projections.

Example 1:

1
2
Input: [[2]]
Output: 5

Example 2:

1
2
3
4
Input: [[1,2],[3,4]]
Output: 17
Explanation:
Here are the three projections ("shadows") of the shape made with each axis-aligned plane.

Example 3:

1
2
Input: [[1,0],[0,2]]
Output: 8

Example 4:

1
2
Input: [[1,1,1],[1,0,1],[1,1,1]]
Output: 14

Example 5:

1
2
Input: [[2,2,2],[2,1,2],[2,2,2]]
Output: 21

Note:

  • 1 <= grid.length = grid[0].length <= 50
  • 0 <= grid[i][j] <= 50

投影题,之前做过类似的,从上往下看的数量是不为零的格子数,从左往右看和从右往左看是每行(或列)最高的,求和。

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
class Solution {
public:
int projectionArea(vector<vector<int>>& grid) {
int x = grid.size();
int y = grid[0].size();
int res = 0;
int max;
for(int i = 0; i < x; i ++)
for(int j = 0; j < y; j ++)
if(grid[i][j] != 0)
res ++;
for(int i = 0; i < x; i ++){
max = -1;
for(int j = 0; j < y; j ++)
if(grid[i][j] > max)
max = grid[i][j];
res += max;
}
for(int i = 0; i < y; i ++){
max = -1;
for(int j = 0; j < x; j ++)
if(grid[j][i] > max)
max = grid[j][i];
res += max;
}
return res;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int projectionArea(vector<vector<int>>& grid) {
int N = grid.size();
int ans = 0;

for (int i = 0; i < N; ++i) {
int bestRow = 0; // largest of grid[i][j]
int bestCol = 0; // largest of grid[j][i]
for (int j = 0; j < N; ++j) {
if (grid[i][j] > 0) ans++; // top shadow
bestRow = max(bestRow, grid[i][j]);
bestCol = max(bestCol, grid[j][i]);
}
ans += bestRow + bestCol;
}

return ans;
}
};

Leetcode884. Uncommon Words from Two Sentences

We are given two sentences A and B. (A sentence is a string of space separated words. Each word consists only of lowercase letters.)

A word is uncommon if it appears exactly once in one of the sentences, and does not appear in the other sentence.

Return a list of all uncommon words.

You may return the list in any order.

Example 1:

1
2
Input: A = "this apple is sweet", B = "this apple is sour"
Output: ["sweet","sour"]

Example 2:

1
2
Input: A = "apple apple", B = "banana"
Output: ["banana"]

Note:

  1. 0 <= A.length <= 200
  2. 0 <= B.length <= 200
  3. A and B both contain only spaces and lowercase letters.

把单词合并然后找到只出现过一次的就好。

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
class Solution {
public:
vector<string> uncommonFromSentences(string A, string B) {
vector<string> result;
map<string, int> mapp;
string temp;
for(int i = 0; i < A.length(); i ++) {
temp = "";
while(i < A.length() && A[i] != ' ')
temp += A[i++];
cout<<temp<<endl;
if(mapp.find(temp) == mapp.end())
mapp[temp] = 1;
else
mapp[temp] ++;
}
for(int i = 0; i < B.length(); i ++) {
temp = "";
while(i < B.length() && B[i] != ' ')
temp += B[i++];
cout<<temp<<endl;
if(mapp.find(temp) == mapp.end())
mapp[temp] = 1;
else
mapp[temp] ++;
}
for(auto iter = mapp.begin(); iter != mapp.end(); iter ++) {
if(iter->second == 1)
result.push_back(iter->first);
}
return result;

}
};

简洁做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
vector<string> uncommonFromSentences(string A, string B) {
unordered_map<string, int> count;
istringstream iss(A + " " + B);
while (iss >> A) count[A]++;
vector<string> res;
for (auto w: count)
if (w.second == 1)
res.push_back(w.first);
return res;
}
};

Leetcode885. Spiral Matrix III

On a 2 dimensional grid with R rows and C columns, we start at (r0, c0) facing east. Here, the north-west corner of the grid is at the first row and column, and the south-east corner of the grid is at the last row and column. Now, we walk in a clockwise spiral shape to visit every position in this grid.

Whenever we would move outside the boundary of the grid, we continue our walk outside the grid (but may return to the grid boundary later.) Eventually, we reach all R * C spaces of the grid. Return a list of coordinates representing the positions of the grid in the order they were visited.

Example 1:

1
2
Input: R = 1, C = 4, r0 = 0, c0 = 0
Output: [[0,0],[0,1],[0,2],[0,3]]

Example 2:

1
2
Input: R = 5, C = 6, r0 = 1, c0 = 4
Output: [[1,4],[1,5],[2,5],[2,4],[2,3],[1,3],[0,3],[0,4],[0,5],[3,5],[3,4],[3,3],[3,2],[2,2],[1,2],[0,2],[4,5],[4,4],[4,3],[4,2],[4,1],[3,1],[2,1],[1,1],[0,1],[4,0],[3,0],[2,0],[1,0],[0,0]]

给定起点(r0,c0),螺旋走路,输出经过的点坐标,简单,只是注意细节。按照顺序,先向右走,再向下走,再向左走,再向上走,经观察发现每个方向的步数依次为1,1,2,2,3,3,…,依次类推,按照步骤走即可,发现不在网格内就跳过。

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
class Solution {
public:

int dir[4][4]={{0,1},{1,0},{0,-1},{-1,0}};

vector<vector<int>> spiralMatrixIII(int R, int C, int r0, int c0) {
vector<vector<int>> res;
res.push_back({r0, c0});

for(int i = 1; res.size() < R * C; i += 2){
for(int j = 0; j < 2; j ++){
for (int k = 0; k < i; k++) {
r0 += dir[j][0];
c0 += dir[j][1];
if(0 <= r0 && r0 < R && 0 <= c0 && c0 < C)
res.push_back({r0,c0});
}
}
for(int j = 2; j < 4; j ++){
for (int k = 0; k < i+1; k++) {
r0 += dir[j][0];
c0 += dir[j][1];
if(0 <= r0 && r0 < R && 0 <= c0 && c0 < C)
res.push_back({r0,c0});
}
}
}
return res;
}
};

一个大佬给了三种做法:

这道题给了我们一个二维矩阵,还给了其中一个位置,让从这个位置开始螺旋打印矩阵。首先是打印给定的位置,然后向右走一位,打印出来,再向下方走一位打印,再向左边走两位打印,再向上方走三位打印,以此类推,螺旋打印。那仔细观察,可以发现,刚开始只是走一步,后来步子越来越大,若只看每个方向走的距离,可以得到如下数组 1,1,2,2,3,3…

步长有了,下面就是方向了,由于确定了起始是向右走,那么方向就是 右->下->左->上 这样的循环。方向和步长都分析清楚了,现在就可以尝试进行遍历了。由于最终是会遍历完所有的位置的,那么最后结果 res 里面的位置个数一定是等于 RxC 的,所以循环的条件就是当结果 res 中的位置数小于R*C。我们还需要一个变量 step 表示当前的步长,初始化为1。

在循环中,首先要向右走 step 步,一步一步走,走到一个新的位置上,要进行判断,若当前位置没有越界,才能加入结果 res 中,由于每次都要判断,所以把这部分抽取出来,放到一个子函数中。由于是向右走,每走一步之后,c0 都要自增1。右边走完了之后,再向下方走 step 步,同理,每走一步之后,要将 r0 自增1。再向左边走之前,要将步数增1,不然无法形成正确的螺旋,同理,再完成向上方走 step 步之后,step 要再增1,参见代码如下:

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
vector<vector<int>> spiralMatrixIII(int R, int C, int r0, int c0) {
vector<vector<int>> res;
int step = 1;
while (res.size() < R * C) {
for (int i = 0; i < step; ++i) add(R, C, r0, c0++, res);
for (int i = 0; i < step; ++i) add(R, C, r0++, c0, res);
++step;
for (int i = 0; i < step; ++i) add(R, C, r0, c0--, res);
for (int i = 0; i < step; ++i) add(R, C, r0--, c0, res);
++step;
}
return res;
}
void add(int R, int C, int x, int y, vector<vector<int>>& res) {
if (x >= 0 && x < R && y >= 0 && y < C) res.push_back({x, y});
}
};

可以用两个数组 dirX 和 dirY 来控制下一个方向,就像迷宫遍历中的那样,这样只需要一个变量 cur,来分别到 dirX 和 dirY 中取值,初始化为0,表示向右的方向。从螺旋遍历的机制可以看出,每当向右或者向左前进时,步长就要加1,那么我们只要判断当 cur 为0或者2的时候,step 就自增1。由于 cur 初始化为0,所以刚开始 step 就会增1,那么就可以将 step 初始化为0,同时还需要把起始位置提前加入结果 res 中。此时在 while 循环中只需要一个 for 循环即可,朝当前的 cur 方向前进 step 步,r0 加上 dirX[cur],c0 加上 dirY[cur],若没有越界,则加入结果 res 中即可。之后记得 cur 要自增1,为了防止越界,对4取余,就像循环数组一样的操作,参见代码如下:

解法二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
vector<vector<int>> spiralMatrixIII(int R, int C, int r0, int c0) {
vector<vector<int>> res{{r0, c0}};
vector<int> dirX{0, 1, 0, -1}, dirY{1, 0, -1, 0};
int step = 0, cur = 0;
while (res.size() < R * C) {
if (cur == 0 || cur == 2) ++step;
for (int i = 0; i < step; ++i) {
r0 += dirX[cur]; c0 += dirY[cur];
if (r0 >= 0 && r0 < R && c0 >= 0 && c0 < C) res.push_back({r0, c0});
}
cur = (cur + 1) % 4;
}
return res;
}
};

我们也可以不使用方向数组,若仔细观察 右->下->左->上 四个方向对应的值 (0, 1) -> (1, 0) -> (0, -1) -> (-1, 0), 实际上,下一个位置的x值是当前的y值,下一个位置的y值是当前的-x值,因为两个方向是相邻的两个方向是垂直的,由向量的叉乘得到 (x, y, 0) × (0, 0, 1) = (y, -x, 0)。所以可以通过当前的x和y值,来计算出下一个位置的值。同理,根据之前的说的步长数组 1,1,2,2,3,3…,可以推出通项公式为 n/2 + 1,这样连步长变量 step 都省了,不过需要统计当前已经遍历的位置的个数,实在想偷懒,也可以用 res.size() 来代替,参见代码如下:

解法三:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
vector<vector<int>> spiralMatrixIII(int R, int C, int r0, int c0) {
vector<vector<int>> res{{r0, c0}};
int x = 0, y = 1, t = 0;
for (int k = 0; res.size() < R * C; ++k) {
for (int i = 0; i < k / 2 + 1; ++i) {
r0 += x; c0 += y;
if (r0 >= 0 && r0 < R && c0 >= 0 && c0 < C) res.push_back({r0, c0});
}
t = x; x = y; y = -t;
}
return res;
}
};

LeetCode886. Possible Bipartition

Given a set of N people (numbered 1, 2, …, N), we would like to split everyone into two groups of any size.

Each person may dislike some other people, and they should not go into the same group.

Formally, if dislikes[i] = [a, b], it means it is not allowed to put the people numbered a and b into the same group.

Return true if and only if it is possible to split everyone into two groups in this way.

Example 1:

1
2
3
Input: N = 4, dislikes = [[1,2],[1,3],[2,4]]
Output: true
Explanation: group1 [1,4], group2 [2,3]

Example 2:

1
2
Input: N = 3, dislikes = [[1,2],[1,3],[2,3]]
Output: false

Example 3:

1
2
Input: N = 5, dislikes = [[1,2],[2,3],[3,4],[4,5],[1,5]]
Output: false

Note:

  • 1 <= N <= 2000
  • 0 <= dislikes.length <= 10000
  • 1 <= dislikes[i][j] <= N
  • dislikes[i][0] < dislikes[i][1]
  • There does not exist i != j for which dislikes[i] == dislikes[j].

这道题又是关于二分图的题,第一次接触的时候是 Is Graph Bipartite?,那道题给的是建好的邻接链表(虽然是用数组实现的),但是本质上和这道题是一样的,同一条边上的两点是不能在同一个集合中的,那么这就相当于本题中的 dislike 的关系,也可以把每个 dislike 看作是一条边,那么两端的两个人不能在同一个集合中。看透了题目的本质后,就不难做了,跟之前的题相比,这里唯一不同的就是邻接链表没有给我们建好,需要自己去建。不管是建邻接链表,还是邻接矩阵都行,反正是要先把图建起来才能遍历。那么这里我们先建立一个邻接矩阵好了,建一个大小为 (N+1) x (N+1) 的二维数组g,其中若 g[i][j] 为1,说明i和j互相不鸟。那么先根据 dislikes 的情况,把二维数组先赋上值,注意这里 g[i][j] 和 g[j][i] 都要更新,因为是互相不鸟,而并不是某一方热脸贴冷屁股。下面就要开始遍历了,还是使用染色法,使用一个一维的 colors 数组,大小为 N+1,初始化是0,由于只有两组,可以用1和 -1 来区分。那么开始遍历图中的结点,对于每个遍历到的结点,如果其还未被染色,还是一张白纸的时候,调用递归函数对其用颜色1进行尝试染色。在递归函数中,现将该结点染色,然后就要遍历所有跟其合不来的人,这里就发现邻接矩阵的好处了吧,不然每次还得遍历 dislikes 数组。由于这里是邻接矩阵,所以只有在其值为1的时候才处理,当找到一个跟其合不来的人,首先检测其染色情况,如果此时两个人颜色相同了,说明已经在一个组里了,这就矛盾了,直接返回 false。如果那个人还是白纸一张,我们尝试用相反的颜色去染他,如果无法成功染色,则返回 false。循环顺序退出后,返回 true,参见代码如下:

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
class Solution {
public:
bool possibleBipartition(int n, vector<vector<int>>& dislikes) {
vector<vector<int> > g(n+1, vector<int>(n+1, 0));
vector<int> color(n+1, 0);
for (int i = 0; i < dislikes.size(); i ++) {
g[dislikes[i][0]][dislikes[i][1]] = 1;
g[dislikes[i][1]][dislikes[i][0]] = 1;
}

for(int i = 1; i <= n; i ++)
if (color[i] == 0 && !helper(g, i, color, 1))
return false;
return true;
}

bool helper(vector<vector<int> >& g, int cur, vector<int>& color, int col) {
color[cur] = col;
for (int i = 0; i < g.size(); i ++) {
if (g[cur][i] == 1) {
if (color[i] == col)
return false;
if (color[i] == 0 && !helper(g, i, color, -col))
return false;
}
}
return true;
}
};

Leetcode888. Fair Candy Swap

Alice and Bob have candy bars of different sizes: A[i] is the size of the i-th bar of candy that Alice has, and B[j] is the size of the j-th bar of candy that Bob has.

Since they are friends, they would like to exchange one candy bar each so that after the exchange, they both have the same total amount of candy. (The total amount of candy a person has is the sum of the sizes of candy bars they have.)

Return an integer array ans where ans[0] is the size of the candy bar that Alice must exchange, and ans[1] is the size of the candy bar that Bob must exchange.

If there are multiple answers, you may return any one of them. It is guaranteed an answer exists.

Example 1:

1
2
Input: A = [1,1], B = [2,2]
Output: [1,2]

Example 2:

1
2
Input: A = [1,2], B = [2,3]
Output: [1,2]

Example 3:

1
2
Input: A = [2], B = [1,3]
Output: [2,3]

Example 4:

1
2
Input: A = [1,2,5], B = [2,4]
Output: [5,4]

Note:

  • 1 <= A.length <= 10000
  • 1 <= B.length <= 10000
  • 1 <= A[i] <= 100000
  • 1 <= B[i] <= 100000
  • It is guaranteed that Alice and Bob have different total amounts of candy.
  • It is guaranteed there exists an answer.

考虑到最终两个人的糖果总量相等,那么可以计算出最终这个相等的总量是多少。

比如1中的例子,A的总量是8,B的总量是6,那么平均下来每个人应该是7。

那么接下来就要在A中找到一个元素比B中某个元素大1的,逐个对比,可以发现交换5和4就可以达成目标。

其中逐个对比这个部分,难道我们要做一个双重循环吗?也没有必要。

我们先做一个升序排序,接着就是两个指针在A中和B中不断地移动就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
vector<int> fairCandySwap(vector<int>& A, vector<int>& B)
{
sort(A.begin(),A.end());//升序排序
sort(B.begin(),B.end());//升序排序
int sum1=0,sum2=0,sum3,cha,cha1;
for(int i:A)//sum1存储A中的总量
sum1+=i;
for(int i:B)//sum2存储B中的总量
sum2+=i;
sum3=(sum1+sum2)/2;//sum3是平均值
cha=sum1-sum3;//cha表示A和平均值之间的差,如果大于0,说明A要在B中找一个小cha这个数值的,如果小于0,同理
int i=0,j=0;
while(i<A.size()&&j<B.size())//i和j两个索引不断地向后走
{
cha1=A[i]-B[j];
if(cha1==cha)//如果刚好等于,那么返回两个数值
return {A[i],B[j]};
else if(cha1<cha)//如果小于,那么说明A[i]数值太小,应该更大一点
i++;
else //如果大于,那么说明B[j]数值太小,应该更大一点
j++;
}
}

Leetcode889. Construct Binary Tree from Preorder and Postorder Traversal

Return any binary tree that matches the given preorder and postorder traversals.

Values in the traversals pre and post are distinct positive integers.

Example 1:

1
2
Input: pre = [1,2,4,5,3,6,7], post = [4,5,2,6,7,3,1]
Output: [1,2,3,4,5,6,7]

Note:

  • 1 <= pre.length == post.length <= 30
  • pre[] and post[] are both permutations of 1, 2, …, pre.length.
  • It is guaranteed an answer exists. If there exists multiple answers, you can return any of them.

这道题给了一棵树的先序遍历和后序遍历的数组,让我们根据这两个数组来重建出原来的二叉树。之前也做过二叉树的先序遍历 Binary Tree Preorder Traversal 和 后序遍历 Binary Tree Postorder Traversal,所以应该对其遍历的顺序并不陌生。其实二叉树最常用的三种遍历方式,先序,中序,和后序遍历,只要知道其中的任意两种遍历得到的数组,就可以重建出原始的二叉树,而且正好都在 LeetCode 中有出现,其他两道分别是 Construct Binary Tree from Inorder and Postorder Traversal 和 Construct Binary Tree from Preorder and Inorder Traversal。如果做过之前两道题,那么这道题就没有什么难度了,若没有的话,可能还是有些 tricky 的,虽然这仅仅只是一道 Medium 的题。

我们知道,先序遍历的顺序是 根->左->右,而后序遍历的顺序是 左->右->根,既然要建立树,那么肯定要从根结点开始创建,然后再创建左右子结点,若你做过很多树相关的题目的话,就会知道大多数都是用递归才做,那么创建的时候也是对左右子结点调用递归来创建。心中有这么个概念就好,可以继续来找这个重复的 pattern。由于先序和后序各自的特点,根结点的位置是固定的,既是先序遍历数组的第一个,又是后序遍历数组的最后一个,而如果给我们的是中序遍历的数组,那么根结点的位置就只能从另一个先序或者后序的数组中来找了,但中序也有中序的好处,其根结点正好分割了左右子树,就不在这里细讲了,还是回到本题吧。知道了根结点的位置后,我们需要分隔左右子树的区间,先序和后序的各个区间表示如下:

  • preorder -> [root] [left subtree] [right subtree]
  • postorder -> [left subtree] [right substree] [root]

具体到题目中的例子就是:

  • preorder -> [1] [2,4,5] [3,6,7]
  • postorder -> [4,5,2] [6,7,3] [root]

先序和后序中各自的左子树区间的长度肯定是相等的,但是其数字顺序可能是不同的,但是我们仔细观察的话,可以发现先序左子树区间的第一个数字2,在后序左右子树区间的最后一个位置,而且这个规律对右子树区间同样适用,这是为啥呢,这就要回到各自遍历的顺序了,先序遍历的顺序是 根->左->右,而后序遍历的顺序是 左->右->根,其实这个2就是左子树的根结点,当然会一个在开头,一个在末尾了。发现了这个规律,就可以根据其来定位左右子树区间的位置范围了。既然要拆分数组,那么就有两种方式,一种是真的拆分成小的子数组,另一种是用双指针来指向子区间的开头和末尾。前一种方法无疑会有大量的数组拷贝,不是很高效,所以我们这里采用第二种方法来做。用 preL 和 preR 分别表示左子树区间的开头和结尾位置,postL 和 postR 表示右子树区间的开头和结尾位置,那么若 preL 大于 preR 或者 postL 大于 postR 的时候,说明已经不存在子树区间,直接返回空指针。然后要先新建当前树的根结点,就通过 pre[preL] 取到即可,接下来要找左子树的根结点在 post 中的位置,最简单的方法就是遍历 post 中的区间 [postL, postR],找到其位置 idx,然后根据这个 idx,就可以算出左子树区间长度为 len = (idx-postL)+1,那么 pre 数组中左子树区间为 [preL+1, preL+len],右子树区间为 [preL+1+len, preR],同理,post 数组中左子树区间为 [postL, idx],右子树区间为 [idx+1, postR-1]。知道了这些信息,就可以分别调用递归函数了,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
TreeNode* constructFromPrePost(vector<int>& pre, vector<int>& post) {
return helper(pre, 0, (int)pre.size() - 1, post, 0, (int)post.size() - 1);
}
TreeNode* helper(vector<int>& pre, int preL, int preR, vector<int>& post, int postL, int postR) {
if (preL > preR || postL > postR) return nullptr;
TreeNode *node = new TreeNode(pre[preL]);
if (preL == preR) return node;
int idx = -1;
for (idx = postL; idx <= postR; ++idx) {
if (pre[preL + 1] == post[idx]) break;
}
node->left = helper(pre, preL + 1, preL + 1 + (idx - postL), post, postL, idx);
node->right = helper(pre, preL + 1 + (idx - postL) + 1, preR, post, idx + 1, postR - 1);
return node;
}
};

Leetcode890. Find and Replace Pattern

You have a list of words and a pattern, and you want to know which words in words matches the pattern.

A word matches the pattern if there exists a permutation of letters p so that after replacing every letter x in the pattern with p(x), we get the desired word.

(Recall that a permutation of letters is a bijection from letters to letters: every letter maps to another letter, and no two letters map to the same letter.)

Return a list of the words in words that match the given pattern.

You may return the answer in any order.

Example 1:

1
2
3
4
5
Input: words = ["abc","deq","mee","aqq","dkd","ccc"], pattern = "abb"
Output: ["mee","aqq"]
Explanation: "mee" matches the pattern because there is a permutation {a -> m, b -> e, ...}.
"ccc" does not match the pattern because {a -> c, b -> c, ...} is not a permutation,
since a and b map to the same letter.

Note:

  • 1 <= words.length <= 50
  • 1 <= pattern.length = words[i].length <= 20
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
class Solution {
public:

bool match(string word,string pattern){
int i,j;
bool seen[26];
map<char,char> table;
map<char, char>::iterator it;
for(i=0;i<26;i++)
seen[i]=false;
for(j=0;j<word.length();j++){
it = table.find(word[j]);
if(it==table.end()){
table[word[j]]=pattern[j];
}
if(table[word[j]]!=pattern[j] )
return false;
}
for(i=0;i<26;i++)
seen[i]=false;
for(it=table.begin();it!=table.end();it++){
if(seen[it->second-'a'])
return false;
seen[it->second-'a']=true;
}
return true;
}


vector<string> findAndReplacePattern(vector<string>& words, string pattern) {
vector<string> res;
int i,j;

for(i=0;i<words.size();i++){
if(match(words[i],pattern))
res.push_back(words[i]);
}
return res;
}
};

这个题判断给定的字符串是不是符合pattern串的模式,很简单的题搞复杂了。用了一个map来匹配字符组合,用seen判断这个pattern字符是否出现过,如果出现过就是非法的了。

Leetcode892. Surface Area of 3D Shapes

On a N * N grid, we place some 1 * 1 * 1 cubes.

Each value v = grid[i][j] represents a tower of v cubes placed on top of grid cell (i, j).

Return the total surface area of the resulting shapes.

Example 1:

1
2
Input: [[2]]
Output: 10

Example 2:

1
2
Input: [[1,2],[3,4]]
Output: 34

Example 3:

1
2
Input: [[1,0],[0,2]]
Output: 16

Example 4:

1
2
Input: [[1,1,1],[1,0,1],[1,1,1]]
Output: 32

Example 5:

1
2
Input: [[2,2,2],[2,1,2],[2,2,2]]
Output: 46

Note:

  • 1 <= N <= 50
  • 0 <= grid[i][j] <= 50

这道题给了我们一个二维数组 grid,其中 grid[i][j] 表示在位置 (i,j) 上累计的小正方体的个数,实际上就像搭积木一样,由这些小正方体来组成一个三维的物体,这里让我们求这个三维物体的表面积。我们知道每个小正方体的表面积是6,若在同一个位置累加两个,表面积就是10,三个累加到了一起就是14,其实是有规律的,n个小正方体累在一起,表面积是 4n+2。

现在不仅仅是累加在一个小正方体上,而是在 nxn 的区间,累加出一个三维物体。当中间的小方块缺失了之后,实际上缺失的地方会产生出四个新的面,而这四个面是应该算在表面积里的,但是用投影的方法是没法算进去的。无奈只能另辟蹊径,实际上这道题正确的思路是一个位置一个位置的累加表面积,就类似微积分的感觉,前面提到了当n个小正方体累到一起的表面积是 4n+2,而这个n就是每个位置的值 grid[i][j],当你在旁边紧挨着再放一个累加的物体时,二者就会产生重叠,重叠的面数就是二者较矮的那堆正方体的个数再乘以2。

明白了这一点,我们就可以从 (0,0) 位置开始累加,先根据 grid[0][0] 的值算出若仅有该位置的三维物体的表面积,然后向 (0,1) 位置遍历,同样要先根据 grid[0][1] 的值算出若仅有该位置的三维物体的表面积,跟之前 grid[0][0] 的累加,然后再减去遮挡住的面积,通过 min(grid[0][0],grid[0][1])x2 来得到,这样每次可以计算出水平方向的遮挡面积,同时还需要减去竖直方向的遮挡面积 min(grid[i][j],grid[i-1][j])x2,这样才能算出正确的表面积.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int surfaceArea(vector<vector<int>>& grid) {
int n =grid.size(), res = 0;
for(int i = 0 ; i < n; i ++)
for(int j = 0; j < n; j ++) {
if(grid[i][j] > 0) {
res += 4 * grid[i][j] + 2;
if(i > 0) res -= min(grid[i][j], grid[i-1][j]) * 2;
if(j > 0) res -= min(grid[i][j], grid[i][j-1]) * 2;
}
}
return res;
}
};

Leetcode893. Groups of Special-Equivalent Strings

You are given an array A of strings.

A move onto S consists of swapping any two even indexed characters of S, or any two odd indexed characters of S.

Two strings S and T are special-equivalent if after any number of moves onto S, S == T.

For example, S = “zzxy” and T = “xyzz” are special-equivalent because we may make the moves “zzxy” -> “xzzy” -> “xyzz” that swap S[0] and S[2], then S[1] and S[3].

Now, a group of special-equivalent strings from A is a non-empty subset of A such that:

Every pair of strings in the group are special equivalent, and;
The group is the largest size possible (ie., there isn’t a string S not in the group such that S is special equivalent to every string in the group)
Return the number of groups of special-equivalent strings from A.

Example 1:

1
2
3
4
5
6
Input: ["abcd","cdab","cbad","xyzz","zzxy","zzyx"]
Output: 3
Explanation:
One group is ["abcd", "cdab", "cbad"], since they are all pairwise special equivalent, and none of the other strings are all pairwise special equivalent to these.

The other two groups are ["xyzz", "zzxy"] and ["zzyx"]. Note that in particular, "zzxy" is not special equivalent to "zzyx".

Example 2:

1
2
Input: ["abc","acb","bac","bca","cab","cba"]
Output: 3

对于一个字符串,假如其偶数位字符之间可以互相交换,且其奇数位字符之间可以互相交换,交换后若能跟另一个字符串相等,则这两个字符串是特殊相等的关系。现在给了我们一个字符串数组,将所有特殊相等的字符串放到一个群组中,问最终能有几个不同的群组。最开始的时候博主没仔细审题,以为是随意交换字母,就直接对每个单词进行排序,然后扔到一个 HashSet 中就行了。后来发现只能是奇偶位上互相交换,于是只能现先将奇偶位上的字母分别抽离出来,然后再进行分别排序,之后再合并起来组成一个新的字符串,再丢到 HashSet 中即可,利用 HashSet 的自动去重复功能,这样最终留下来的就是不同的群组了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int numSpecialEquivGroups(vector<string>& A) {
unordered_set<string> st;
for (string word : A) {
string even, odd;
for (int i = 0; i < word.size(); ++i)
if (i % 2 == 0) even += word[i];
else odd += word[i];

sort(even.begin(), even.end());
sort(odd.begin(), odd.end());
st.insert(even + odd);
}
return st.size();
}
};

Leetcode894. All Possible Full Binary Trees

A full binary tree is a binary tree where each node has exactly 0 or 2 children.

Return a list of all possible full binary trees with N nodes. Each element of the answer is the root node of one possible tree.

Each node of each tree in the answer must have node.val = 0.

You may return the final list of trees in any order.

Example 1:

1
2
Input: 7
Output: [[0,0,0,null,null,0,0,null,null,0,0],[0,0,0,null,null,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,null,null,null,null,0,0],[0,0,0,0,0,null,null,0,0]]

Explanation:

给出了个N,代表一棵二叉树有N个节点,求所能构成的树。

解题方法
所有能构成的树,并且返回的不是数目,而是真正的树。所以一定会把所有的节点都求出来。一般就使用了递归。

这个题中,重点是返回一个列表,也就是说每个能够成的树的根节点都要放到这个列表里。而且当左子树、右子树的节点个数固定的时候,也会出现排列组合的情况,所以使用了两重for循环来完成所有的左右子树的组合。

另外的一个技巧就是,左右子树的个数一定是奇数个。

递归方法,虽然比较慢,但是容易理解,就是组成小的子树,一个个拼接,为啥要减1,是因为一定会有个根节点,先把这个减去再说。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
vector<TreeNode*> allPossibleFBT(int N) {
N--;
vector<TreeNode*> res;
if(N==0){
res.push_back(new TreeNode(0));
return res;
}
for (int i = 1; i < N; i += 2) {
for (auto& left : allPossibleFBT(i)) {
for (auto& right : allPossibleFBT(N - i)) {
TreeNode* root = new TreeNode(0);
root->left = left;
root->right = right;
res.push_back(root);
}
}
}
return res;
}
};

Leetcode896. Monotonic Array

An array is monotonic if it is either monotone increasing or monotone decreasing.

An array A is monotone increasing if for all i <= j, A[i] <= A[j]. An array A is monotone decreasing if for all i <= j, A[i] >= A[j].

Return true if and only if the given array A is monotonic.

判断数组是否单调。

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
bool isMonotonic(vector<int>& A) {
bool inc=true, dec=true;
for(int i = 0; i < A.size()-1; i ++) {
if(A[i] > A[i+1]) inc = false;
if(A[i] < A[i+1]) dec = false;
}
return inc || dec;
}
};

Leetcode897. Increasing Order Search Tree

Given a binary search tree, rearrange the tree in in-order so that the leftmost node in the tree is now the root of the tree, and every node has no left child and only 1 right child.

Example 1:
Input: [5,3,6,2,4,null,8,1,null,null,null,7,9]

1
2
3
4
5
6
7
       5
/ \
3 6
/ \ \
2 4 8
/ / \
1 7 9

Output: [1,null,2,null,3,null,4,null,5,null,6,null,7,null,8,null,9]

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

Note:

The number of nodes in the given tree will be between 1 and 100.
Each node will have a unique integer value from 0 to 1000.

本题要求把二叉树的结点重新排列,使其成为从小到大只有右孩子的二叉树。考虑使用中序遍历的迭代方法,对每个结点入栈,出栈时先访问左结点,然后中结点,最后把右指针和下一个入栈的结点链接起来。

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
class Solution {
public:

TreeNode* increasingBST(TreeNode* root) {
if(root==NULL)
return NULL;
if(!root->left && !root->right)
return root;
stack<TreeNode*> dst;
TreeNode *head = new TreeNode(0), *pre = head;
while(root || !dst.empty()){
while(root){
dst.push(root);
root = root->left;
}
root = dst.top();
dst.pop();
pre->right=root;
pre = pre -> right;
root -> left = NULL;
root=root->right;
}
return head->right;

}
};

然后朴素的中序遍历是这样

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
class Solution {
public:

TreeNode *s=NULL,*p=NULL;

void inorder(TreeNode* root){
if(root){
inorder(root->left);

if(s == NULL) {
s = new TreeNode(root->val);
p = s;
}
else{
TreeNode* temp = new TreeNode(root->val);
s->right = temp;
s = s->right;
}

inorder(root->right);
}
}

TreeNode* increasingBST(TreeNode* root) {
if(root==NULL)
return NULL;
inorder(root);

return p;
}
};

Leetcode900. RLE Iterator

Write an iterator that iterates through a run-length encoded sequence.

The iterator is initialized by RLEIterator(int[] A), where A is a run-length encoding of some sequence. More specifically, for all even i, A[i] tells us the number of times that the non-negative integer value A[i+1] is repeated in the sequence.

The iterator supports one function: next(int n), which exhausts the next n elements (n >= 1) and returns the last element exhausted in this way. If there is no element left to exhaust, next returns -1instead.

For example, we start with A = [3,8,0,9,2,5], which is a run-length encoding of the sequence [8,8,8,5,5]. This is because the sequence can be read as “three eights, zero nines, two fives”.

Example 1:

1
2
3
4
5
6
7
8
9
10
11
Input: ["RLEIterator","next","next","next","next"], [[[3,8,0,9,2,5]],[2],[1],[1],[2]]
Output: [null,8,8,5,-1]
Explanation:
RLEIterator is initialized with RLEIterator([3,8,0,9,2,5]).
This maps to the sequence [8,8,8,5,5].
RLEIterator.next is then called 4 times:

.next(2) exhausts 2 terms of the sequence, returning 8. The remaining sequence is now [8, 5, 5].
.next(1) exhausts 1 term of the sequence, returning 8. The remaining sequence is now [5, 5].
.next(1) exhausts 1 term of the sequence, returning 5. The remaining sequence is now [5].
.next(2) exhausts 2 terms, returning -1. This is because the first term exhausted was 5, but the second term did not exist. Since the last term exhausted does not exist, we return -1.

Note:

  • 0 <= A.length <= 1000
  • A.length is an even integer.
  • 0 <= A[i] <= 10^9

这道题给了我们一种 Run-Length Encoded 的数组,就是每两个数字组成一个数字对儿,前一个数字表示后面的一个数字重复出现的次数。然后有一个 next 函数,让我们返回数组的第n个数字,题目中给的例子也很好的说明了题意。将每个数字对儿抽离出来,放到一个新的数组中。这样我们就只要遍历这个只有数字对儿的数组,当出现次数是0的时候,直接跳过当前数字对儿。若出现次数大于等于n,那么现将次数减去n,然后再返回该数字。否则用n减去次数,并将次数赋值为0,继续遍历下一个数字对儿。若循环退出了,直接返回 -1 即可,参见代码如下:

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
class RLEIterator {
public:
vector<pair<int, int>> m;
int pointer_en, pointer_m;
RLEIterator(vector<int>& encoding) {
int len = encoding.size();
pointer_en = 0;
pointer_m = 0;
for (int i = 0; i < len; i += 2)
if (encoding[i] > 0)
m.push_back(make_pair(encoding[i], encoding[i+1]));
}

int next(int n) {
int len = m.size();
while(pointer_m < len) {
if (m[pointer_m].first < n) {
n -= m[pointer_m].first;
pointer_m ++;
}
else {
m[pointer_m].first -= n;
break;
}
}
if (pointer_m < len)
return m[pointer_m].second;
else
return -1;
}
};

其实我们根本不用将数字对儿抽离出来,直接用输入数组的形式就可以,再用一个指针 cur,指向当前数字对儿的次数即可。那么在 next 函数中,我们首先来个 while 循环,判读假如 cur 没有越界,且当n大于当前当次数了,则n减去当前次数,cur 自增2,移动到下一个数字对儿的次数上。当 while 循环结束后,判断若此时 cur 已经越界了,则返回 -1,否则当前次数减去n,并且返回当前数字即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class RLEIterator {
public:
RLEIterator(vector& A): nums(A), cur(0) {}

int next(int n) {
while (cur < nums.size() && n > nums[cur]) {
n -= nums[cur];
cur += 2;
}
if (cur >= nums.size()) return -1;
nums[cur] -= n;
return nums[cur + 1];
}

private:
int cur;
vector nums;
};

Leetcode701. Insert into a Binary Search Tree

Given the root node of a binary search tree (BST) and a value to be inserted into the tree, insert the value into the BST. Return the root node of the BST after the insertion. It is guaranteed that the new value does not exist in the original BST.

Note that there may exist multiple valid ways for the insertion, as long as the tree remains a BST after insertion. You can return any of them.

For example,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Given the tree:
4
/ \
2 7
/ \
1 3
And the value to insert: 5
You can return this binary search tree:

4
/ \
2 7
/ \ /
1 3 5
This tree is also valid:

5
/ \
2 7
/ \
1 3
\
4

非常简单,不用详细说了,递归插入一个节点即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if(root == NULL){
root = new TreeNode(val);
return root;
}
if(val < root->val)
root->left = insertIntoBST(root->left,val);
else
root->right = insertIntoBST(root->right,val);
return root;
}
};

Leetcode703. Kth Largest Element in a Stream

Design a class to find the kth largest element in a stream. Note that it is the kth largest element in the sorted order, not the kth distinct element.

Your KthLargest class will have a constructor which accepts an integer k and an integer array nums, which contains initial elements from the stream. For each call to the method KthLargest.add, return the element representing the kth largest element in the stream.

Example:

1
2
3
4
5
6
7
8
int k = 3;
int[] arr = [4,5,8,2];
KthLargest kthLargest = new KthLargest(3, arr);
kthLargest.add(3); // returns 4
kthLargest.add(5); // returns 5
kthLargest.add(10); // returns 5
kthLargest.add(9); // returns 8
kthLargest.add(4); // returns 8

创建一个优先队列,维护最大的k个数(其实是一个小顶堆);每次add之后若size大于k则删除队列中的最小值,若小于k则不做操作。最后输出队顶元素即为所求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class KthLargest {
public:
priority_queue<int, vector<int>, greater<int>> q;
int size;

KthLargest(int k, vector<int>& nums) {
int len = nums.size();
size = k;
for(int i = 0; i < len; i ++)
add(nums[i]);
}

int add(int val) {
q.push(val);
if(q.size() > size)
q.pop();
return q.top();
}
};

Leetcode704. Binary Search

Given a sorted (in ascending order) integer array nums of n elements and a target value, write a function to search target in nums. If target exists, then return its index, otherwise return -1.

Example 1:

1
2
3
Input: nums = [-1,0,3,5,9,12], target = 9
Output: 4
Explanation: 9 exists in nums and its index is 4

Example 2:

1
2
3
Input: nums = [-1,0,3,5,9,12], target = 2
Output: -1
Explanation: 2 does not exist in nums so return -1

二分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
int search(vector<int>& nums, int target) {
if(nums.size() == 0)
return -1;
int left = 0, right = nums.size() - 1;
int mid;
while(left <= right) {
mid = left + (right - left) / 2;
if(nums[mid] < target)
left = mid + 1;
else if(nums[mid] > target)
right = mid - 1;
else
return mid;
}
if(nums[mid] == target)
return mid;
return -1;
}
};

Leetcode705. Design HashSet

Design a HashSet without using any built-in hash table libraries.

To be specific, your design should include these functions:

add(value): Insert a value into the HashSet.
contains(value) : Return whether the value exists in the HashSet or not.
remove(value): Remove a value in the HashSet. If the value does not exist in the HashSet, do nothing.

Example:

1
2
3
4
5
6
7
8
9
MyHashSet hashSet = new MyHashSet();
hashSet.add(1);
hashSet.add(2);
hashSet.contains(1); // returns true
hashSet.contains(3); // returns false (not found)
hashSet.add(2);
hashSet.contains(2); // returns true
hashSet.remove(2);
hashSet.contains(2); // returns false (already removed)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyHashSet {
public:
/** Initialize your data structure here. */
vector<bool> v;

MyHashSet() {
v.resize(1000001, 0);
}

void add(int key) {
v[key] = true;
}

void remove(int key) {
v[key] = false;
}

/** Returns true if this set contains the specified element */
bool contains(int key) {
return v[key];
}
};

Leetcode706. Design HashMap

Design a HashMap without using any built-in hash table libraries.

To be specific, your design should include these functions:

  • put(key, value) : Insert a (key, value) pair into the HashMap. If the value already exists in the HashMap, update the value.
  • get(key): Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key.
  • remove(key) : Remove the mapping for the value key if this map contains the mapping for the key.

Example:

1
2
3
4
5
6
7
8
9
MyHashMap hashMap = new MyHashMap();
hashMap.put(1, 1);
hashMap.put(2, 2);
hashMap.get(1); // returns 1
hashMap.get(3); // returns -1 (not found)
hashMap.put(2, 1); // update the existing value
hashMap.get(2); // returns 1
hashMap.remove(2); // remove the mapping for 2
hashMap.get(2); // returns -1 (not found)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class MyHashMap {
public:
vector<int> v;

/** Initialize your data structure here. */
MyHashMap() {
v.resize(1000001, -1);
}

/** value will always be non-negative. */
void put(int key, int value) {
v[key] = value;
}

/** Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key */
int get(int key) {
return v[key];
}

/** Removes the mapping of the specified value key if this map contains a mapping for the key */
void remove(int key) {
v[key] = -1;
}
};

由于存入 HashMap 的映射对儿也许不会跨度很大,那么直接就申请长度为 1000000 的数组可能会有些浪费,其实可以使用 1000 个长度为 1000 的数组来代替,那么就要用个二维数组啦,实际上开始只申请了 1000 个空数组,对于每个要处理的元素,首先对 1000 取余,得到的值就当作哈希值,对应申请的那 1000 个空数组的位置,在建立映射时,一旦计算出了哈希值,将对应的空数组 resize 为长度 1000,然后根据哈希值和 key/1000 来确定具体的加入映射值的位置。获取映射值时,计算出哈希值,若对应的数组不为空,直接返回对应的位置上的值。移除映射值一样的,先计算出哈希值,如果对应的数组不为空的话,找到对应的位置并重置为 -1。参见代码如下:

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
class MyHashMap {
public:
MyHashMap() {
data.resize(1000, vector<int>());
}
void put(int key, int value) {
int hashKey = key % 1000;
if (data[hashKey].empty()) {
data[hashKey].resize(1000, -1);
}
data[hashKey][key / 1000] = value;
}
int get(int key) {
int hashKey = key % 1000;
if (!data[hashKey].empty()) {
return data[hashKey][key / 1000];
}
return -1;
}
void remove(int key) {
int hashKey = key % 1000;
if (!data[hashKey].empty()) {
data[hashKey][key / 1000] = -1;
}
}

private:
vector<vector<int>> data;
};

Leetcode707. Design Linked List

Design your implementation of the linked list. You can choose to use the singly linked list or the doubly linked list. A node in a singly linked list should have two attributes: val and next. val is the value of the current node, and next is a pointer/reference to the next node. If you want to use the doubly linked list, you will need one more attribute prev to indicate the previous node in the linked list. Assume all nodes in the linked list are 0-indexed.

Implement these functions in your linked list class:

  • get(index) : Get the value of the index-th node in the linked list. If the index is invalid, return -1.
  • addAtHead(val) : Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list.
  • addAtTail(val) : Append a node of value val to the last element of the linked list.
  • addAtIndex(index, val) : Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. If index is negative, the node will be inserted at the head of the list.
  • deleteAtIndex(index) : Delete the index-th node in the linked list, if the index is valid.

Example:

1
2
3
4
5
6
7
MyLinkedList linkedList = new MyLinkedList();
linkedList.addAtHead(1);
linkedList.addAtTail(3);
linkedList.addAtIndex(1, 2); // linked list becomes 1->2->3
linkedList.get(1); // returns 2
linkedList.deleteAtIndex(1); // now the linked list is 1->3
linkedList.get(1); // returns 3

Note:

  • All values will be in the range of [1, 1000].
  • The number of operations will be in the range of [1, 1000].

这道题让我们实现一个链表的数据结构,看需要实现的哪些函数,分别是根据坐标取结点,在链表开头和末尾加结点,根据坐标位置加结点,根据坐标位置删除结点。既然是结点组成的链表,那么肯定不能向数组那样可以根据坐标直接访问元素,肯定至少要知道表头的位置。同时,在根据链表取结点函数说明了给定的位置可能是非法的,则也要知道链表中所有元素的个数,这样可以快速的判定给定的位置是否合法。

好,下面来看每个函数如何实现。首先来看根据坐标取结点函数,先判定 index 是否合法,然后从表头向后移动 index 个位置,找到要返回的结点即可。对于增加表头函数就比较简单了,新建一个头结点,next 连上 head,然后 head 重新指向这个新结点,同时 size 自增1。同样,对于增加表尾结点函数,首先遍历到表尾,然后在之后连上一个新建的结点,同时 size 自增1。下面是根据位置来加结点,肯定还是先来判定 index 是否合法,题目要求有过变动,新加一条说是当 index 为负数时,要在表头加个结点,这样的话只需要判断 index 是否大于 size 这一种非法情况。然后再处理一个 corner case,就是当 index 小于等于0的时候,直接调用前面的表头加结点函数即可。然后就是往后遍历 index-1 个结点,这里为啥要减1呢,因为要加入结点的话,必须要知道加入位置前面一个结点才行,链表加入结点的问题之前的题目中做过很多,这里就不说细节了,最后 size 还是要自增1。根据位置删除结点也是大同小异,没有太大的难度,参见代码如下:

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
class MyLinkedList {
public:
MyLinkedList() {
head = NULL;
size = 0;
}
int get(int index) {
if (index < 0 || index >= size) return -1;
Node *cur = head;
for (int i = 0; i < index; ++i) cur = cur->next;
return cur->val;
}
void addAtHead(int val) {
Node *t = new Node(val, head);
head = t;
++size;
}
void addAtTail(int val) {
Node *cur = head;
while (cur->next) cur = cur->next;
cur->next = new Node(val, NULL);
++size;
}
void addAtIndex(int index, int val) {
if (index > size) return;
if (index <= 0) {addAtHead(val); return;}
Node *cur = head;
for (int i = 0; i < index - 1; ++i) cur = cur->next;
Node *t = new Node(val, cur->next);
cur->next = t;
++size;
}
void deleteAtIndex(int index) {
if (index < 0 || index >= size) return;
if (index == 0) {
head = head->next;
--size;
return;
}
Node *cur = head;
for (int i = 0; i < index - 1; ++i) cur = cur->next;
cur->next = cur->next->next;
--size;
}

private:
struct Node {
int val;
Node *next;
Node(int x, Node* n): val(x), next(n) {}
};
Node *head, *tail;
int size;
};

Leetcode709. 转换成小写字母

太简单了

Leetcode712. Minimum ASCII Delete Sum for Two Strings

Given two strings s1, s2, find the lowest ASCII sum of deleted characters to make two strings equal.

Example 1:

1
2
3
4
5
Input: s1 = "sea", s2 = "eat"
Output: 231
Explanation: Deleting "s" from "sea" adds the ASCII value of "s" (115) to the sum.
Deleting "t" from "eat" adds 116 to the sum.
At the end, both strings are equal, and 115 + 116 = 231 is the minimum sum possible to achieve this.

Example 2:

1
2
3
4
5
6
Input: s1 = "delete", s2 = "leet"
Output: 403
Explanation: Deleting "dee" from "delete" to turn the string into "let",
adds 100[d]+101[e]+101[e] to the sum. Deleting "e" from "leet" adds 101[e] to the sum.
At the end, both strings are equal to "let", and the answer is 100+101+101+101 = 403.
If instead we turned both strings into "lee" or "eet", we would get answers of 433 or 417, which are higher.

这道题给了我们两个字符串,让我们删除一些字符使得两个字符串相等,我们希望删除的字符的ASCII码最小。这道题跟之前那道Delete Operation for Two Strings极其类似,那道题让求删除的最少的字符数,这道题换成了ASCII码值。其实很多大厂的面试就是这种改动,虽然很少出原题,但是这种小范围的改动却是很经常的,所以当背题侠是没有用的,必须要完全掌握了解题思想,并能举一反三才是最重要的。看到这种玩字符串又是求极值的题,想都不要想直接上DP,我们建立一个二维数组dp,其中dp[i][j]表示字符串s1的前i个字符和字符串s2的前j个字符变相等所要删除的字符的最小ASCII码累加值。那么我们可以先初始化边缘,即有一个字符串为空的话,那么另一个字符串有多少字符就要删多少字符,才能变空字符串。

所以我们初始化dp[0][j]和dp[i][0],计算方法就是上一个dp值加上对应位置的字符,有点像计算累加数组的方法,由于字符就是用ASCII表示的,所以我们不用转int,直接累加就可以。这里我们把dp[i][0]的计算放入大的循环中计算,是为了少写一个for循环。好,现在我们来看递推公式,需要遍历这个二维数组的每一个位置即dp[i][j],当对应位置的字符相等时,s1[i-1] == s2[j-1],(注意由于dp数组的i和j是从1开始的,所以字符串中要减1),那么我们直接赋值为上一个状态的dp值,即dp[i-1][j-1],因为已经匹配上了,不用删除字符。如果s1[i-1] != s2[j-1],那么就有两种情况,我们可以删除s[i-1]的字符,且加上被删除的字符的ASCII码到上一个状态的dp值中,即dp[i-1][j] + s1[i-1],或者删除s[j-1]的字符,且加上被删除的字符的ASCII码到上一个状态的dp值中,即dp[i][j-1] + s2[j-1]。这不难理解吧,比如sea和eat,当首字符s和e失配了,那么有两种情况,要么删掉s,用ea和eat继续匹配,或者删掉e,用sea和at继续匹配,记住删掉的字符一定要累加到dp值中才行,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int minimumDeleteSum(string s1, string s2) {
int m = s1.size(), n = s2.size();
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for (int j = 1; j <= n; ++j) dp[0][j] = dp[0][j - 1] + s2[j - 1];
for (int i = 1; i <= m; ++i) {
dp[i][0] = dp[i - 1][0] + s1[i - 1];
for (int j = 1; j <= n; ++j) {
dp[i][j] = (s1[i - 1] == s2[j - 1]) ? dp[i - 1][j - 1] : min(dp[i - 1][j] + s1[i - 1], dp[i][j - 1] + s2[j - 1]);
}
}
return dp[m][n];
}
};

我们也可以优化空间复杂度,使用一个一维数组dp,其中dp[i]表示字符串s1和字符串s2的前i个字符变相等所要删除的字符的最小ASCII码累加值。刚开始还是要初始化dp[j],这里用变量t1和t2保存上一个状态的值,并不断更新。如果面试官没有特别的要求,还是用二维dp数组吧,毕竟逻辑更清晰一些,一维的容易写错~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int minimumDeleteSum(string s1, string s2) {
int m = s1.size(), n = s2.size();
vector<int> dp(n + 1, 0);
for (int j = 1; j <= n; ++j) dp[j] = dp[j - 1] + s2[j - 1];
for (int i = 1; i <= m; ++i) {
int t1 = dp[0];
dp[0] += s1[i - 1];
for (int j = 1; j <= n; ++j) {
int t2 = dp[j];
dp[j] = (s1[i - 1] == s2[j - 1]) ? t1 : min(dp[j] + s1[i - 1], dp[j - 1] + s2[j - 1]);
t1 = t2;
}
}
return dp[n];
}
};

Leetcode713. Subarray Product Less Than K

Your are given an array of positive integers nums.

Count and print the number of (contiguous) subarrays where the product of all the elements in the subarray is less than k.

Example 1:

1
2
3
4
Input: nums = [10, 5, 2, 6], k = 100
Output: 8
Explanation: The 8 subarrays that have product less than 100 are: [10], [5], [2], [6], [10, 5], [5, 2], [2, 6], [5, 2, 6].
Note that [10, 5, 2] is not included as the product of 100 is not strictly less than k.

Note:

  • 0 < nums.length <= 50000.
  • 0 < nums[i] < 1000.
  • 0 <= k < 10^6.

这道题给了我们一个数组和一个数字K,让求子数组且满足乘积小于K的个数。既然是子数组,那么必须是连续的,所以肯定不能给数组排序了,这道题好在限定了输入数字都是正数,能稍稍好做一点。博主刚开始用的是暴力搜索的方法来做的,就是遍历所有的子数组算乘积和K比较,两个 for 循环就行了,但是 OJ 不答应。于是上网搜大神们的解法,思路很赞。相当于是一种滑动窗口的解法,维护一个数字乘积刚好小于k的滑动窗口,用变量 left 来记录其左边界的位置,右边界i就是当前遍历到的位置。遍历原数组,用 prod 乘上当前遍历到的数字,然后进行 while 循环,如果 prod 大于等于k,则滑动窗口的左边界需要向右移动一位,删除最左边的数字,那么少了一个数字,乘积就会改变,所以用 prod 除以最左边的数字,然后左边右移一位,即 left 自增1。当确定了窗口的大小后,就可以统计子数组的个数了,就是窗口的大小。为啥呢,比如 [5 2 6] 这个窗口,k还是 100,右边界刚滑到6这个位置,这个窗口的大小就是包含6的子数组乘积小于k的个数,即 [6], [2 6], [5 2 6],正好是3个。所以窗口每次向右增加一个数字,然后左边去掉需要去掉的数字后,窗口的大小就是新的子数组的个数,每次加到结果 res 中即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int numSubarrayProductLessThanK(vector<int>& nums, int k) {
if (k <= 1) return 0;
int res = 0, prod = 1, left = 0;
for (int i = 0; i < nums.size(); ++i) {
prod *= nums[i];
while (left <= i && prod >= k) prod /= nums[left++];
res += i - left + 1;
}
return res;
}
};

Leetcode714. Best Time to Buy and Sell Stock with Transaction Fee

Your are given an array of integers prices, for which the i-th element is the price of a given stock on day i; and a non-negative integer fee representing a transaction fee.

You may complete as many transactions as you like, but you need to pay the transaction fee for each transaction. You may not buy more than 1 share of a stock at a time (ie. you must sell the stock share before you buy again.)

Return the maximum profit you can make.

Example 1:

1
2
Input: prices = [1, 3, 2, 8, 4, 9], fee = 2
Output: 8

Explanation: The maximum profit can be achieved by:

  • Buying at prices[0] = 1
  • Selling at prices[3] = 8
  • Buying at prices[4] = 4
  • Selling at prices[5] = 9
  • The total profit is ((8 - 1) - 2) + ((9 - 4) - 2) = 8.

Note:

  • 0 < prices.length <= 50000.
  • 0 < prices[i] < 50000.
  • 0 <= fee < 50000.

首先就是要确定需要保存的状态,题目所求为这么多天后能获得的最大收益,那么我们直接令A[i]为第i天所能取得的最大收益。但是我们很快发现,某一天其实会有两种可能性,就是持有一股、或者持有零股,所以我们令 A[i] 为第i天持有一股时的最大收益,而 B[i] 为持有零股时的最大收益。

A[i]状态转移方程分为两种情况,第i天买入和第i天继续持仓。

  • 第i天买入的情况:收益为 B[i−1]−prices[i]
  • 第i天继续持仓的情况:收益为 A[i−1]

以上两个方程乍看都很好理解,但是 A[i] 要在这两个的较大值中选择,在买入一股以后,状态由B转向A,他当前手上的资产为 现金+一股,如果我们想要知道这个决定是否比较优,那么就与相同资产状况的时候做比较,即第i天之前的现金+一股时的资产总额。

如果第i天买入要优于第i-1天手上有一股,我们就选择第i天买入,反之我们就认为 A[i−1] 更优,即保持手中有一股的状态。

B[i]的状态转移方程比较好理解,因为要满足第i天手上没有持有股票的情况的话,只有两种情况,第i天卖出或者继续不买入。对于第i天卖出的情况,算上每笔交易的手续费,此时收益为A[i−1]+prices[i]−fee;对于继续不买入的情况,其收益与前一天空持的收益相同,为B[i−1]。综合以上,总的状态转移方程为:A[i]=max(A[i−1],B[i−1]−prices[i])B[i]=max(B[i−1],A[i−1]+prices[i]−fee)

根据题意:最后结果为最后一天手上没有股票时的最大收益,即返回B[prices.size()−1]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
// have one stock on hand
vector<int> A(prices.size(), 0);
// no stock on hand
vector<int> B(prices.size(), 0);
A[0] = A[0] - prices[0];
for (int i = 1; i < prices.size(); ++i) {
A[i] = max(A[i - 1], B[i - 1] - prices[i]);
B[i] = max(B[i - 1], A[i - 1] + prices[i] - fee);
}
return B[prices.size() - 1];
}
};

Leetcode717 1-bit and 2-bit Characters

We have two special characters. The first character can be represented by one bit 0. The second character can be represented by two bits (10 or 11).

Now given a string represented by several bits. Return whether the last character must be a one-bit character or not. The given string will always end with a zero.

Example 1:

1
2
3
4
5
Input: 
bits = [1, 0, 0]
Output: True
Explanation:
The only way to decode it is two-bit character and one-bit character. So the last character is one-bit character.

Example 2:

1
2
3
4
5
Input: 
bits = [1, 1, 1, 0]
Output: False
Explanation:
The only way to decode it is two-bit character and two-bit character. So the last character is NOT one-bit character.

Note:

  • 1 <= len(bits) <= 1000.
  • bits[i] is always 0 or 1.

看上去只要判断最后的两三位就可以了,但是既然给了一整个数组,肯定不会这么轻易就让你完成这道题的。我的方法是从头开始遍历这个数组,维护一个int bitA ,用来保存当前这个是不是1,如果是1,那它后面的一位就不用判断了,这两个肯定是组成一个字符的,跳过后一个继续,当前这个位是0,那它肯定是单独编码成字符的,直到最后一位字符,判断它是作为bitA,还是bitA后需要跳过的那个元素。
这里采用的思想是确保前面的元素都已经被确定分成两个一组或者一个一组了,最后的那个才能分得清是否单独分成一组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
bool isOneBitCharacter(vector<int>& bits) {
int i = 0;
int ret = false;
while(1) {
if(bits[i] == 0) {
i ++;
ret = true;
}
else if(bits[i] == 1) {
i += 2;
ret = false;
}
if(i >= bits.size())
break;
}
return ret;
}
};

Leetcode718. Maximum Length of Repeated Subarray

Given two integer arrays A and B, return the maximum length of an subarray that appears in both arrays.

Example 1:

1
2
3
4
5
6
Input:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
Output: 3
Explanation:
The repeated subarray with maximum length is [3, 2, 1].

这道题给了我们两个数组A和B,让返回连个数组的最长重复子数组。那么如果将数组换成字符串,实际这道题就是求 Longest Common Substring 的问题了,而貌似 LeetCode 上并没有这种明显的要求最长相同子串的题,注意需要跟最长子序列 Longest Common Subsequence 区分开,关于最长子序列会在 follow up 中讨论。好,先来看这道题,既然是子数组,那么重复的地方一定是连续的,而且起点可能会是在数组中的任意地方,这样的话,最暴力的方法就是遍历A中的每个位置,把每个位置都当作是起点进行和B从开头比较,每次A和B都同时前进一个,假如相等,则计数器会累加1,不相等的话,计数器会重置为0,每次用计数器 cnt 的长度来更新结果 res。然后用同样的方法对B也处理一遍,把每个位置都当作是起点进行和A从开头比较,每次A和B都同时前进一个,这样最终下来,就可以求出最长重复子数组的长度,令人惊喜的是,这种暴力搜索解法的击败率相当的高,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
int findLength(vector<int>& A, vector<int>& B) {
int m = A.size(), n = B.size(), res = 0;
for (int offset = 0; offset < m; ++offset) {
for (int i = offset, j = 0; i < m && j < n;) {
int cnt = 0;
while (i < m && j < n && A[i++] == B[j++]) ++cnt;
res = max(res, cnt);
}
}
for (int offset = 0; offset < n; ++offset) {
for (int i = 0, j = offset; i < m && j < n;) {
int cnt = 0;
while (i < m && j < n && A[i++] == B[j++]) ++cnt;
res = max(res, cnt);
}
}
return res;
}
};

我们还可以使用二分法+哈希表来做,别问博主怎么知道(看了题目标签,然后去论坛上找对应的解法即可,哈哈~)。虽然解法看起来很炫,但不太简洁,不是博主的 style,但还是收录进来吧。这里使用二分搜索法来找什么呢?其实是来直接查找最长重叠子数组的长度的,因为这个长度是有范围限制的,在 [0, min(m, n)] 之间,其中m和n分别是数组A和B的长度。这样每次折半出一个 mid,然后验证有没有这么一个长度为 mid 的子数组在A和B中都存在。从数组中取子数组有些麻烦,可以将数组转为字符串,取子串就相对来说容易一些了。将数组A和B都先转化为字符串 strA 和 strB,但是这里很 tricky,转换的方式不能是直接将整型数字转为字符串,再连接起来,这样会出错,因为会导致一个整型数占据多位字符,所以这里是需要将每个整型数直接加入字符串,从而将该整型数当作 ASCII 码来处理,寻找对应的字符,使得转换后的 strA 和 strB 变成各种凌乱的怪异字符,不过不影响解题。这里的二分应该属于博主之前的总结贴 LeetCode Binary Search Summary 二分搜索法小结 中的第四类,但是写法上却跟第三类的变形很像,因为博主平时的习惯是右边界设置为开区间,所以初始化为 min(m, n)+1,当然博主之前就说过二分搜索的写有各种各样的,像这个帖子中写法也是可以的。博主的这种写法实际上是在找第一个不大于目标值的数,这里的目标值就是那个 helper 子函数,也就是验证函数。如何实现这个验证函数呢,由于是要找长度为 len 的子串是否同时存在于 strA 和 strB 中,可以用一个 HashSet 保存 strA 中所有长度为 len 的子串,然后遍历 strB 中所有长度为 len 的子串,假如有任何一个在 HashSet 中存在,则直接返回 true,否则循环退出后,返回 false,参见代码如下:

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
class Solution {
public:
int findLength(vector<int>& A, vector<int>& B) {
string strA = stringify(A), strB = stringify(B);
int left = 0, right = min(A.size(), B.size()) + 1;
while (left < right) {
int mid = (left + right) / 2;
if (helper(strA, strB, mid)) left = mid + 1;
else right = mid;
}
return right - 1;
}
bool helper(string& strA, string& strB, int len) {
unordered_set<string> st;
for (int i = 0, j = len; j <= strA.size(); ++i, ++j) {
st.insert(strA.substr(i, j - i));
}
for (int i = 0, j = len; j <= strB.size(); ++i, ++j) {
if (st.count(strB.substr(i, j - i))) return true;
}
return false;
}
string stringify(vector<int>& nums) {
string res;
for (int num : nums) res += num;
return res;
}
};

对于这种求极值的问题,动态规划 Dynamic Programming 一直都是一个很好的选择,这里使用一个二维的 DP 数组,其中 dp[i][j] 表示数组A的前i个数字和数组B的前j个数字在尾部匹配的最长子数组的长度,如果 dp[i][j] 不为0,则A中第i个数组和B中第j个数字必须相等,且 dp[i][j] 的值就是往前推分别相等的个数。

注意观察,dp 值不为0的地方,都是当A[i] == B[j]的地方,而且还要加上左上方的 dp 值,即dp[i-1][j-1],所以当前的dp[i][j]就等于dp[i-1][j-1] + 1,而一旦A[i] != B[j]时,直接赋值为0,不用多想,因为子数组是要连续的,一旦不匹配了,就不能再增加长度了。每次算出一个 dp 值,都要用来更新结果 res,这样就能得到最长相同子数组的长度了,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
int findLength(vector<int>& A, vector<int>& B) {
int res = 0, m = A.size(), n = B.size();
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
dp[i][j] = (A[i - 1] == B[j - 1]) ? dp[i - 1][j - 1] + 1 : 0;
res = max(res, dp[i][j]);
}
}
return res;
}
};

Leetcode720. Longest Word in Dictionary

Given a list of strings words representing an English Dictionary, find the longest word in words that can be built one character at a time by other words in words. If there is more than one possible answer, return the longest word with the smallest lexicographical order.

If there is no answer, return the empty string.
Example 1:

1
2
3
4
5
Input: 
words = ["w","wo","wor","worl", "world"]
Output: "world"
Explanation:
The word "world" can be built one character at a time by "w", "wo", "wor", and "worl".

Example 2:

1
2
3
4
5
Input: 
words = ["a", "banana", "app", "appl", "ap", "apply", "apple"]
Output: "apple"
Explanation:
Both "apply" and "apple" can be built from other words in the dictionary. However, "apple" is lexicographically smaller than "apply".

Note:

  • All the strings in the input will only contain lowercase letters.
  • The length of words will be in the range [1, 1000].
  • The length of words[i] will be in the range [1, 30].

如果这个有ab,abc,那么是不可以的,因为没有a,也就是说必须最长字母的每一个非空子串都在words里面。一开始没搞懂这个意思。

先对words进行排序。可以用一个hashset保存字符串(首先加入一个空串,便于长度为1的字符串和大于1的字符串同等处理),如果能找到这个字符串的子串(除去末尾最后一个字符),则将该字符串加入set;
同时保存最长字符串,如果字符串长度相等,则返回字典序小的那个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
string longestWord(vector<string>& words) {
sort(words.begin(), words.end());
string result = "";
vector<string> vec;
vec.push_back("");
for(int i = 0; i < words.size(); i ++) {
string temp = words[i];
temp.erase(temp.length()-1);
if(find(vec.begin(), vec.end(), temp)!=vec.end()) {
vec.push_back(words[i]);
if(words[i].length() > result.length())
result = words[i];
else if(words[i].length() == result.length())
result = result < words[i] ? result : words[i];
}
}
return result;
}
};

Leetcode721. Accounts Merge

Given a list accounts, each element accounts[i] is a list of strings, where the first element accounts[i][0] is a name, and the rest of the elements are emails representing emails of the account.

Now, we would like to merge these accounts. Two accounts definitely belong to the same person if there is some email that is common to both accounts. Note that even if two accounts have the same name, they may belong to different people as people could have the same name. A person can have any number of accounts initially, but all of their accounts definitely have the same name.

After merging the accounts, return the accounts in the following format: the first element of each account is the name, and the rest of the elements are emails in sorted order. The accounts themselves can be returned in any order.

Example 1:

1
2
3
Input: 
accounts = [["John", "johnsmith@mail.com", "john00@mail.com"], ["John", "johnnybravo@mail.com"], ["John", "johnsmith@mail.com", "john_newyork@mail.com"], ["Mary", "mary@mail.com"]]
Output: [["John", 'john00@mail.com', 'john_newyork@mail.com', 'johnsmith@mail.com'], ["John", "johnnybravo@mail.com"], ["Mary", "mary@mail.com"]]

Explanation:

1
2
3
4
The first and third John's are the same person as they have the common email "johnsmith@mail.com".
The second John and Mary are different people as none of their email addresses are used by other accounts.
We could return these lists in any order, for example the answer [['Mary', 'mary@mail.com'], ['John', 'johnnybravo@mail.com'],
['John', 'john00@mail.com', 'john_newyork@mail.com', 'johnsmith@mail.com']] would still be accepted.

Note:

  • The length of accounts will be in the range [1, 1000].
  • The length of accounts[i] will be in the range [1, 10].
  • The length of accounts[i][j] will be in the range [1, 30].

这道题给了一堆人名和邮箱,一个名字可能有多个邮箱,但是一个邮箱只属于一个人,让我们把同一个人的邮箱都合并到一起,名字相同不一定是同一个人,只有当两个名字有相同的邮箱,才能确定是同一个人,题目中的例子很好说明了这个问题,输入有三个 John,最后合并之后就只有两个了。

这个归组类的问题,最典型的就是岛屿问题(例如 Number of Islands II),很适合使用 Union Find 来做,LeetCode 中有很多道可以使用这个方法来做的题,比如 Friend Circles,Graph Valid Tree,Number of Connected Components in an Undirected Graph,和 Redundant Connection 等等。都是要用一个 root 数组,每个点开始初始化为不同的值,如果两个点属于相同的组,就将其中一个点的 root 值赋值为另一个点的位置,这样只要是相同组里的两点,通过 find 函数得到相同的值。在这里,由于邮件是字符串不是数字,所以 root 可以用 HashMap 来代替,还需要一个 HashMap 映射owner,建立每个邮箱和其所有者姓名之前的映射,另外用一个 HashMap 来建立用户和其所有的邮箱之间的映射,也就是合并后的结果。

首先遍历每个账户和其中的所有邮箱,先将每个邮箱的 root 映射为其自身,然后将 owner 赋值为用户名。然后开始另一个循环,遍历每一个账号,首先对帐号的第一个邮箱调用 find 函数,得到其父串p,然后遍历之后的邮箱,对每个遍历到的邮箱先调用 find 函数,将其父串的 root 值赋值为p,这样做相当于将相同账号内的所有邮箱都链接起来了。接下来要做的就是再次遍历每个账户内的所有邮箱,先对该邮箱调用 find 函数,找到父串,然后将该邮箱加入该父串映射的集合汇总,这样就就完成了合并。最后只需要将集合转为字符串数组,加入结果 res 中,通过 owner 映射找到父串的用户名,加入字符串数组的首位置,参见代码如下:

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<vector<string>> accountsMerge(vector<vector<string>>& accounts) {
vector<vector<string>> res;
unordered_map<string, string> root;
unordered_map<string, string> owner;
unordered_map<string, set<string>> m;
for (auto account : accounts) {
for (int i = 1; i < account.size(); ++i) {
root[account[i]] = account[i];
owner[account[i]] = account[0];
}
}
for (auto account : accounts) {
string p = find(account[1], root);
for (int i = 2; i < account.size(); ++i) {
root[find(account[i], root)] = p;
}
}
for (auto account : accounts) {
for (int i = 1; i < account.size(); ++i) {
m[find(account[i], root)].insert(account[i]);
}
}
for (auto a : m) {
vector<string> v(a.second.begin(), a.second.end());
v.insert(v.begin(), owner[a.first]);
res.push_back(v);
}
return res;
}
string find(string s, unordered_map<string, string>& root) {
return root[s] == s ? s : find(root[s], root);
}
};

Leetcode724. Find Pivot Index

Given an array of integers nums, write a method that returns the “pivot” index of this array.

We define the pivot index as the index where the sum of the numbers to the left of the index is equal to the sum of the numbers to the right of the index.

If no such index exists, we should return -1. If there are multiple pivot indexes, you should return the left-most pivot index.

Example 1:

1
2
3
4
5
6
Input: 
nums = [1, 7, 3, 6, 5, 6]
Output: 3
Explanation:
The sum of the numbers to the left of index 3 (nums[3] = 6) is equal to the sum of numbers to the right of index 3.
Also, 3 is the first index where this occurs.

Example 2:

1
2
3
4
5
Input: 
nums = [1, 2, 3]
Output: -1
Explanation:
There is no index that satisfies the conditions in the problem statement.

Note:

The length of nums will be in the range [0, 10000].
Each element nums[i] will be an integer in the range [-1000, 1000].

题目大意:在一个数组中找到一个数字的下标,要求这个数字左边的数字和等于右边的数字和,如果不存在就返回-1

分析:计算数组所有元素和sum,然后遍历数组,每次将前i-1个数字的和累加在t中,每次从sum中减去nums[i],这样sum就是nums[i]后面所有数字的和,如果sum == t就返回i,否则就返回-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
int pivotIndex(vector<int>& nums) {
int sum = 0, t = 0;
for (int i = 0; i < nums.size(); i++)
sum += nums[i];
for(int i = 0; i < nums.size(); i ++) {
sum -= nums[i];
if(sum == t) return i;
t += nums[i];
}
return -1;
}
};

Leetcode725. Split Linked List in Parts

Given the head of a singly linked list and an integer k, split the linked list into k consecutive linked list parts.

The length of each part should be as equal as possible: no two parts should have a size differing by more than one. This may lead to some parts being null.

The parts should be in the order of occurrence in the input list, and parts occurring earlier should always have a size greater than or equal to parts occurring later.

Return an array of the k parts.

Example 1:

1
2
3
4
5
Input: head = [1,2,3], k = 5
Output: [[1],[2],[3],[],[]]
Explanation:
The first element output[0] has output[0].val = 1, output[0].next = null.
The last element output[4] is null, but its string representation as a ListNode is [].

Example 2:

1
2
3
4
Input: head = [1,2,3,4,5,6,7,8,9,10], k = 3
Output: [[1,2,3,4],[5,6,7],[8,9,10]]
Explanation:
The input has been split into consecutive parts with size difference at most 1, and earlier parts are a larger size than the later parts.

这道题目是希望把链表分成k组,其中有些限制条件,任意小组之间的长度不能超过1,而且小组链表长度是降序的。看懂题意这道题目的解题思路就很明确了:

  • step1:初始化一个大小为k的链表数组,默认值为NULL
  • step2:遍历链表并计算链表长度
  • step3:计算划分k组后每个小组的链表长度,并开始进行划分
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
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
vector<ListNode*> splitListToParts(ListNode* head, int k) {
ListNode *cur = head, *prev;
int nums = 0;
while(cur != NULL) {
cur = cur->next;
nums ++;
}
vector<int> length(k, nums/k);
nums = nums % k;
for (int i = 0; i < nums; i ++)
length[i] ++;
vector<ListNode*> res;
cur = head;
for (int i = 0; i < length.size(); i ++) {
if (length[i] <= 0)
res.push_back(NULL);
else {
res.push_back(cur);
while(cur && length[i]--) {
prev = cur;
cur = cur->next;
}
prev->next = NULL;
}
}
return res;
}
};

Leetcode728 Self Dividing Numbers

A self-dividing number is a number that is divisible by every digit it contains. For example, 128 is a self-dividing number because 128 % 1 == 0, 128 % 2 == 0, and 128 % 8 == 0. Also, a self-dividing number is not allowed to contain the digit zero. Given a lower and upper number bound, output a list of every possible self dividing number, including the bounds if possible.

Example 1:

1
2
3
Input: 
left = 1, right = 22
Output: [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 15, 22]

Note:

The boundaries of each input argument are 1 <= left <= right <= 10000.

自除数 是指可以被它包含的每一位数除尽的数。例如,128 是一个自除数,因为 128 % 1 == 0,128 % 2 == 0,128 % 8 == 0。还有,自除数不允许包含 0 。给定上边界和下边界数字,输出一个列表,列表的元素是边界(含边界)内所有的自除数。

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
class Solution {
public:
vector<int> get_indics(int i) {
vector<int> res;
int temp;
while(i > 0) {
temp = i % 10;
if(temp == 0) {
res.clear();
res.push_back(-1);
return res;
}
res.push_back(temp);
i /= 10;
}
return res;
}

vector<int> selfDividingNumbers(int left, int right) {
vector<int> result, temp;
int flag = true;
for(int i = left; i <= right; i ++) {
temp = get_indics(i);
flag = true;
if(temp.size()==1 && temp[0]==-1)
continue;
for(int j = 0; j < temp.size(); j ++){
if(i % temp[j] != 0)
flag = false;
}
if(flag)
result.push_back(i);
}
return result;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:

vector<int> selfDividingNumbers(int left, int right) {
vector<int> result;
bool flag;
int temp;
for(int i = left; i <= right; i ++) {
int ii = i;
flag = true;
while(ii > 0) {
temp = ii % 10;
if(temp == 0 || i % temp != 0) {
flag=false;
break;
}
ii /= 10;
}
if (flag)
result.push_back(i);
}
return result;
}
};

Leetcode729. My Calendar I

Implement a MyCalendar class to store your events. A new event can be added if adding the event will not cause a double booking.

Your class will have the method, book(int start, int end). Formally, this represents a booking on the half open interval [start, end), the range of real numbers x such that start <= x < end.

A double booking happens when two events have some non-empty intersection (ie., there is some time that is common to both events.)

For each call to the method MyCalendar.book, return true if the event can be added to the calendar successfully without causing a double booking. Otherwise, return false and do not add the event to the calendar.

Your class will be called like this: MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end)

Example 1:

1
2
3
4
5
6
7
MyCalendar();
MyCalendar.book(10, 20); // returns true
MyCalendar.book(15, 25); // returns false
MyCalendar.book(20, 30); // returns true
Explanation:
The first event can be booked. The second can't because time 15 is already booked by another event.
The third event can be booked, as the first event takes every time less than 20, but not including 20.

Note:

  • The number of calls to MyCalendar.book per test case will be at most 1000.
  • In calls to MyCalendar.book(start, end), start and end are integers in the range [0, 10^9].

这道题让我们设计一个我的日历类,里面有一个book函数,需要给定一个起始时间和结束时间,与Google Calendar不同的是,我们的事件事件上不能重叠,实际上这道题的本质就是检查区间是否重叠。那么我们可以暴力搜索,对于每一个将要加入的区间,我们都和已经已经存在的区间进行比较,看是否有重复。而新加入的区间和当前区间产生重复的情况有两种,一种是新加入区间的前半段重复,并且,另一种是新加入区间的后半段重复。比如当前区间如果是[3, 8),那么第一种情况下新加入区间就是[6, 9),那么触发条件就是当前区间的起始时间小于等于新加入区间的起始时间,并且结束时间大于新加入区间的结束时间。第二种情况下新加入区间就是[2,5),那么触发条件就是当前区间的起始时间大于等于新加入区间的起始时间,并且起始时间小于新加入区间的结束时间。这两种情况均返回false,否则就将新区间加入数组,并返回true即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyCalendar {
public:
MyCalendar() {}

bool book(int start, int end) {
for (auto a : cal) {
if (a.first <= start && a.second > start) return false;
if (a.first >= start && a.first < end) return false;
}
cal.push_back({start, end});
return true;
}

private:
vector<pair<int, int>> cal;
};

下面这种方法将上面方法的两个if判断融合成为了一个,我们来观察两个区间的起始和结束位置的关系发现,如果两个区间的起始时间中的较大值小于结束区间的较小值,那么就有重合,返回false。比如 [3, 8) 和 [6, 9),3和6中的较大值6,小于8和9中的较小值8,有重叠。再比如[3, 8) 和 [2, 5),3和2中的较大值3,就小于8和5中的较小值5,有重叠。而对于[3, 8) 和 [9, 10),3和9中的较大值9,不小于8和10中的较小值8,所以没有重叠,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyCalendar {
public:
MyCalendar() {}

bool book(int start, int end) {
for (auto a : cal) {
if (max(a.first, start) < min(a.second, end)) return false;
}
cal.push_back({start, end});
return true;
}

private:
vector<pair<int, int>> cal;
};

Leetcode731. My Calendar II

Implement a MyCalendarTwo class to store your events. A new event can be added if adding the event will not cause a triple booking.

Your class will have one method, book(int start, int end). Formally, this represents a booking on the half open interval [start, end), the range of real numbers x such that start <= x < end.

A triple booking happens when three events have some non-empty intersection (ie., there is some time that is common to all 3 events.)

For each call to the method MyCalendar.book, return true if the event can be added to the calendar successfully without causing a triple booking. Otherwise, return false and do not add the event to the calendar.

Your class will be called like this: MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end)

Example 1:

1
2
3
4
5
6
7
MyCalendar();
MyCalendar.book(10, 20); // returns true
MyCalendar.book(50, 60); // returns true
MyCalendar.book(10, 40); // returns true
MyCalendar.book(5, 15); // returns false
MyCalendar.book(5, 10); // returns true
MyCalendar.book(25, 55); // returns true

Explanation:

  • The first two events can be booked. The third event can be double booked.
  • The fourth event (5, 15) can’t be booked, because it would result in a triple booking.
  • The fifth event (5, 10) can be booked, as it does not use time 10 which is already double booked.
  • The sixth event (25, 55) can be booked, as the time in [25, 40) will be double booked with the third event;
  • The time [40, 50) will be single booked, and the time [50, 55) will be double booked with the second event.

Note:

  • The number of calls to MyCalendar.book per test case will be at most 1000.
  • In calls to MyCalendar.book(start, end), start and end are integers in the range [0, 10^9].

这道题是 My Calendar I 的拓展,之前那道题说是不能有任何的重叠区间,而这道题说最多容忍两个重叠区域,注意是重叠区域,不是事件。比如事件 A,B,C 互不重叠,但是有一个事件D,和这三个事件都重叠,这样是可以的,因为重叠的区域最多只有两个。所以关键还是要知道具体的重叠区域,如果两个事件重叠,那么重叠区域就是它们的交集,求交集的方法是两个区间的起始时间中的较大值,到结束时间中的较小值。可以用一个 TreeSet 来专门存重叠区间,再用一个 TreeSet 来存完整的区间,那么思路就是,先遍历专门存重叠区间的 TreeSet,因为能在这里出现的区间,都已经是出现两次了,如果当前新的区间跟重叠区间有交集的话,说明此时三个事件重叠了,直接返回 false。如果当前区间跟重叠区间没有交集的话,则再来遍历完整区间的集合,如果有交集的话,那么应该算出重叠区间并且加入放重叠区间的 TreeSet 中。最后记得将新区间加入完整区间的 TreeSet 中,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MyCalendarTwo {
public:
MyCalendarTwo() {}

bool book(int start, int end) {
for (auto &a : s2) {
if (start >= a.second || end <= a.first) continue;
return false;
}
for (auto &a : s1) {
if (start >= a.second || end <= a.first) continue;
s2.insert({max(start, a.first), min(end, a.second)});
}
s1.insert({start, end});
return true;
}

private:
set<pair<int, int>> s1, s2;
};

下面这种方法相当的巧妙,建立一个时间点和次数之间的映射,规定遇到起始时间点,次数加1,遇到结束时间点,次数减1。那么首先更改新的起始时间 start 和结束时间 end 的映射,start 对应值增1,end 对应值减1。然后定义一个变量 cnt,来统计当前的次数。使用 TreeMap 具有自动排序的功能,所以遍历的时候就是按时间顺序的,最先遍历到的一定是一个起始时间,所以加上其映射值,一定是个正数。如果此时只有一个区间,就是刚加进来的区间的话,那么首先肯定遍历到 start,那么 cnt 此时加1,然后就会遍历到 end,那么此时 cnt 减1,最后下来 cnt 为0,没有重叠。还是用具体数字来说吧,现在假设 TreeMap 中已经加入了一个区间 [3, 5) 了,就有下面的映射:

3 -> 1

5 -> -1

假如此时要加入的区间为 [3, 8) 的话,则先对3和8分别加1减1,此时的映射为:

3 -> 2

5 -> -1

8 -> -1

最先遍历到3,cnt 为2,没有超过3,此时有两个事件有重叠,是允许的。然后遍历5和8,分别减去1,最终又变成0了,始终 cnt 没有超过2,所以是符合题意的。如果此时再加入一个新的区间 [1, 4),则先对1和4分别加1减1,那么此时的映射为:

1 -> 1

3 -> 2

4 -> -1

5 -> -1

8 -> -1

先遍历到1,cnt为1,然后遍历到3,此时 cnt 为3了,那么就知道有三个事件有重叠区间了,所以这个新区间是不能加入的,需要还原其 start 和 end 做的操作,把 start 的映射值减1,end 的映射值加1,然后返回 false。否则没有三个事件有共同重叠区间的话,返回 true 即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyCalendarTwo {
public:
MyCalendarTwo() {}

bool book(int start, int end) {
++freq[start];
--freq[end];
int cnt = 0;
for (auto f : freq) {
cnt += f.second;
if (cnt == 3) {
--freq[start];
++freq[end];
return false;
}
}
return true;
}

private:
map<int, int> freq;
};

Leetcode733. Flood Fill

An image is represented by a 2-D array of integers, each integer representing the pixel value of the image (from 0 to 65535).

Given a coordinate (sr, sc) representing the starting pixel (row and column) of the flood fill, and a pixel value newColor, “flood fill” the image.

To perform a “flood fill”, consider the starting pixel, plus any pixels connected 4-directionally to the starting pixel of the same color as the starting pixel, plus any pixels connected 4-directionally to those pixels (also with the same color as the starting pixel), and so on. Replace the color of all of the aforementioned pixels with the newColor.

At the end, return the modified image.

Example 1:

1
2
3
4
5
6
7
8
9
Input: 
image = [[1,1,1],[1,1,0],[1,0,1]]
sr = 1, sc = 1, newColor = 2
Output: [[2,2,2],[2,2,0],[2,0,1]]
Explanation:
From the center of the image (with position (sr, sc) = (1, 1)), all pixels connected
by a path of the same color as the starting pixel are colored with the new color.
Note the bottom corner is not colored 2, because it is not 4-directionally connected
to the starting pixel.

Note:

The length of image and image[0] will be in the range [1, 50].
The given starting pixel will satisfy 0 <= sr < image.length and 0 <= sc < image[0].length.
The value of each color in image[i][j] and newColor will be an integer in [0, 65535].

这道题给了一个用二维数组表示的图像,不同的数字代表不同的颜色,给了一个起始点坐标,还有一个新的颜色,让我们把起始点的颜色以及其相邻的同样的颜色都换成新的颜色。实际上就是一个找相同区间的题,可以用 BFS 或者 DFS 来做。先来看 BFS 的解法,使用一个队列 queue 来辅助,首先将给定点放入队列中,然后进行 while 循环,条件是 queue 不为空,然后进行类似层序遍历的方法,取出队首元素,将其赋值为新的颜色,然后遍历周围四个点,如果不越界,且周围的颜色跟起始颜色相同的话,将位置加入队列中,参见代码如下:

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
class Solution {
public:
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
if(image[sr][sc]==newColor)
return image;
int dir[4][2] = {{0,1},{0,-1},{-1,0},{1,0}};
queue<pair<int,int> > q;
int m = image.size(), n = image[0].size();
q.push(make_pair(sr,sc));
int org_color = image[sr][sc];
while(!q.empty()){
pair<int,int> temp = q.front();
q.pop();
image[temp.first][temp.second] = newColor;
for(int i = 0; i < 4; i ++) {
int temp1 = temp.first+dir[i][0];
int temp2 = temp.second+dir[i][1];
if(0<=temp1 && temp1<m && 0<=temp2 && temp2<n && image[temp1][temp2]==org_color) {
q.push(make_pair(temp1,temp2));
}
}
}
return image;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {

void color(vector<vector<int>>& image, int i, int j, int m, int n, int c, int o){
if(i>=0 && j>=0 && i<m && j<n && image[i][j] == o){
image[i][j] = c;
color(image, i+1, j, m, n, c, o);
color(image, i, j-1, m, n, c, o);
color(image, i-1, j, m, n, c, o);
color(image, i, j+1, m, n, c, o);
}
}

public:
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
if(newColor!=image[sr][sc])
color(image, sr, sc, image.size(), image[0].size(), newColor, image[sr][sc]);
return image;
}
};

Leetcode735. Asteroid Collision

We are given an array asteroids of integers representing asteroids in a row.

For each asteroid, the absolute value represents its size, and the sign represents its direction (positive meaning right, negative meaning left). Each asteroid moves at the same speed.

Find out the state of the asteroids after all collisions. If two asteroids meet, the smaller one will explode. If both are the same size, both will explode. Two asteroids moving in the same direction will never meet.

Example 1:

1
2
3
Input: asteroids = [5,10,-5]
Output: [5,10]
Explanation: The 10 and -5 collide resulting in 10. The 5 and 10 never collide.

Example 2:

1
2
3
Input: asteroids = [8,-8]
Output: []
Explanation: The 8 and -8 collide exploding each other.

Example 3:

1
2
3
Input: asteroids = [10,2,-5]
Output: [10]
Explanation: The 2 and -5 collide resulting in -5. The 10 and -5 collide resulting in 10.

Example 4:

1
2
3
Input: asteroids = [-2,-1,1,2]
Output: [-2,-1,1,2]
Explanation: The -2 and -1 are moving left, while the 1 and 2 are moving right. Asteroids moving the same direction never meet, so no asteroids will meet each other.

给定一个不含0的长n数组A,表示一个数轴上的小行星的速度,正数代表向右,负数代表向左,绝对值代表小行星的质量。同向的小行星不会相撞,但反向的小行星会相撞,相撞的结果是,质量大的继续存在,小的爆炸;如果质量一样则都爆炸。问最后剩下的小行星的速度状态是什么,返回一个数组。

其实就是模拟相撞的过程。开个栈,遍历A,如果遇到了向右的小行星则直接入栈;否则,如果栈空或者栈顶也是向左的小行星,则也入栈,如果栈不空并且栈顶是向右的小行星,则进行相撞操作,直到栈空或者栈顶是向左的小行星为止,同时记录一下当前小行星是否爆炸。如果退出循环的时候当前小行星没有爆炸,则入栈。最后逆序存一下栈,就是答案。代码如下:

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
class Solution {
public:
vector<int> asteroidCollision(vector<int>& asteroids) {
stack<int> s;
for (int i : asteroids) {
if (i < 0) {
if (s.empty() || s.top() < 0)
s.push(i);
else {
bool is = true;
while(!s.empty() && s.top() > 0) {
int t = s.top();
s.pop();
if (t + i > 0) {
s.push(t);
is = false;
break;
}
else if (t + i == 0) {
is = false;
break;
}
else
continue;
}
if (is)
s.push(i);
}
}
else
s.push(i);
}
vector<int> res(s.size());
for (int i = s.size()-1; i >= 0; i --) {
res[i] = s.top();
s.pop();
}
return res;
}
};

Leetcode736. Parse Lisp Expression

给你一个类似 Lisp 语句的字符串表达式 expression,求出其计算结果。

表达式语法如下所示:

  • 表达式可以为整数,let 表达式,add 表达式,mult 表达式,或赋值的变量。表达式的结果总是一个整数。(整数可以是正整数、负整数、0)
  • let 表达式采用 “(let v1 e1 v2 e2 … vn en expr)” 的形式,其中 let 总是以字符串 “let”来表示,接下来会跟随一对或多对交替的变量和表达式,也就是说,第一个变量 v1被分配为表达式 e1 的值,第二个变量 v2 被分配为表达式 e2 的值,依次类推;最终 let 表达式的值为 expr表达式的值。
  • add 表达式表示为 “(add e1 e2)” ,其中 add 总是以字符串 “add” 来表示,该表达式总是包含两个表达式 e1、e2 ,最终结果是 e1 表达式的值与 e2 表达式的值之 和 。
  • mult 表达式表示为 “(mult e1 e2)” ,其中 mult 总是以字符串 “mult” 表示,该表达式总是包含两个表达式 e1、e2,最终结果是 e1 表达式的值与 e2 表达式的值之 积 。
  • 在该题目中,变量名以小写字符开始,之后跟随 0 个或多个小写字符或数字。为了方便,”add” ,”let” ,”mult” 会被定义为 “关键字” ,不会用作变量名。
  • 最后,要说一下作用域的概念。计算变量名所对应的表达式时,在计算上下文中,首先检查最内层作用域(按括号计),然后按顺序依次检查外部作用域。测试用例中每一个表达式都是合法的。有关作用域的更多详细信息,请参阅示例。

示例 1:

1
2
3
4
5
6
输入:expression = "(let x 2 (mult x (let x 3 y 4 (add x y))))"
输出:14
解释:
计算表达式 (add x y), 在检查变量 x 值时,
在变量的上下文中由最内层作用域依次向外检查。
首先找到 x = 3, 所以此处的 x 值是 3 。

示例 2:

1
2
3
输入:expression = "(let x 3 x 2 x)"
输出:2
解释:let 语句中的赋值运算按顺序处理即可。

示例 3:

1
2
3
4
5
输入:expression = "(let x 1 y 2 x (add x y) (add x y))"
输出:5
解释:
第一个 (add x y) 计算结果是 3,并且将此值赋给了 x 。
第二个 (add x y) 计算结果是 3 + 2 = 5 。

提示:

  • 1 <= expression.length <= 2000
  • exprssion 中不含前导和尾随空格
  • expressoin 中的不同部分(token)之间用单个空格进行分隔
  • 答案和所有中间计算结果都符合 32-bit 整数范围
  • 测试用例中的表达式均为合法的且最终结果为整数

这道题让我们解析Lisp语言的表达式。估计题目只让我们处理一些简单的情况,毕竟不可能让我们写一个编译器出来。我们通过分析例子发现,所有的命令都是用括号来包裹的,而且里面还可以嵌套小括号即子命令。让我们处理的命令只有三种,add,mult,和let。其中add和mult比较简单就是加法和乘法,就把后面两个数字或者子表达式的值加起来或成起来即可。let命令稍稍麻烦一些,后面可以跟好多变量或表达式,最简单的是三个,一般第一个是个变量,比如x,后面会跟一个数字或子表达式,就是把后面的数字或子表达式的值赋值给前面的变量,第三个位置是个表达式,其值是当前let命令的返回值。还有一个比较重要的特性是外层的变量值不会随着里层的变量值改变,比如对于下面这个例子:

1
(let x 2 (add (let x 3 (let x 4 x)) x))

刚开始x被赋值为2了,然后在返回值表达式中,又有一个add操作,add操作的第一个变量又是一个子表达式,在这个子表达式中又定义了一个变量x,并复制为3,再其返回值表达式又定义了一个变量x,赋值为4,并返回这个x,那么最内层的表达式的返回值是4,那么x被赋值为3的那层的返回值也是4,此时add的第一个数就是4,那么其第二个x是多少,其实这个x并没有被里层的x的影响,仍然是刚开始赋值的2,那么我们就看出特点了,外层的变量是能影响里层变量的,而里层变量无法影响外层变量。那么我们只要在递归的时候不加引用就行了,这样值就不会在递归函数中被更改了。

对于这种长度不定且每个可能包含子表达式的题,递归是一个很好的选择,由于需要给变量赋值,所以需要建立一个变量和其值之间的映射,然后我们就要来写递归函数了,最开始我们给定的表达式肯定是有括号的,所以我们先处理这种情况,括号对于我们的解析没有用,所以要去掉首尾的括号,然后我们用一个变量cur表示当前指向字符的位置,初始化为0,下面要做的就是先解析出命令单词,我们调用一个子函数parse,在parse函数中,简单的情况就是解析出add,mult,或let这三个命令单词,我们用一个指针来遍历字符,当越界或遇到空格就停止,但是如果我们需要解析的是个子表达式,而且里面可能还有多个子表达式,那么我们就需要找出最外面这个左括号对应的右括号,因为中间可能还会有别的左右括号,里面的内容就再之后再次调用递归函数时处理。判断的方法就是利用匹配括号的方法,用变量cnt来表示左括号的的个数,初始化为1,当要parse的表达式第一个字符是左括号时,进入循环,循环条件是cnt不为0,当遇到左括号时cnt自增1,反之当遇到右括号时cnt自减1,每次指针end都向右移动一个,最后我们根据end的位置减去初始时cur的值(保存在变量t中),可以得到表达式。如果解析出的是命令let,那么进行while循环,然后继续解析后面的内容,如果此时cur大于s的长度了,说明此时是let命令的最后一个部分,也就是返回值部分,直接调用递归函数返回即可。否则就再解析下一个部分,说明此时是变量和其对应值,我们要建立映射关系。如果之前解析出来的是add命令,那么比较简单,就直接解析出后面的两个部分的表达式,并分别调用递归函数,将递归函数的返回值累加并返回即可。对于mult命令同样的处理方式,只不过是将两个递归函数的返回值乘起来并返回。然后我们再来看如果表达式不是以左括号开头的,说明只能是数字或者变量,那么先来检测数字,如果第一个字符是负号或者0到9之间的数字,那么直接将表达式转为int型即可;否则的话就是变量,我们直接从哈希map中取值即可。最后需要注意的就是递归函数的参数哈希map一定不能加引用,具体可以参见上面那个例子的分析,加了引用后外层的变量值就会受内层的影响,这是不符合题意的,参见代码如下:

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
class Solution {
public:
unordered_map<string, vector<int>> map;
int evaluate(string expression) {
int start = 0;
return calc(expression, start);
}

int calc(string& expression, int& start) {
if (expression[start] != '(') {
if (islower(expression[start])) {
string var = parseStr(expression, start);
return map[var].back();
}
else {
return parseInt(expression, start);
}
}
int res;
start ++;
if (expression[start] == 'l') {
start += 4;
vector<string> vars;
while(true) {
if (!islower(expression[start])) {
res = calc(expression, start);
break;
}
string var = parseStr(expression, start);
if (expression[start] = ')') {
res = map[var].back();
break;
}
vars.push_back(var);
start ++;
int e = parseInt(expression, start);
map[var].push_back(e);
start ++;
}
}
else if (expression[start] == 'a') {
start += 4;
int t1 = calc(expression, start);
start ++;
int t2 = calc(expression, start);
res = t1 + t2;
}
else if (expression[start] == 'm') {
start += 5;
int t1 = calc(expression, start);
start ++;
int t2 = calc(expression, start);
res = t1 * t2;
}
start ++;
return res;
}

int parseInt(string& expression, int& start) {
int flag = 1, res = 0;
int len = expression.length();
if (expression[start] == '-') {
flag = -1;
start ++;
}
while(start < len && expression[start] >= '0' && expression[start] <= '9') {
res = res * 10 + (expression[start] - '0');
start++;
}
return res * flag;
}

string parseStr(string& expression, int& start) {
string res;
int len = expression.length();
while(start < len && expression[start] != ' ' && expression[start] != ')') {
res += expression[start];
start ++;
}
return res;
}
};

Leetcode738. Monotone Increasing Digits

An integer has monotone increasing digits if and only if each pair of adjacent digits x and y satisfy x <= y.

Given an integer n, return the largest number that is less than or equal to n with monotone increasing digits.

Example 1:

1
2
Input: n = 10
Output: 9

Example 2:

1
2
Input: n = 1234
Output: 1234

Example 3:

1
2
Input: n = 332
Output: 299

给定一个非负正整数n,求一个这样的非负整数x,x从左到右读是单调上升的(不要求严格上升),x ≤ n 并且x是满足这两个条件的最大整数。

先将n转为字符串s,然后从左向右扫描。如果一路都是递增的,那么直接返回n自己就好了。否则考虑如何求。我们找到第一个s [ i ] > s [ j ]的位置,这里下降了,需要作出调整。我们必须调整s [ i ]或者其左边的数,否则不能使得数字变得比n小。因为要使得答案尽量大,所以我们尝试改尽量低位的数字,我们就从s [ i ]开始向左找到第一个小于s [ i ]的数,如果找不到,则必须把开头改成s [ i ] − 1才能使得数字变小,接着后面全填9就行了;如果找到了,比如说s [ j ] < s [ i ],那么可以修改的最低位就是s [ j + 1 ],将其改为s [ j + 1 ] − 1然后后面全填9即可。代码如下:

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
class Solution {
public:
int monotoneIncreasingDigits(int n) {
char s[12];
int len = 0, res = n;
while(n > 0) {
s[len++] = '0' + n % 10;
n /= 10;
}
int i = 0, j = len-1;
while(i <= j) {
char t = s[i];
s[i] = s[j];
s[j] = t;
i ++;
j --;
}
i = 0;
while(i+1 < len && s[i] <= s[i+1])
i ++;
if (i == len-1)
return res;
j = i;
while (j >= 0 && s[i] == s[j])
j --;
j ++;
s[j++] --;
for ( ; j < len; j ++)
s[j] = '9';

res = 0;
for (i = 0; i < len; i ++)
res = res * 10 + (s[i] - '0');
return res;
}
};

Leetcode739. Daily Temperatures

Given an array of integers temperatures represents the daily temperatures, return an array answer such that answer[i] is the number of days you have to wait after the ith day to get a warmer temperature. If there is no future day for which this is possible, keep answer[i] == 0 instead.

Example 1:

1
2
Input: temperatures = [73,74,75,71,69,72,76,73]
Output: [1,1,4,2,1,1,0,0]

Example 2:

1
2
Input: temperatures = [30,40,50,60]
Output: [1,1,1,0]

Example 3:

1
2
Input: temperatures = [30,60,90]
Output: [1,1,0]

根据每日 气温 列表,请重新生成一个列表,对应位置的输出是需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。

可以维护一个存储下标的单调栈,从栈底到栈顶的下标对应的温度列表中的温度依次递减。如果一个下标在单调栈里,则表示尚未找到下一次温度更高的下标。

正向遍历温度列表。对于温度列表中的每个元素 T[i],如果栈为空,则直接将 i 进栈,如果栈不为空,则比较栈顶元素 prevIndex 对应的温度 T[prevIndex] 和当前温度 T[i],如果 T[i] > T[prevIndex],则将 prevIndex 移除,并将 prevIndex 对应的等待天数赋为 i - prevIndex,重复上述操作直到栈为空或者栈顶元素对应的温度小于等于当前温度,然后将 i 进栈。

由于单调栈满足从栈底到栈顶元素对应的温度递减,因此每次有元素进栈时,会将温度更低的元素全部移除,并更新出栈元素对应的等待天数,这样可以确保等待天数一定是最小的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& t) {
int len = t.size();
vector<int> res(len, 0);
stack<int> s;
for (int i = 0; i < len; i ++) {
while (!s.empty() && t[s.top()] < t[i]) {
res[s.top()] = i - s.top();
s.pop();
}
s.push(i);
}
return res;
}
};

Leetcode740. Delete and Earn

Given an array nums of integers, you can perform operations on the array.

In each operation, you pick any nums[i] and delete it to earn nums[i] points. After, you must delete every element equal to nums[i] - 1 or nums[i] + 1.

You start with 0 points. Return the maximum number of points you can earn by applying such operations.

Example 1:

1
2
3
4
5
Input: nums = [3, 4, 2]
Output: 6
Explanation:
Delete 4 to earn 4 points, consequently 3 is also deleted.
Then, delete 2 to earn 2 points. 6 total points are earned.

Example 2:

1
2
3
4
5
6
Input: nums = [2, 2, 3, 3, 3, 4]
Output: 9
Explanation:
Delete 3 to earn 3 points, deleting both 2's and the 4.
Then, delete 3 again to earn 3 points, and 3 again to earn 3 points.
9 total points are earned.

Note:

  • The length of nums is at most 20000.
  • Each element nums[i] is an integer in the range [1, 10000].

给出一组数,选择一个就删除它相邻大小的数,求最大的被选择数之和。

将值相等的数归在一起,设置一个数组sum,比如有三个3,那么sum[3]=9,这样处理之后,因为取一个数,那么与它相等的都会被取。再找状态转移方程,因为取i的话,那么i-1就取不了了,所以dp[i]=max(dp[i-2]+sum[i],sum[i-1]),因为自底向上求,所以dp可以用sum代替。

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
int deleteAndEarn(vector<int>& nums) {
vector<int> sums(10001, 0);
for (int num : nums) sums[num] += num;
for (int i = 2; i < 10001; ++i) {
sums[i] = max(sums[i - 1], sums[i - 2] + sums[i]);
}
return sums[10000];
}
};

或者直接使用两个变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
int deleteAndEarn(vector<int>& nums) {
vector<int> dp(10001, 0);
for (int i : nums)
dp[i] += i;
int use = 0, not_use = 0, prev = -2;
for (int i = 0; i < 10001; i ++) {
if (dp[i] > 0) {
int tmp = max(use, not_use);
if (i - 1 != prev)
use = tmp + dp[i];
else
use = not_use + dp[i];

not_use = tmp;
prev = i;
}
}
return max(use, not_use);
}
};

Leetcode743. Network Delay Time

You are given a network of n nodes, labeled from 1 to n. You are also given times, a list of travel times as directed edges times[i] = (ui, vi, wi), where ui is the source node, vi is the target node, and wi is the time it takes for a signal to travel from source to target.

We will send a signal from a given node k. Return the time it takes for all the n nodes to receive the signal. If it is impossible for all the n nodes to receive the signal, return -1.

Example 1:

1
2
Input: times = [[2,1,1],[2,3,1],[3,4,1]], n = 4, k = 2
Output: 2

Example 2:

1
2
Input: times = [[1,2,1]], n = 2, k = 1
Output: 1

Example 3:

1
2
Input: times = [[1,2,1]], n = 2, k = 2
Output: -1

裸的最短路,使用dij求。

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
class Solution {
public:
int networkDelayTime(vector<vector<int>>& times, int n, int k) {
vector<vector<int>> g(n+1, vector<int>(n+1, INT_MAX));
vector<bool> flag(n+1, false);
vector<int> dist(n+1, INT_MAX);
int cnt = 1;
flag[k] = true;
for (int i = 0; i < times.size(); i ++)
g[times[i][0]][times[i][1]] = times[i][2];
for (int i = 1; i <= n; i ++)
dist[i] = g[k][i];
dist[k] = 0;
while(cnt != n) {
int tmp = -1, minn = INT_MAX;
for (int i = 1; i <= n; i ++)
if (!flag[i] && dist[i] < minn) {
minn = dist[i];
tmp = i;
}
if (tmp == -1)
break;
flag[tmp] = true;
cnt ++;
for (int i = 1; i <= n; i ++) {
if (!flag[i] && g[tmp][i] != INT_MAX && dist[i] > dist[tmp]+g[tmp][i])
dist[i] = dist[tmp]+g[tmp][i];
}
}
int res = INT_MIN;
for (int i = 1; i <= n; i ++)
res = max(res, dist[i]);
if (res == INT_MAX)
res = -1;
return res;
}
};

Leetcode744. Find Smallest Letter Greater Than Target

Given a list of sorted characters letters containing only lowercase letters, and given a target letter target, find the smallest element in the list that is larger than the given target.

Letters also wrap around. For example, if the target is target = ‘z’ and letters = [‘a’, ‘b’], the answer is ‘a’.

Examples:

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
Input:
letters = ["c", "f", "j"]
target = "a"
Output: "c"

Input:
letters = ["c", "f", "j"]
target = "c"
Output: "f"

Input:
letters = ["c", "f", "j"]
target = "d"
Output: "f"

Input:
letters = ["c", "f", "j"]
target = "g"
Output: "j"

Input:
letters = ["c", "f", "j"]
target = "j"
Output: "c"

Input:
letters = ["c", "f", "j"]
target = "k"
Output: "c"

很不好的一道题,找到比target大的字母。

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
char nextGreatestLetter(vector<char>& letters, char target) {
int cha = 99999;
for(char c : letters) {
if(c > target && (c-target) < cha)
cha = c - target;
}
return target + cha > 'z' ? letters[0] : target + cha;
}
};

Leetcode746. Min Cost Climbing Stairs

On a staircase, the i-th step has some non-negative cost cost[i] assigned (0 indexed).

Once you pay the cost, you can either climb one or two steps. You need to find minimum cost to reach the top of the floor, and you can either start from the step with index 0, or the step with index 1.

Example 1:

1
2
3
Input: cost = [10, 15, 20]
Output: 15
Explanation: Cheapest is start on cost[1], pay that cost and go to the top.

Example 2:

1
2
3
Input: cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
Output: 6
Explanation: Cheapest is start on cost[0], and only step on 1s, skipping cost[3].

简单dp。这道题应该算是之前那道 Climbing Stairs 的拓展,这里不是求步数,而是每个台阶上都有一个 cost,让我们求爬到顶端的最小 cost 是多少。换汤不换药,还是用动态规划 Dynamic Programming 来做。这里定义一个一维的 dp数组,其中 dp[i] 表示爬到第i层的最小 cost,然后来想 dp[i] 如何推导。来思考一下如何才能到第i层呢?是不是只有两种可能性,一个是从第 i-2 层上直接跳上来,一个是从第 i-1 层上跳上来。不会再有别的方法,所以 dp[i] 只和前两层有关系,可以写做如下:

1
dp[i] = min(dp[i- 2] + cost[i - 2], dp[i - 1] + cost[i - 1])

最后返回最后一个数字dp[n]即可

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
vector<int> v(cost.size()+1, 0);
v[0] = 0;
v[1] = 0;
for(int i = 2; i < cost.size()+1; i ++) {
v[i] = min(v[i-2]+cost[i-2], v[i-1]+cost[i-1]);
}
return v[cost.size()];
}
};

Leetcode747. Largest Number At Least Twice of Others

In a given integer array nums, there is always exactly one largest element. Find whether the largest element in the array is at least twice as much as every other number in the array. If it is, return the index of the largest element, otherwise return -1.

Example 1:

1
2
3
4
Input: nums = [3, 6, 1, 0]
Output: 1
Explanation: 6 is the largest integer, and for every other number in the array x,
6 is more than twice as big as x. The index of value 6 is 1, so we return 1.

Example 2:

1
2
3
Input: nums = [1, 2, 3, 4]
Output: -1
Explanation: 4 isn't at least as big as twice the value of 3, so we return -1.

查找数组中最大的元素是否至少是数组中其他数字的两倍。是的话就返回最大元素的索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int dominantIndex(vector<int>& nums) {
int max1 = -1, max2 = -1, index = -1;
for(int i = 0; i < nums.size(); i ++) {
if(nums[i] > max1) {
max2 = max1;
max1 = nums[i];
index = i;
}
else if(nums[i] > max2) {
max2 = nums[i];
}
}
return max1 >= max2 * 2 ? index : -1;
}
};

Leetcode748. Shortest Completing Word

Find the minimum length word from a given dictionary words, which has all the letters from the string licensePlate. Such a word is said to complete the given string licensePlate

Here, for letters we ignore case. For example, “P” on the licensePlate still matches “p” on the word.

It is guaranteed an answer exists. If there are multiple answers, return the one that occurs first in the array.

The license plate might have the same letter occurring multiple times. For example, given a licensePlate of “PP”, the word “pair” does not complete the licensePlate, but the word “supper” does.

Example 1:

1
2
3
4
5
Input: licensePlate = "1s3 PSt", words = ["step", "steps", "stripe", "stepple"]
Output: "steps"
Explanation: The smallest length word that contains the letters "S", "P", "S", and "T".
Note that the answer is not "step", because the letter "s" must occur in the word twice.
Also note that we ignored case for the purposes of comparing whether a letter exists in the word.

Example 2:

1
2
3
4
Input: licensePlate = "1s3 456", words = ["looks", "pest", "stew", "show"]
Output: "pest"
Explanation: There are 3 smallest length words that contains the letters "s".
We return the one that occurred first.

要找包含给出模板所有字母的最短单词,可以使用哈希表记录模板中每个字母的个数,再对应每个单词中每个字母出现的个数,如果后者中每个字母出现个数大于或等于前者,则改单词是完全包含模板的,找出其中长度最短的即可。

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
class Solution {
public:

string tolow(string s) {
for (int i = 0; i < s.length(); i++) {
if (s[i] >= 'A' && s[i] <= 'Z') {
s[i] = s[i]-'A'+'a';
}
}
return s;
}

bool matched(string word, int* mymap) {
int tmap[26];
for (int i = 0; i < 26; i++) {
tmap[i] = mymap[i];
}
for (int i = 0; i < word.length(); i++) {
tmap[word[i]-'a']--;
}
for (int i = 0; i < 26; i++)
if (tmap[i] > 0 )
return false;
return true;
}

string shortestCompletingWord(string licensePlate, vector<string>& words) {
licensePlate = tolow(licensePlate);
int m[26] = {0};
for (int i = 0; i < licensePlate.length(); i ++) {
if (licensePlate[i] <= 'z' && licensePlate[i] >= 'a')
m[licensePlate[i]-'a']++;
}
string result;
int minn = 1001;
for (auto word : words) {
if (matched(word, m) && word.length() < minn) {
minn = word.length();
result = word;
}
}
return result;
}
};

Leetcode752. Open the Lock

You have a lock in front of you with 4 circular wheels. Each wheel has 10 slots: ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’. The wheels can rotate freely and wrap around: for example we can turn ‘9’ to be ‘0’, or ‘0’ to be ‘9’. Each move consists of turning one wheel one slot.

The lock initially starts at ‘0000’, a string representing the state of the 4 wheels.

You are given a list of deadends dead ends, meaning if the lock displays any of these codes, the wheels of the lock will stop turning and you will be unable to open it.

Given a target representing the value of the wheels that will unlock the lock, return the minimum total number of turns required to open the lock, or -1 if it is impossible.

Example 1:

1
2
3
4
5
6
Input: deadends = ["0201","0101","0102","1212","2002"], target = "0202"
Output: 6
Explanation:
A sequence of valid moves would be "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202".
Note that a sequence like "0000" -> "0001" -> "0002" -> "0102" -> "0202" would be invalid,
because the wheels of the lock become stuck after the display becomes the dead end "0102".

Example 2:

1
2
3
4
Input: deadends = ["8888"], target = "0009"
Output: 1
Explanation:
We can turn the last wheel in reverse to move from "0000" -> "0009".

Example 3:

1
2
3
4
Input: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888"
Output: -1
Explanation:
We can't reach the target without getting stuck.

Example 4:

1
2
Input: deadends = ["0000"], target = "8888"
Output: -1

这道题说有一种可滑动的四位数的锁,貌似行李箱上比较常见这种锁。给了我们一个目标值,还有一些死锁的情况,就是说如果到达这些死锁的位置,就不能再动了,相当于迷宫中的障碍物。然后问我们最少多少步可以从初始的0000位置滑动到给定的target位置。如果各位足够老辣的话,应该能发现其实本质就是个迷宫遍历的问题,只不过相邻位置不再是上下左右四个位置,而是四位数字每个都加一减一,总共有八个相邻的位置。遍历迷宫问题中求最短路径要用BFS来做,那么这道题也就是用BFS来解啦,和经典BFS遍历迷宫解法唯一不同的就是找下一个位置的地方,这里我们要遍历四位数字的每一位,然后分别加1减1,我们用j从-1遍历到1,遇到0跳过,也就是实现了加1减1的过程。然后我们要计算要更新位上的数字,为了处理9加1变0,和0减1变9的情况,我们统一给该位数字加上个10,然后再加或减1,最后再对10取余即可,注意字符和整型数之间通过加或减’0’来转换。我们用结果res来记录BFS遍历的层数,如果此时新生成的字符串等于target了,直接返回结果res,否则我们看如果该字符串不在死锁集合里,且之前没有遍历过,那么加入队列queue中,之后将该字符串加入visited集合中即可。注意这里在while循环中,由于要一层一层的往外扩展,一般的做法是会用一个变量len来记录当前的q.size(),博主为了简洁,使用了一个trick,就是从q.size()往0遍历,千万不能反回来,因为在计算的过程中q的大小会变化,如果让k < q.size() 为终止条件,绝b会出错,而我们初始化为q.size()就没事,参见代码如下:

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
class Solution {
public:
int openLock(vector<string>& deadends, string target) {
queue<string> q;
q.push("0000");
int step = 0;
set<string> visited;
for (string s : deadends)
visited.insert(s);

while(!q.empty()) {
int size = q.size();
for (int i = 0; i < size; i ++) {
string tmp = q.front();
q.pop();
if (visited.count(tmp))
continue;
if (tmp == target)
return step;
for (int j = 0; j < 4; j ++) {
for (int k = -1; k < 2; k += 2) {
string next = tmp;
next[j] = '0' + (next[j] - '0' + k + 10) % 10;
q.push(next);
}
}
visited.insert(tmp);
}
step ++;
}
return -1;
}
};

Leetcode754. Reach a Number

You are standing at position 0 on an infinite number line. There is a destination at position target.

You can make some number of moves numMoves so that:

On each move, you can either go left or right.During the ith move (starting from i == 1 to i == numMoves), you take i steps in the chosen direction. Given the integer target, return the minimum number of moves required (i.e., the minimum numMoves) to reach the destination.

Example 1:

1
2
3
4
5
6
Input: target = 2
Output: 3
Explanation:
On the 1st move, we step from 0 to 1 (1 step).
On the 2nd move, we step from 1 to -1 (2 steps).
On the 3rd move, we step from -1 to 2 (3 steps).

Example 2:

1
2
3
4
5
Input: target = 3
Output: 2
Explanation:
On the 1st move, we step from 0 to 1 (1 step).
On the 2nd move, we step from 1 to 3 (2 steps).

这道题让我们从起点0开始,每次可以向数轴的左右两个方向中的任意一个走,第一步走距离1,第二步走距离2,以此类推,第n步走距离n,然后给了我们一个目标值 target,问最少用多少步可以到达这个值。这道题的正确解法用到了些数学知识,还有一些小 trick,首先来说说小 trick,第一个 trick 是到达 target 和 -target 的步数相同,因为数轴是对称的,只要将到达 target 的每步的距离都取反,就能到达 -target。下面来说第二个 trick,这个是解题的关键,比如说目标值是4,那么如果一直累加步数,直到其正好大于等于target时,有:

0 + 1 = 1

1 + 2 = 3

3 + 3 = 6

第三步加上3,得到了6,超过了目标值4,超过了的距离为2,是偶数,那么实际上只要将加上距离为1的时候,不加1,而是加 -1,那么此时累加和就损失了2,那么正好能到目标值4,如下:

0 - 1 = -1

-1 + 2 = 1

1 + 3 = 4

这就是第二个 trick 啦,当超过目标值的差值d为偶数时,只要将第 d/2 步的距离取反,就能得到目标值,此时的步数即为到达目标值的步数。那么,如果d为奇数时,且当前为第n步,那么看下一步 n+1 的奇偶,如果 n+1 为奇数,则加上 n+1 再做差,得到的差值就为偶数了,问题解决,如果 n+1 为偶数,则还得加上 n+2 这个奇数,才能让差值为偶数,这样就多加了两步。分析到这里,解题思路也就明晰了吧:

先对 target 取绝对值,因为正负不影响最小步数。然后求出第n步,使得从1累加到n刚好大于等于 target

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int reachNumber(int target) {
target = abs(target);
int res = 0, sum = 0;
while(sum < target || ( sum - target) % 2 == 1) {
res ++;
sum += res;
}
return res;
}
};

Leetcode756. Pyramid Transition Matrix

We are stacking blocks to form a pyramid. Each block has a color which is a one letter string, like ‘Z’.

For every block of color C we place not in the bottom row, we are placing it on top of a left block of color A and right block of color B. We are allowed to place the block there only if (A, B, C) is an allowed triple.

We start with a bottom row of bottom, represented as a single string. We also start with a list of allowed triples allowed. Each allowed triple is represented as a string of length 3.

Return true if we can build the pyramid all the way to the top, otherwise false.

Example 1:

1
2
3
4
5
6
7
8
9
10
11
Input: bottom = "XYZ", allowed = ["XYD", "YZE", "DEA", "FFF"]
Output: true
Explanation:
We can stack the pyramid like this:
A
/ \
D E
/ \ / \
X Y Z

This works because ('X', 'Y', 'D'), ('Y', 'Z', 'E'), and ('D', 'E', 'A') are allowed triples.

Example 2:

1
2
3
4
5
Input: bottom = "XXYX", allowed = ["XXX", "XXY", "XYX", "XYY", "YXZ"]
Output: false
Explanation:
We can't stack the pyramid to the top.
Note that there could be allowed triples (A, B, C) and (A, B, D) with C != D.

Note:

  • bottom will be a string with length in range [2, 8].
  • allowed will have length in range [0, 200].
  • Letters in all strings will be chosen from the set {‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’}.

这道题让我们累一个金字塔,用字母来代表每块砖的颜色,给了一个allowed数组,里面都是长度为三的字符串,比如“ABC”表示C可以放在A和B的上方,注意AB上面也可以放其他的,比如“ABD”也可以同时出现,不过搭积木的时候只能选择一种。给了我们一个bottom字符串,是金字塔的底层,问我们能不能搭出一个完整的金字塔。那么实际上我们就是从底层开始,一层一层的向上来累加,直到搭出整个金字塔。我们先来看递归的解法,首先由于我们想快速知道两个字母上方可以放的字母,需要建立基座字符串和上方字符集合之间的映射,由于上方字符可以不唯一,所以用个HashSet来放字符。我们的递归函数有三个参数,当前层字符串cur,上层字符串above,还有我们的HashMap。如果cur的大小为2,above的大小为1,那么说明当前已经达到金字塔的顶端了,已经搭出来了,直接返回true。否则看,如果上一层的长度比当前层的长度正好小一个,说明上一层也搭好了,我们现在去搭上上层,于是调用递归函数,将above当作当前层,空字符串为上一层,将调用的递归函数结果直接返回。否则表示我们还需要继续去搭above层,我们先算出above层的长度pos,然后从当前层的pos位置开始取两个字符,就是above层接下来需要搭的字符的基座字符,举个例子如下:

1
2
3
  D   
/ \ / \
A B C

我们看到现在above层只有一个D,那么pos为1,在cur层1位置开始取两个字符,得到”BC”,即是D的下一个位置的字符的基座字符串base。取出了base后,如果HashMap中有映射,则我们遍历其映射的字符集合中的所有字符,对每个字符都调用递归函数,此时above字符串需要加上这个遍历到的字符,因为我们在尝试填充这个位置,如果有返回true的,那么当前递归函数就返回true了,否则最终返回false,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
bool pyramidTransition(string bottom, vector<string>& allowed) {
unordered_map<string, unordered_set<char>> m;
for (string str : allowed) {
m[str.substr(0, 2)].insert(str[2]);
}
return helper(bottom, "", m);
}
bool helper(string cur, string above, unordered_map<string, unordered_set<char>>& m) {
if (cur.size() == 2 && above.size() == 1) return true;
if (above.size() == cur.size() - 1) return helper(above, "", m);
int pos = above.size();
string base = cur.substr(pos, 2);
if (m.count(base)) {
for (char ch : m[base]) {
if (helper(cur, above + string(1, ch), m)) {
return true;
}
}
}
return false;
}
};

下面来看一种迭代的写法,这是一种DP的解法,建立一个三维的dp数组,其中dp[i][j][ch]表示在金字塔(i, j)位置上是否可以放字符ch,金字塔的宽和高已经确定了,都是n。每个位置对应着nxn的数组的左半边,如下所示:

F _ _
D E _
A B C

除了底层,每个位置可能可以放多个字符,所以这里dp数组是一个三维数组,第三维的长度为7,因为题目中限定了字母只有A到G共7个,如果dp值为true,表示该位置放该字母,我们根据bottom字符串来初始化dp数组的底层。这里还是需要一个HashMap,不过跟上面的解法略有不同的是,我们建立上方字母跟其能放的基座字符串集合的映射,因为一个字母可能可以放多个位置,所以用个集合来表示。然后我们就开始从倒数第二层开始往顶部更新啦,对于金字塔的每个位置,我们都遍历A到G中所有的字母,如果当前字母在HashMap中有映射,则我们遍历对应的基座字符串集合中的所有字符串,基座字符串共有两个字母,左边的字母对应的金字塔中的位置是(i + 1, j),右边的字母对应的位置是(i + 1, j + 1),我们只要在这两个位置上分别查询对应的字母的dp值是否为true,是的话,说明当前位置有字母可以放,我们将当前位置的字母对应的dp值赋值为true。这样,当我们整个更新完成了之后,我们只要看金字塔顶端位置(0, 0)是否有字母可以放,有的话,说明可以搭出金字塔,返回true,否则返回false,参见代码如下:

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
class Solution {
public:
bool pyramidTransition(string bottom, vector<string>& allowed) {
int n = bottom.size();
vector<vector<vector<bool>>> dp(n, vector<vector<bool>>(n, vector<bool>(7, false)));
unordered_map<char, unordered_set<string>> m;
for (string str : allowed) {
m[str[2]].insert(str.substr(0, 2));
}
for (int i = 0; i < n; ++i) {
dp[n - 1][i][bottom[i] - 'A'] = true;
}
for (int i = n - 2; i >= 0; --i) {
for (int j = 0; j <= i; ++j) {
for (char ch = 'A'; ch <= 'G'; ++ch) {
if (!m.count(ch)) continue;
for (string str : m[ch]) {
if (dp[i + 1][j][str[0] - 'A'] && dp[i + 1][j + 1][str[1] - 'A']) {
dp[i][j][ch - 'A'] = true;
}
}
}
}
}
for (int i = 0; i < 7; ++i) {
if (dp[0][0][i]) return true;
}
return false;
}
};

Leetcode761. Special Binary String

Special binary strings are binary strings with the following two properties:

  • The number of 0’s is equal to the number of 1’s.
  • Every prefix of the binary string has at least as many 1’s as 0’s.

Given a special string S, a move consists of choosing two consecutive, non-empty, special substrings of S, and swapping them. (Two strings are consecutive if the last character of the first string is exactly one index before the first character of the second string.)

At the end of any number of moves, what is the lexicographically largest resulting string possible?

Example 1:

1
2
3
4
5
Input: S = "11011000"
Output: "11100100"
Explanation:
The strings "10" [occuring at S[1]] and "1100" [at S[3]] are swapped.
This is the lexicographically largest string possible after some number of swaps.

Note:

  • S has length at most 50.
  • S is guaranteed to be a special binary string as defined above.

这道题给了我们一个特殊的二进制字符串,说是需要满足两个要求,一是0和1的个数要相等,二是任何一个前缀中的1的个数都要大于等于0的个数。根据压力山大大神的帖子,其实就是一个括号字符串啊。这里的1表示左括号,0表示右括号,那么题目中的两个限制条件其实就是限定这个括号字符串必须合法,即左右括号的个数必须相同,且左括号的个数随时都要大于等于右括号的个数,可以参见类似的题目Valid Parenthesis String。那么这道题让我们通过交换子字符串,生成字母顺序最大的特殊字符串,注意这里交换的子字符串也必须是特殊字符串,满足题目中给定的两个条件,换作括号来说就是交换的子括号字符串也必须是合法的。那么我们来想什么样的字符串是字母顺序最大的呢,根据题目中的例子可以分析得出,应该是1靠前的越多越好,那么换作括号来说就是括号嵌套多的应该放在前面。比如我们分析题目中的例子:

11011000 -> (()(()))

11100100 -> ((())())

我们发现,题目中的例子中的交换操作其实是将上面的红色部分和蓝色部分交换了,因为蓝色的部分嵌套的括号多,那么左括号就多,在前面的1就多,所以字母顺序大。所以我们要做的就是将中间的子串分别提取出来,然后排序,再放回即可。上面的这个例子相对简单一些,实际上上面的红色和蓝色部分完全可以更复杂,所以再给它们排序之前,其自身的顺序应该已经按字母顺序排好了才行,这种特点天然适合递归的思路,先递归到最里层,然后一层一层向外扩展,直至完成所有的排序。

好,下面我们来看递归函数的具体写法,由于我们移动的子字符串也必须是合法的,那么我们利用检测括号字符串合法性的一个最常用的方法,就是遇到左括号加1,遇到右括号-1,这样得到0的时候,就是一个合法的子字符串了。我们用变量i来统计这个合法子字符串的起始位置,字符串数组v来保存这些合法的子字符串。好了,我们开始遍历字符串S,遇到1,cnt自增1,否则自减1。当cnt为0时,我们将这个字串加入v,注意前面说过,我们需要给这个字串自身也排序,所以我们要对自身调用递归函数,我们不用对整个子串调用递归,因为字串的起始位置和结束位置是确定的,一定是1和0,我们只需对中间的调用递归即可,然后更新i为j+1。当我们将所有排序后的合法字串存入v中后,我们对v进行排序,将字母顺序大的放前面,最后将其连为一个字符串即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
string makeLargestSpecial(string S) {
int cnt = 0, i = 0;
vector<string> v;
string res = "";
for (int j = 0; j < S.size(); ++j) {
cnt += (S[j] == '1') ? 1 : -1;
if (cnt == 0) {
v.push_back('1' + makeLargestSpecial(S.substr(i + 1, j - i - 1)) + '0');
i = j + 1;
}
}
sort(v.begin(), v.end(), greater<string>());
for (int i = 0; i < v.size(); ++i) res += v[i];
return res;
}
};

Leetcode762. Prime Number of Set Bits in Binary Representation

Given two integers L and R, find the count of numbers in the range [L, R] (inclusive) having a prime number of set bits in their binary representation.

(Recall that the number of set bits an integer has is the number of 1s present when written in binary. For example, 21 written in binary is 10101 which has 3 set bits. Also, 1 is not a prime.)

Example 1:

1
2
3
4
5
6
7
Input: L = 6, R = 10
Output: 4
Explanation:
6 -> 110 (2 set bits, 2 is prime)
7 -> 111 (3 set bits, 3 is prime)
9 -> 1001 (2 set bits , 2 is prime)
10->1010 (2 set bits , 2 is prime)

Example 2:

1
2
3
4
5
6
7
8
9
Input: L = 10, R = 15
Output: 5
Explanation:
10 -> 1010 (2 set bits, 2 is prime)
11 -> 1011 (3 set bits, 3 is prime)
12 -> 1100 (2 set bits, 2 is prime)
13 -> 1101 (3 set bits, 3 is prime)
14 -> 1110 (3 set bits, 3 is prime)
15 -> 1111 (4 set bits, 4 is not prime)

判断一个范围内的数的二进制表示中的1的个数是不是一个质数……无聊

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:

bool isprime(int x) {
if(x == 0 || x == 1)
return false;
int xx = sqrt(x);
for(int i = 2; i <= xx; i ++) {
if(!(x % i))
return false;
}
return true;
}

int getbit(int x) {
int res = 0;
while(x) {
int temp = x & 1;
res = temp == 1 ? res+1 : res;
x = x >> 1;
}
return res;
}

int countPrimeSetBits(int L, int R) {
int res = 0;
for(int i = L; i <= R; i ++) {
int bit_num = getbit(i);
printf("%d\n", bit_num);
if(isprime(bit_num))
res ++;
}
return res;
}
};

Leetcode763. Partition Labels

A string S of lowercase letters is given. We want to partition this string into as many parts as possible so that each letter appears in at most one part, and return a list of integers representing the size of these parts.

Example 1:

1
2
Input: S = "ababcbacadefegdehijhklij"
Output: [9,7,8]

Explanation:

  • The partition is “ababcbaca”, “defegde”, “hijhklij”.
  • This is a partition so that each letter appears in at most one part.
  • A partition like “ababcbacadefegde”, “hijhklij” is incorrect, because it splits S into less parts.

Note:

  • S will have length in range [1, 500].
  • S will consist of lowercase letters (‘a’ to ‘z’) only.

字符串分割,且每一个字符只能最多出现在一个子串中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
vector<int> partitionLabels(string S) {
vector<int> res;
int a[26];
for(int i=0;i<S.size();i++){
a[((int)(S[i]-'a'))]=i;
}
int j=0,temp =0;
for(int i=0;i<S.size();i++){
j=max(j,a[((int)(S[i]-'a'))]);
if(i==j){
res.push_back(i-temp+1);
temp = i+1;
}
}

return res;
}
};

Leetcode764. Largest Plus Sign

In a 2D grid from (0, 0) to (N-1, N-1), every cell contains a 1, except those cells in the given list mines which are 0. What is the largest axis-aligned plus sign of 1s contained in the grid? Return the order of the plus sign. If there is none, return 0.

An “ axis-aligned plus sign of1s of order k” has some center grid[x][y] = 1 along with 4 arms of length k-1going up, down, left, and right, and made of 1s. This is demonstrated in the diagrams below. Note that there could be 0s or 1s beyond the arms of the plus sign, only the relevant area of the plus sign is checked for 1s.

Examples of Axis-Aligned Plus Signs of Order k:

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

Order 2:
00000
00100
01110
00100
00000

Order 3:
0000000
0001000
0001000
0111110
0001000
0001000
0000000

Example 1:

1
2
3
4
5
6
7
8
9
Input: N = 5, mines = [[4, 2]]
Output: 2
Explanation:
11111
11111
11111
11111
11011
In the above grid, the largest plus sign can only be order 2. One of them is marked in bold.

Example 2:

1
2
3
4
Input: N = 2, mines = []
Output: 1
Explanation:
There is no plus sign of order 2, but there is of order 1.

Example 3:

1
2
3
4
Input: N = 1, mines = [[0, 0]]
Output: 0
Explanation:
There is no plus sign, so return 0.

Note:

  • N will be an integer in the range [1, 500].
  • mines will have length at most 5000.
  • mines[i] will be length 2 and consist of integers in the range [0, N-1].

(Additionally, programs submitted in C, C++, or C# will be judged with a slightly smaller time limit.)

这道题给了我们一个数字N,表示一个NxN的二位数字,初始化均为1,又给了一个mines数组,里面是一些坐标,表示数组中这些位置都为0,然后让我们找最大的加型符号。所谓的加型符号是有数字1组成的一个十字型的加号,题目中也给出了长度分别为1,2,3的加型符号的样子。好,理解了题意以后,我们来想想该如何破题。首先,最简单的就是考虑暴力搜索啦,以每个1为中心,向四个方向分别去找,只要任何一个方向遇到了0就停止,然后更新结果res。暴力搜索的时间复杂度之所以高的原因是因为对于每一个1都要遍历其上下左右四个方向,有大量的重复计算,我们为了提高效率,可以对于每一个点,都计算好其上下左右连续1的个数。博主最先用的方法是建立四个方向的dp数组,dp[i][j]表示 (i, j) 位置上该特定方向连续1的个数,那么就需要4个二维dp数组,举个栗子,比如:

原数组:

1
2
3
1  0  1  0
1 1 1 1
1 0 1 1

那么我们建立left数组是当前及其左边连续1的个数,如下所示:

1
2
3
1  0  1  0
1 2 3 4
1 0 1 2

right数组是当前及其右边连续1的个数,如下所示:

1
2
3
1  0  1  0
4 3 2 1
1 0 2 1

up数组是当前及其上边连续1的个数,如下所示:

1
2
3
1  0  1  0
2 1 2 1
3 0 3 2

down数组是当前及其下边连续1的个数,如下所示:

1
2
3
3  0  3  0
2 1 2 2
1 0 1 1

我们需要做的是在这四个dp数组中的相同位置的四个值中取最小的一个,然后在所有的这些去除的最小值中选最大一个返回即可。为了节省空间,我们不用四个二维dp数组,而只用一个就可以了,因为对于每一个特定位置,我们只需要保留较小值,所以在更新的时候,只需要跟原来值相比取较小值即可。在计算down数组的时候,我们就可以直接更新结果res了,因为四个值都已经计算过了,我们就不用再重新在外面开for循环了,参见代码如下:

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:
int orderOfLargestPlusSign(int N, vector<vector<int>>& mines) {
int res = 0, cnt = 0;
vector<vector<int>> dp(N, vector<int>(N, 0));
unordered_set<int> s;
for (auto mine : mines) s.insert(mine[0] * N + mine[1]);
for (int j = 0; j < N; ++j) {
cnt = 0;
for (int i = 0; i < N; ++i) { // up
cnt = s.count(i * N + j) ? 0 : cnt + 1;
dp[i][j] = cnt;
}
cnt = 0;
for (int i = N - 1; i >= 0; --i) { // down
cnt = s.count(i * N + j) ? 0 : cnt + 1;
dp[i][j] = min(dp[i][j], cnt);
}
}
for (int i = 0; i < N; ++i) {
cnt = 0;
for (int j = 0; j < N; ++j) { // left
cnt = s.count(i * N + j) ? 0 : cnt + 1;
dp[i][j] = min(dp[i][j], cnt);
}
cnt = 0;
for (int j = N - 1; j >= 0; --j) { // right
cnt = s.count(i * N + j) ? 0 : cnt + 1;
dp[i][j] = min(dp[i][j], cnt);
res = max(res, dp[i][j]);
}
}
return res;
}
};

LeetCode765. Couples Holding Hands

N couples sit in 2N seats arranged in a row and want to hold hands. We want to know the minimum number of swaps so that every couple is sitting side by side. A swap consists of choosing any two people, then they stand up and switch seats.

The people and seats are represented by an integer from 0 to 2N-1, the couples are numbered in order, the first couple being (0, 1), the second couple being (2, 3), and so on with the last couple being (2N-2, 2N-1).

The couples’ initial seating is given by row[i] being the value of the person who is initially sitting in the i-th seat.

Example 1:

1
2
3
Input: row = [0, 2, 1, 3]
Output: 1
Explanation: We only need to swap the second (row[1]) and third (row[2]) person.

Example 2:

1
2
3
Input: row = [3, 2, 0, 1]
Output: 0
Explanation: All couples are already seated side by side.

Note:

  • len(row) is even and in the range of [4, 60].
  • row is guaranteed to be a permutation of 0…len(row)-1.

这道题给了我们一个长度为n的数组,里面包含的数字是 [0, n-1] 范围内的数字各一个,让通过调换任意两个数字的位置,使得相邻的奇偶数靠在一起。因为要两两成对,所以题目限定了输入数组必须是偶数个。要明确的是,组成对儿的两个是从0开始,每两个一对儿的。比如0和1,2和3,像1和2就不行。而且检测的时候也是两个数两个数的检测,左右顺序无所谓,比如2和3,或者3和2都行。当暂时对如何用代码来解决问题没啥头绪的时候,一个很好的办法是,先手动解决问题,意思是,假设这道题不要求你写代码,就让你按照要求排好序怎么做。随便举个例子来说吧,比如:

[3 1 4 0 2 5]

如何将其重新排序呢?首先明确,交换数字位置的动机是要凑对儿,如果交换的两个数字无法组成新对儿,那么这个交换就毫无意义。来手动交换吧,两个两个的来看数字,前两个数是3和1,知道其不成对儿,数字3的老相好是2,不是1,那么怎么办呢?就把1和2交换位置呗。好,那么现在3和2牵手成功,度假去了,再来看后面的:

[3 2 4 0 1 5]

再取两数字,4和0,互不认识!4跟5有一腿儿,不是0,那么就把0和5,交换一下吧,得到:

[3 2 4 5 1 0]

好了,再取最后两个数字,1和0,两口子,不用动!前面都成对的话,最后两个数字一定成对。而且这种方法所用的交换次数一定是最少的,不要问博主怎么证明,博主也不会 |||-.-~ 明眼人应该已经看出来了,这就是一种贪婪算法 Greedy Algorithm。思路有了,代码就很容易写了,注意这里在找老伴儿时用了一个 trick,一个数 ‘异或’ 上1就是其另一个位,这个不难理解,如果是偶数的话,最后位是0,‘异或’上1等于加了1,变成了可以的成对奇数。如果是奇数的话,最后位是1,‘异或’上1后变为了0,变成了可以的成对偶数。参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int minSwapsCouples(vector<int>& row) {
int res = 0, n = row.size();
for (int i = 0; i < n; i += 2) {
if (row[i + 1] == (row[i] ^ 1)) continue;
++res;
for (int j = i + 1; j < n; ++j) {
if (row[j] == (row[i] ^ 1)) {
row[j] = row[i + 1];
row[i + 1] = row[i] ^ 1;
break;
}
}
}
return res;
}
};

下面来看一种使用联合查找 Union Find 的解法。该解法对于处理群组问题时非常有效,比如岛屿数量有关的题就经常使用 UF 解法。核心思想是用一个 root 数组,每个点开始初始化为不同的值,如果两个点属于相同的组,就将其中一个点的 root 值赋值为另一个点的位置,这样只要是相同组里的两点,通过 find 函数会得到相同的值。 那么如果总共有n个数字,则共有 n/2 对儿,所以初始化 n/2 个群组,还是每次处理两个数字。每个数字除以2就是其群组号,那么属于同一组的两个数的群组号是相同的,比如2和3,其分别除以2均得到1,所以其组号均为1。那么这对解题有啥作用呢?作用忒大了,由于每次取的是两个数,且计算其群组号,并调用 find 函数,那么如果这两个数的群组号相同,那么 find 函数必然会返回同样的值,不用做什么额外动作,因为本身就是一对儿。如果两个数不是一对儿,那么其群组号必然不同,在二者没有归为一组之前,调用 find 函数返回的值就不同,此时将二者归为一组,并且 cnt 自减1,忘说了,cnt 初始化为总群组数,即 n/2。那么最终 cnt 减少的个数就是交换的步数,但是这里为了简便,直接用个 res 变量来统计群组减少的个数,还是用上面讲解中的例子来说明吧:

[3 1 4 0 2 5]

最开始的群组关系是:

群组0:0,1

群组1:2,3

群组2:4,5

取出前两个数字3和1,其群组号分别为1和0,带入 find 函数返回不同值,则此时将群组0和群组1链接起来,变成一个群组,则此时只有两个群组了,res 自增1,变为了1。

群组0 & 1:0,1,2,3

群组2:4,5

此时取出4和0,其群组号分别为2和0,带入 find 函数返回不同值,则此时将群组 0&1 和群组2链接起来,变成一个超大群组,res 自增1,变为了2。

群组0 & 1 & 2:0,1,2,3,4,5

此时取出最后两个数2和5,其群组号分别为1和2,因为此时都是一个大组内的了,带入 find 函数返回相同的值,不做任何处理。最终交换的步数就是 res 值,为2,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int minSwapsCouples(vector<int>& row) {
int res = 0, n = row.size();
vector<int> root(n, 0);
for (int i = 0; i < n; ++i) root[i] = i;
for (int i = 0; i < n; i += 2) {
int x = find(root, row[i] / 2);
int y = find(root, row[i + 1] / 2);
if (x != y) {
root[x] = y;
++res;
}
}
return res;
}
int find(vector<int>& root, int i) {
return (i == root[i]) ? i : find(root, root[i]);
}
};

Leetcode766. Toeplitz Matrix

A matrix is Toeplitz if every diagonal from top-left to bottom-right has the same element. Now given an M x N matrix, return True if and only if the matrix is Toeplitz.

Example 1:

1
2
3
4
5
6
7
8
9
10
11
Input:
matrix = [
[1,2,3,4],
[5,1,2,3],
[9,5,1,2]
]
Output: True
Explanation:
In the above grid, the diagonals are:
"[9]", "[5, 5]", "[1, 1, 1]", "[2, 2, 2]", "[3, 3]", "[4]".
In each diagonal all elements are the same, so the answer is True.

Example 2:

1
2
3
4
5
6
7
8
Input:
matrix = [
[1,2],
[2,2]
]
Output: False
Explanation:
The diagonal "[1, 2]" has different elements.

矩阵坐标瞎转换……强行模拟出来了。

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
class Solution {
public:
bool isToeplitzMatrix(vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();

for(int i = 0; i < m; i ++) {
int ii = i;
int constant = matrix[ii][0];
for(int j = 0; j < n && ii < m; ii ++, j ++)
if(constant != matrix[ii][j]) {
return false;
}
}

for(int j = 0; j < n; j ++) {
int jj = j;
int constant = matrix[0][jj];
for(int i = 0; i < m && jj < n; i ++, jj ++)
if(constant != matrix[i][jj]) {
return false;
}
}
return true;
}
};

Leetcode767. Reorganize String

Given a string S, check if the letters can be rearranged so that two characters that are adjacent to each other are not the same.

If possible, output any possible result. If not possible, return the empty string.

Example 1:

1
2
Input: S = "aab"
Output: "aba"

Example 2:

1
2
Input: S = "aaab"
Output: ""

Note:

  • S will consist of lowercase letters and have length in range [1, 500].

这道题给了一个字符串,让我们重构这个字符串,使得相同的字符不会相邻,如果无法做到,就返回空串,题目中的例子很好的说明了这一点。要统计每个字符串出现的次数啊,这里使用 HashMap 来建立字母和其出现次数之间的映射。由于希望次数多的字符排前面,可以使用一个最大堆,C++ 中就是优先队列 Priority Queue,将次数当做排序的 key,把次数和其对应的字母组成一个 pair,放进最大堆中自动排序。这里其实有个剪枝的 trick,如果某个字母出现的频率大于总长度的一半了,那么必然会有两个相邻的字母出现。这里博主就不证明了,感觉有点像抽屉原理。所以在将映射对加入优先队列时,先判断下次数,超过总长度一半了的话直接返回空串就行了。

接下来,每次从优先队列中取队首的两个映射对儿处理,因为要拆开相同的字母,这两个映射对儿肯定是不同的字母,可以将其放在一起,之后需要将两个映射对儿中的次数自减1,如果还有多余的字母,即减1后的次数仍大于0的话,将其再放回最大堆。由于是两个两个取的,所以最后 while 循环退出后,有可能优先队列中还剩下了一个映射对儿,此时将其加入结果 res 即可。而且这个多余的映射对儿一定只有一个字母了,因为提前判断过各个字母的出现次数是否小于等于总长度的一半,按这种机制来取字母,不可能会剩下多余一个的相同的字母,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
string reorganizeString(string S) {
string res = "";
unordered_map<char, int> m;
priority_queue<pair<int, char>> q;
for (char c : S) ++m[c];
for (auto a : m) {
if (a.second > (S.size() + 1) / 2) return "";
q.push({a.second, a.first});
}
while (q.size() >= 2) {
auto t1 = q.top(); q.pop();
auto t2 = q.top(); q.pop();
res.push_back(t1.second);
res.push_back(t2.second);
if (--t1.first > 0) q.push(t1);
if (--t2.first > 0) q.push(t2);
}
if (q.size() > 0) res.push_back(q.top().second);
return res;
}
};

下面这种解法使用了一个长度为 26 的一位数组 cnt 来代替上面的 HashMap 进行统计字母的出现次数,然后比较秀的一点是,把上面的映射对儿压缩成了一个整数,做法是将次数乘以了 100,再加上当前字母在一位数字中的位置坐标i,这样一个整数就同时 encode 了次数和对应字母的信息了,而且之后 decode 也很方便。数组 cnt 更新好了后,需要排个序,这一步就是模拟上面解法中最大堆的自动排序功能。不过这里是数字小的在前面,即先处理出现次数少的字母。这里除了和上面一样检测次数不能大于总长度的一半的操作外,还有一个小 trick,就是构建字符串的时候,是从第二个位置开始的。这里构建的字符串是直接对原字符串S进行修改的,因为 cnt 数组建立了之后,字符串S就没啥用了。用一个变量 idx 来表示当前更新字母的位置,初始化为1,表示要从第二个位置开始更新。因为出现次数最多的字母一定要占据第一个位置才行,这就是留出第一个位置的原因。这里很叼的一点,就是隔位更新,这样能保证相同的字母不相邻,而且当 idx 越界后,拉回到起始位置0,这就有点遍历循环数组的感觉。举个栗子来说吧,比如 “aaabbc”,更新顺序为:

_ c _ _ _ _

_ c _ b _ _

_ c _ b _ b

a c _ b _ b

a c a b _ b

a c a b a b

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
string reorganizeString(string S) {
int n = S.size(), idx = 1;
vector<int> cnt(26, 0);
for (char c : S) cnt[c - 'a'] += 100;
for (int i = 0; i < 26; ++i) cnt[i] += i;
sort(cnt.begin(), cnt.end());
for (int num : cnt) {
int t = num / 100;
char ch = 'a' + (num % 100);
if (t > (n + 1) / 2) return "";
for (int i = 0; i < t; ++i) {
if (idx >= n) idx = 0;
S[idx] = ch;
idx += 2;
}
}
return S;
}
};

Leetcode769. Max Chunks To Make Sorted

You are given an integer array arr of length n that represents a permutation of the integers in the range [0, n - 1].

We split arr into some number of chunks (i.e., partitions), and individually sort each chunk. After concatenating them, the result should equal the sorted array.

Return the largest number of chunks we can make to sort the array.

Example 1:

1
2
3
4
5
Input: arr = [4,3,2,1,0]
Output: 1
Explanation:
Splitting into two or more chunks will not return the required result.
For example, splitting into [4, 3], [2, 1, 0] will result in [3, 4, 0, 1, 2], which isn't sorted.

Example 2:

1
2
3
4
5
Input: arr = [1,0,2,3,4]
Output: 4
Explanation:
We can split into two chunks, such as [1, 0], [2, 3, 4].
However, splitting into [1, 0], [2], [3], [4] is the highest number of chunks possible.

题目描述:给出0到arr.length-1之间的一个数组,将这些数组分成一些“块”(分区),并对每个块进行单独的排序,然后将
它们连接后,结果等于排序后的数组,问最多能分多少个分区块

思路:题中有一个点很重要,那就是permutation of [0, 1, …, arr.length - 1],数组中的元素是0到arr.length-1之间的
所以如果有序的话就是arr[i] == i,那么本身有序的数可以自成一段,而无序的只需要找到最大的那个错序数,作为分段的终点
也就是max 是i 之前中的最大数,如果max == i 那么就保证了前面的数字能够排列成正确的顺序,这就是一个分段,size++,
然后继续向下遍历,找到下一个满足max == i 的地方,就可以又分成一个块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int maxChunksToSorted(vector<int>& arr) {
if(arr.size() == 0)
return 0;
vector<int> maxx(arr.size(), 0);
maxx[0] = arr[0];
for (int i = 1; i < arr.size(); i ++) {
maxx[i] = std::max(maxx[i-1], arr[i]);
}
int count = 0;
for (int i = 0; i < arr.size(); i ++) {
if(i == maxx[i])
count ++;
}
return count;
}
};

Leetcode773. Sliding Puzzle

On a 2x3 board, there are 5 tiles represented by the integers 1 through 5, and an empty square represented by 0.

A move consists of choosing 0 and a 4-directionally adjacent number and swapping it.

The state of the board is solved if and only if the board is [[1,2,3],[4,5,0]].

Given a puzzle board, return the least number of moves required so that the state of the board is solved. If it is impossible for the state of the board to be solved, return -1.

Examples:

1
2
3
Input: board = [[1,2,3],[4,0,5]]
Output: 1
Explanation: Swap the 0 and the 5 in one move.
1
2
3
Input: board = [[1,2,3],[5,4,0]]
Output: -1
Explanation: No number of moves will make the board solved.
1
2
3
4
5
6
7
8
9
10
Input: board = [[4,1,2],[5,0,3]]
Output: 5
Explanation: 5 is the smallest number of moves that solves the board.
An example path:
After move 0: [[4,1,2],[5,0,3]]
After move 1: [[4,1,2],[0,5,3]]
After move 2: [[0,1,2],[4,5,3]]
After move 3: [[1,0,2],[4,5,3]]
After move 4: [[1,2,0],[4,5,3]]
After move 5: [[1,2,3],[4,5,0]]

由于0的位置只有6个,我们可以列举出所有其下一步可能移动到的位置。为了知道每次移动后拼图是否已经恢复了正确的位置,我们肯定需要用个常量表示出正确位置以作为比较,那么对于这个正确的位置,我们还用二维数组表示吗?也不是不行,但我们可以更加简洁一些,就用一个字符串 “123450”来表示就行了,注意这里我们是把第二行直接拼接到第一行后面的,数字3和4起始并不是相连的。好,下面来看0在不同位置上能去的地方,字符串长度为6,则其坐标为 012345,转回二维数组为:

1
2
0  1  2
3 4 5

那么当0在位置0时,其可以移动到右边和下边,即{1, 3}位置;在位置1时,其可以移动到左边,右边和下边,即{0, 2, 4}位置;在位置2时,其可以移动到左边和下边,即{1, 5}位置;在位置3时,其可以移动到上边和右边,即{0, 4}位置;在位置4时,其可以移动到左边,右边和上边,即{1, 3, 5}位置;在位置5时,其可以移动到上边和左边,即{2, 4}位置。

然后就是标准的BFS的流程了,使用一个HashSet来记录访问过的状态,将初始状态start放入,使用一个queue开始遍历,将初始状态start放入。然后就是按层遍历,取出队首状态,先和target比较,相同就直接返回步数,否则就找出当前状态中0的位置,到dirs中去找下一个能去的位置,赋值一个临时变量cand,去交换0和其下一个位置,生成一个新的状态,如果这个状态不在visited中,则加入visited,并且压入队列queue,步数自增1。如果while循环退出后都没有回到正确状态,则返回-1,参见代码如下:

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
class Solution {
public:
int slidingPuzzle(vector<vector<int>>& board) {
int n = board.size(), m = board[0].size();
int res = 0;
string end = "123450", start = "";
unordered_set<string> visited;
queue<string> q;
vector<vector<int>> dirs{{1,3}, {0,2,4}, {1,5}, {0,4}, {1,3,5}, {2,4}};
for (int i = 0; i < n; i ++)
for (int j = 0; j < m; j ++)
start += to_string(board[i][j]);
visited.insert(start);
q.push(start);
while(!q.empty()) {
int size = q.size();
for (int i = 0; i < size; i ++) {
string t = q.front(), next;
q.pop();
if (t == end)
return res;
int cur = t.find("0");

for (int j = 0; j < dirs[cur].size(); j ++) {
next = t;
next[cur] = t[dirs[cur][j]];
next[dirs[cur][j]] = t[cur];
if (visited.count(next))
continue;
visited.insert(next);
q.push(next);
}
}
res ++;
}

return -1;
}
};

Leetcode775. Global and Local Inversions

We have some permutation A of [0, 1, …, N - 1], where N is the length of A.

The number of (global) inversions is the number of i < j with 0 <= i < j < N and A[i] > A[j].

The number of local inversions is the number of i with 0 <= i < N and A[i] > A[i+1].

Return true if and only if the number of global inversions is equal to the number of local inversions.

Example 1:

1
2
3
Input: A = [1,0,2]
Output: true
Explanation: There is 1 global inversion, and 1 local inversion.

Example 2:

1
2
3
Input: A = [1,2,0]
Output: false
Explanation: There are 2 global inversions, and 1 local inversion.

Note:

  • A will be a permutation of [0, 1, …, A.length - 1].
  • A will have length in range [1, 5000].
  • The time limit for this problem has been reduced.

这道题给了一个长度为n的数组,里面是0到n-1数字的任意排序。又定义了两种倒置方法,全局倒置和局部倒置。其中全局倒置说的是坐标小的值大,局部倒置说的是相邻的两个数,坐标小的值大。那么我们可以发现,其实局部倒置是全局倒置的一种特殊情况,即局部倒置一定是全局倒置,而全局倒置不一定是局部倒置,这是解这道题的关键点。题目让我们判断该数组的全局倒置和局部倒置的个数是否相同,那么我们想,什么情况下会不相同?如果所有的倒置都是局部倒置,那么由于局部倒置一定是全局倒置,则二者个数一定相等。如果出现某个全局倒置不是局部倒置的情况,那么二者的个数一定不会相等。所以问题的焦点就变成了是否能找出不是局部倒置的全局倒置。所以为了和局部倒置区别开来,我们不能比较相邻的两个,而是至少要隔一个来比较。我们可以从后往前遍历数组,遍历到第三个数字停止,然后维护一个 [i, n-1] 范围内的最小值,每次和 A[i - 2] 比较,如果小于 A[i - 2],说明这是个全局的倒置,并且不是局部倒置,那么我们直接返回false即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
bool isIdealPermutation(vector<int>& A) {
int n = A.size(), mn = INT_MAX;
for (int i = n - 1; i >= 2; --i) {
mn = min(mn, A[i]);
if (A[i - 2] > mn) return false;
}
return true;
}
};

Leetcode777. Swap Adjacent in LR String

In a string composed of ‘L’, ‘R’, and ‘X’ characters, like “RXXLRXRXL”, a move consists of either replacing one occurrence of “XL” with “LX”, or replacing one occurrence of “RX” with “XR”. Given the starting string start and the ending string end, return True if and only if there exists a sequence of moves to transform one string to the other.

Example:

1
2
3
4
5
6
7
8
9
Input: start = "RXXLRXRXL", end = "XRLXXRRLX"
Output: True
Explanation:
We can transform start to end following these steps:
RXXLRXRXL ->
XRXLRXRXL ->
XRLXRXRXL ->
XRLXXRRXL ->
XRLXXRRLX

Note:

  • 1 <= len(start) = len(end) <= 10000.
  • Both start and end will only consist of characters in {‘L’, ‘R’, ‘X’}.

这道题给了我们一个只含有L,R,X三个字符的字符串,然后说有两种操作,一种是把 “XL” 变成 “LX”,另一种是把 “RX” 变成 “XR”。博主刚开始没有读题意,以为二者是可以互换的,错误的认为认为 “LX” 也能变成 “XL”,其实题目这种变换是单向,这种单向关系就是解题的关键,具体来说,就是要把 start 字符串变成 end 字符串的话,L只能往前移动,因为是把 “XL” 变成 “LX”,同样,R只能往后移动,因为是把 “RX” 变成 “XR”。题目给的那个例子并不能很好的说明问题,博主之前那种双向变换的错误认知会跪在这个例子:

1
2
start = "XXRXXLXXXX"
end = "XXXXRXXLXX"

观察这个 test case,可以发现 start 中的R可以往后移动,没有问题,但是 start 中的L永远无法变到end中L的位置,因为L只能往前移。这道题被归类为 brainteaser,估计就是因为要观察出这个规律吧。那么搞明白这个以后,其实可以用双指针来解题,思路是,每次分别找到 start 和 end 中非X的字符,如果二者不相同的话,直接返回 false,想想问什么?这是因为不论是L还是R,其只能跟X交换位置,L和R之间是不能改变相对顺序的,所以如果分别将 start 和 end 中所有的X去掉后的字符串不相等的话,那么就永远无法让 start 和 end 相等了。这个判断完之后,就来验证L只能前移,R只能后移这个限制条件吧,当i指向 start 中的L时,那么j指向 end 中的L必须要在前面,所以如果i小于j的话,就不对了,同理,当i指向 start 中的R,那么j指向 end 中的R必须在后面,所以i大于j就是错的,最后别忘了i和j同时要自增1,不然死循环了。while 循环退出后,有可能i或j其中一个还未遍历到结尾,而此时剩余到字符中是不能再出现L或R的,否则不能成功匹配,此时用两个 while 循环分别将i或j遍历完成,需要到了L或R直接返回 false 即可,加上了这一步后就不用在开头检测 start 和 end 中L和R的个数是否相同了,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
bool canTransform(string start, string end) {
int n = start.size(), i = 0, j = 0;
while (i < n && j < n) {
while (i < n && start[i] == 'X') ++i;
while (j < n && end[j] == 'X') ++j;
if (start[i] != end[j]) return false;
if ((start[i] == 'L' && i < j) || (start[i] == 'R' && i > j)) return false;
++i; ++j;
}
while (i < n) {
if (start[i] != 'X') return false;
++i;
}
while (j < n) {
if (end[j] != 'X') return false;
++j;
}
return true;
}
};

Leetcode778. Swim in Rising Water

On an N x N grid, each square grid[i][j] represents the elevation at that point (i,j).

Now rain starts to fall. At time t, the depth of the water everywhere is t. You can swim from a square to another 4-directionally adjacent square if and only if the elevation of both squares individually are at most t. You can swim infinite distance in zero time. Of course, you must stay within the boundaries of the grid during your swim.

You start at the top left square (0, 0). What is the least time until you can reach the bottom right square (N-1, N-1)?

Example 1:

1
2
3
4
5
Input: [[0,2],[1,3]]
Output: 3
Explanation:
At time 0, you are in grid location (0, 0).
You cannot go anywhere else because 4-directionally adjacent neighbors have a higher elevation than t = 0.

You cannot reach point (1, 1) until time 3.
When the depth of water is 3, we can swim anywhere inside the grid.

Example 2:

1
2
3
Input: [[0,1,2,3,4],[24,23,22,21,5],[12,13,14,15,16],[11,17,18,19,20],[10,9,8,7,6]]
Output: 16
Explanation:

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

The final route is marked in bold. We need to wait until time 16 so that (0, 0) and (4, 4) are connected.

Note:

  • 2 <= N <= 50.
  • grid[i][j] is a permutation of [0, …, N*N - 1].

这道题给了我们一个二维数组,可以看作一个水池,这里不同数字的高度可以看作台阶的高度,只有当水面升高到台阶的高度时,我们才能到达该台阶,起始点在左上角位置,问我们水面最低升到啥高度就可以到达右下角的位置。这是一道蛮有意思的题目。对于这种类似迷宫遍历的题,一般都是DFS或者BFS。而如果有极值问题存在的时候,一般都是优先考虑BFS的,但是这道题比较特别,有一个上升水面的设定,我们可以想象一下,比如洪水爆发了,大坝垮了,那么愤怒汹涌的水流冲了出来,地势低洼处就会被淹没,而地势高的地方,比如山峰啥的,就会绕道而过。这里也是一样,随着水面不断的上升,低于水平面的地方就可以到达,直到水流到了右下角的位置停止。因为水流要向周围低洼处蔓延,所以BFS仍是一个不错的选择,由于水是向低洼处蔓延的,而低洼处的位置又是不定的,所以我们希望每次取出最低位置进行遍历,那么使用最小堆就是一个很好的选择,这样高度低的就会被先处理。在每次取出高度最小的数字时,我们用此高度来更新结果res,如果当前位置已经是右下角了,则我们直接返回结果res,否则就遍历当前位置的周围位置,如果未越界且未被访问过,则标记已经访问过,并且加入队列,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
int swimInWater(vector<vector<int>>& grid) {
int res = 0, n = grid.size();
unordered_set<int> visited{0};
vector<vector<int>> dirs{{0, -1}, {-1, 0}, {0, 1}, {1, 0}};
auto cmp = [](pair<int, int>& a, pair<int, int>& b) {return a.first > b.first;};
priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp) > q(cmp);
q.push({grid[0][0], 0});
while (!q.empty()) {
int i = q.top().second / n, j = q.top().second % n; q.pop();
res = max(res, grid[i][j]);
if (i == n - 1 && j == n - 1) return res;
for (auto dir : dirs) {
int x = i + dir[0], y = j + dir[1];
if (x < 0 || x >= n || y < 0 || y >= n || visited.count(x * n + y)) continue;
visited.insert(x * n + y);
q.push({grid[x][y], x * n + y});
}
}
return res;
}
};

我们也可以使用DP+DFS来做,这里使用一个二维dp数组,其中dp[i][j]表示到达 (i, j) 位置所需要的最低水面高度,均初始化为整型数最大值,我们的递归函数函数需要知道当前的位置 (x, y),还有当前的水高cur,同时传入grid数组和需要不停更新的dp数组,如果当前位置越界了,或者是当前水高和grid[x][y]中的较大值大于等于dp[x][y]了,直接跳过,因为此时的dp值更小,不需要被更新了。否则dp[x][y]更新为较大值,然后对周围四个位置调用递归函数继续更新dp数组,最终返回右下位置的dp值即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
vector<vector<int>> dirs{{0, -1}, {-1, 0}, {0, 1}, {1, 0}};
int swimInWater(vector<vector<int>>& grid) {
int n = grid.size();
vector<vector<int>> dp(n, vector<int>(n, INT_MAX));
helper(grid, 0, 0, grid[0][0], dp);
return dp[n - 1][n - 1];
}
void helper(vector<vector<int>>& grid, int x, int y, int cur, vector<vector<int>>& dp) {
int n = grid.size();
if (x < 0 || x >= n || y < 0 || y >= n || max(cur, grid[x][y]) >= dp[x][y]) return;
dp[x][y] = max(cur, grid[x][y]);
for (auto dir : dirs) {
helper(grid, x + dir[0], y + dir[1], dp[x][y], dp);
}
}
};

Leetcode779. K-th Symbol in Grammar

On the first row, we write a 0. Now in every subsequent row, we look at the previous row and replace each occurrence of 0 with 01, and each occurrence of 1 with 10.

Given row N and index K, return the K-th indexed symbol in row N. (The values of K are 1-indexed.) (1 indexed).

Examples:

1
2
Input: N = 1, K = 1
Output: 0
1
2
Input: N = 2, K = 1
Output: 0
1
2
Input: N = 2, K = 2
Output: 1
1
2
3
4
5
6
7
8
Input: N = 4, K = 5
Output: 1

Explanation:
row 1: 0
row 2: 01
row 3: 0110
row 4: 01101001

Note:

  • N will be an integer in the range [1, 30].
  • K will be an integer in the range [1, 2^(N-1)].

这道题说第一行写上了一个0,然后从第二行开始,遇到0,就变为01,遇到1,则变为10,问我们第N行的第K个数字是啥。这是一道蛮有意思的题目,首先如果没啥思路的话,按照给定的方法,一行行generate出来,直到生成第N行,那么第K个数字也就知道了。但是这种brute force的方法无法通过OJ,这里就不多说了,需要想一些更高端的解法。我们想啊,遇到0变为01,那么可不可以把0和1看作上一层0的左右子结点呢,同时,把1和0看作上一层1的左右子结点,这样的话,我们整个结构就可以转为二叉树了,那么前四层的二叉树结构如下所示:

1
2
3
4
5
6
7
              0
/ \
0 1
/ \ / \
0 1 1 0
/ \ / \ / \ / \
0 1 1 0 1 0 0 1

我们仔细观察上面这棵二叉树,第四层K=3的那个红色的左子结点,其父结点的位置是第三层的第 (K+1)/2 = 2个红色结点,而第四层K=6的那个蓝色幽子结点,其父节点的位置是第三层的第 K/2 = 3个蓝色结点。那么我们就可以一层一层的往上推,直到到达第一层的那个0。所以我们的思路是根据当前层K的奇偶性来确定上一层中父节点的位置,然后继续往上一层推,直到推倒第一层的0,然后再返回确定路径上每一个位置的值,这天然就是递归的运行机制啊。我们可以根据K的奇偶性知道其是左结点还是右结点,由于K是从1开始的,所以当K是奇数时,其是左结点,当K是偶数时,其是右结点。而且还能观察出来的是,左子结点和其父节点的值相同,右子结点和其父节点值相反,这是因为0换成了01,1换成了10,左子结点保持不变,右子结点flip了一下。想通了这些,那么我们的递归解法就不难写出来了,参见代码如下:

1
2
3
4
5
6
7
8
class Solution {
public:
int kthGrammar(int N, int K) {
if (N == 1) return 0;
if (K % 2 == 0) return (kthGrammar(N - 1, K / 2) == 0) ? 1 : 0;
else return (kthGrammar(N - 1, (K + 1) / 2) == 0) ? 0 : 1;
}
};

我们可以简化下上面的解法,你们可能会说,纳尼?已经三行了还要简化?没错,博主就是这样一个精益求精的人(此处应有掌声👏)。我们知道偶数加1除以2,和其本身除以2的值是相同的,那么其实不论K是奇是偶,其父节点的位置都可以用 (K+1)/2 来表示,问题在于K本身的奇偶决定了其左右结点的位置,从而决定要不要flip父节点的值,这才是上面解法中我们要使用 if…else 结构的原因。实际上我们可以通过‘亦或’操作来实现一行搞定,叼不叼。我们来看下变换规则,0换成了01,1换成了10。

0 -> 01

左子结点(0) = 父节点(0) ^ 0

右子结点(1) = 父节点(0) ^ 1

1 -> 10

左子结点(1) = 父节点(1) ^ 0

右子结点(0) = 父节点(1) ^ 1

那么只要我们知道了父结点的值和当前K的奇偶性就可以知道K的值了,因为左子结点就是父结点值‘亦或’0,右子结点就是父结点值‘亦或’1,由于左子结点的K是奇数,我们可以对其取反再‘与’1,所以就是 (~K & 1),再‘亦或’上递归函数的返回值即可,参见代码如下:

1
2
3
4
5
6
7
class Solution {
public:
int kthGrammar(int N, int K) {
if (N == 1) return 0;
return (~K & 1) ^ kthGrammar(N - 1, (K + 1) / 2);
}
};

Leetcode781. Rabbits in Forest

In a forest, each rabbit has some color. Some subset of rabbits (possibly all of them) tell you how many other rabbits have the same color as them. Those answers are placed in an array.

Return the minimum number of rabbits that could be in the forest.

Examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Input: answers = [1, 1, 2]
Output: 5
Explanation:
The two rabbits that answered "1" could both be the same color, say red.
The rabbit than answered "2" can't be red or the answers would be inconsistent.
Say the rabbit that answered "2" was blue.
Then there should be 2 other blue rabbits in the forest that didn't answer into the array.
The smallest possible number of rabbits in the forest is therefore 5: 3 that answered plus 2 that didn't.

Input: answers = [10, 10, 10]
Output: 11

Input: answers = []
Output: 0

Note:

  • answers will have length at most 1000.
  • Each answers[i] will be an integer in the range [0, 999].

这道题说的是大森林中有一堆成了精的兔子,有着不同的颜色,还会回答问题。每个兔子会告诉你森林中还有多少个和其颜色相同的兔子,当然并不是所有的兔子多出现在数组中,所以我们要根据兔子们的回答,来估计森林中最少有多少只能确定的兔子。例子1给的数字是 [1, 1, 2],第一只兔子说森林里还有另一只兔子跟其颜色一样,第二只兔子也说还有另一只兔子和其颜色一样,那么为了使兔子总数最少,我们可以让前两只兔子是相同的颜色,可以使其回答不会矛盾。第三只兔子说森林里还有两只兔子和其颜色一样,那么这只兔的颜色就不能和前两只兔子的颜色相同了,否则就会跟前面两只兔子的回答矛盾了,因为根据第三只兔子的描述,森林里共有三只这种颜色的兔子,所有总共可以推断出最少有五只兔子。对于例子2,[10, 10, 10] 来说,这三只兔子都说森林里还有10只跟其颜色相同的兔子,那么这三只兔子颜色可以相同,所以总共有11只兔子。

来看一个比较tricky的例子,[0, 0, 1, 1, 1],前两只兔子都说森林里没有兔子和其颜色相同了,那么这两只兔子就是森林里独一无二的兔子,且颜色并不相同,所以目前已经确定了两只。然后后面三只都说森林里还有一只兔子和其颜色相同,那么这三只兔子就不可能颜色都相同了,但我们可以让两只颜色相同,另外一只颜色不同,那么就是说还有一只兔子并没有在数组中,所以森林中最少有6只兔子。分析完了这几个例子,我们可以发现,如果某个兔子回答的数字是x,那么说明森林里共有x+1个相同颜色的兔子,我们最多允许x+1个兔子同时回答x个,一旦超过了x+1个兔子,那么就得再增加了x+1个新兔子了。所以我们可以使用一个HashMap来建立某种颜色兔子的总个数和在数组中还允许出现的个数之间的映射,然后我们遍历数组中的每个兔子,如果该兔子回答了x个,若该颜色兔子的总个数x+1不在HashMap中,或者映射为0了,我们将这x+1个兔子加入结果res中,然后将其映射值设为x,表示在数组中还允许出现x个也回答x的兔子;否则的话,将映射值自减1即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int numRabbits(vector<int>& answers) {
int res = 0;
unordered_map<int, int> m;
for (int ans : answers) {
if (!m.count(ans + 1) || m[ans + 1] == 0) {
res += ans + 1;
m[ans + 1] = ans;
} else {
--m[ans + 1];
}
}
return res;
}
};

Leetcode783. Minimum Distance Between BST Nodes

Given a Binary Search Tree (BST) with the root node root, return the minimum difference between the values of any two different nodes in the tree.

Example :

1
2
3
4
5
6
7
8
9
10
11
12
Input: root = [4,2,6,1,3,null,null]
Output: 1
Explanation:
Note that root is a TreeNode object, not an array.

The given tree [4,2,6,1,3,null,null] is represented by the following diagram:

4
/ \
2 6
/ \
1 3

while the minimum difference in this tree is 1, it occurs between node 1 and node 2, also between node 3 and node 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
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:

void dfs(TreeNode* root, vector<int>& v) {
if(root == NULL)
return;
dfs(root->left, v);
v.push_back(root->val);
dfs(root->right, v);
}

int minDiffInBST(TreeNode* root) {
vector<int> vec;
int minn = INT_MAX;
dfs(root, vec);
for(int i = 1; i < vec.size(); i ++) {
minn = min(minn, abs(vec[i] - vec[i-1]));
}
return minn;
}
};

Leetcode784. Letter Case Permutation

Given a string S, we can transform every letter individually to be lowercase or uppercase to create another string. Return a list of all possible strings we could create.

Examples:

1
2
3
4
5
6
7
8
Input: S = "a1b2"
Output: ["a1b2", "a1B2", "A1b2", "A1B2"]

Input: S = "3z4"
Output: ["3z4", "3Z4"]

Input: S = "12345"
Output: ["12345"]

每遇上一个字母,就从头把结果容器中已有的字符串全部过一遍,针对当前字母修改大小写,然后存入这个结果容器中。

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
class Solution {
public:

vector<string> letterCasePermutation(string S) {
vector<string> result;
result.push_back(S);
string c;
for(int i = 0; i < S.length(); i ++) {
if (('a' <= S[i]) && (S[i] <= 'z') || ('A' <= S[i]) && (S[i] <= 'Z')) {
int l = result.size();
for (int j = 0; j < l; j++) {
string tm = result[j];
if('a' <= tm[i] && tm[i] <= 'z') {
c = (tm[i]-'a'+'A');
result.push_back(tm.replace(i, 1, c));
}
else if('A' <= tm[i] && tm[i] <= 'Z') {
c = (tm[i]-'A'+'a');
result.push_back(tm.replace(i, 1, c));
}
}
}
}
return result;
}
};

Leetcode785. Is Graph Bipartite?

Given an undirected graph, return true if and only if it is bipartite.

Recall that a graph is bipartite if we can split it’s set of nodes into two independent subsets A and B such that every edge in the graph has one node in A and another node in B.

The graph is given in the following form: graph[i] is a list of indexes j for which the edge between nodes i and j exists. Each node is an integer between 0 and graph.length - 1. There are no self edges or parallel edges: graph[i] does not contain i, and it doesn’t contain any element twice.

Example 1:

1
2
3
4
5
6
7
8
9
Input: [[1,3], [0,2], [1,3], [0,2]]
Output: true
Explanation:
The graph looks like this:
0----1
| |
| |
3----2
We can divide the vertices into two groups: {0, 2} and {1, 3}.

Example 2:

1
2
3
4
5
6
7
8
9
Input: [[1,2,3], [0,2], [0,1,3], [0,2]]
Output: false
Explanation:
The graph looks like this:
0----1
| \ |
| \ |
3----2
We cannot find a way to divide the set of nodes into two independent subsets.

Note:

  • graph will have length in range [1, 100].
  • graph[i] will contain integers in range [0, graph.length - 1].
  • graph[i] will not contain i or duplicate values.
  • The graph is undirected: if any element j is in graph[i], then i will be in graph[j].

这道题让我们验证给定的图是否是二分图,所谓二分图,就是可以将图中的所有顶点分成两个不相交的集合,使得同一个集合的顶点不相连。为了验证是否有这样的两个不相交的集合存在,我们采用一种很机智的染色法,大体上的思路是要将相连的两个顶点染成不同的颜色,一旦在染的过程中发现有两连的两个顶点已经被染成相同的颜色,说明不是二分图。这里我们使用两种颜色,分别用1和 -1 来表示,初始时每个顶点用0表示未染色,然后遍历每一个顶点,如果该顶点未被访问过,则调用递归函数,如果返回 false,那么说明不是二分图,则直接返回 false。如果循环退出后没有返回 false,则返回 true。在递归函数中,如果当前顶点已经染色,如果该顶点的颜色和将要染的颜色相同,则返回 true,否则返回 false。如果没被染色,则将当前顶点染色,然后再遍历与该顶点相连的所有的顶点,调用递归函数,如果返回 false 了,则当前递归函数的返回 false,循环结束返回 true,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
bool isBipartite(vector<vector<int>>& graph) {
vector<int> colors(graph.size());
for (int i = 0; i < graph.size(); ++i) {
if (colors[i] == 0 && !valid(graph, 1, i, colors)) {
return false;
}
}
return true;
}
bool valid(vector<vector<int>>& graph, int color, int cur, vector<int>& colors) {
if (colors[cur] != 0) return colors[cur] == color;
colors[cur] = color;
for (int i : graph[cur]) {
if (!valid(graph, -1 * color, i, colors)) {
return false;
}
}
return true;
}
};

我们再来看一种迭代的解法,整体思路还是一样的,还是遍历整个顶点,如果未被染色,则先染色为1,然后使用 BFS 进行遍历,将当前顶点放入队列 queue 中,然后 while 循环 queue 不为空,取出队首元素,遍历其所有相邻的顶点,如果相邻顶点未被染色,则染成和当前顶点相反的颜色,然后把相邻顶点加入 queue 中,否则如果当前顶点和相邻顶点颜色相同,直接返回 false,循环退出后返回 true,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
bool isBipartite(vector<vector<int>>& graph) {
vector<int> colors(graph.size());
for (int i = 0; i < graph.size(); ++i) {
if (colors[i] != 0) continue;
colors[i] = 1;
queue<int> q{{i}};
while (!q.empty()) {
int t = q.front(); q.pop();
for (auto a : graph[t]) {
if (colors[a] == colors[t]) return false;
if (colors[a] == 0) {
colors[a] = -1 * colors[t];
q.push(a);
}
}
}
}
return true;
}
};

Leetcode787. Cheapest Flights Within K Stops

There are n cities connected by m flights. Each fight starts from city u and arrives at v with a price w.

Now given all the cities and fights, together with starting city src and the destination dst, your task is to find the cheapest price from src to dst with up to k stops. If there is no such route, output -1.

Example 1:

1
2
3
4
5
6
Input: 
n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
src = 0, dst = 2, k = 1
Output: 200
Explanation:
The graph looks like this:

The cheapest price from city 0 to city 2 with at most 1 stop costs 200, as marked red in the picture.

Example 2:

1
2
3
4
5
6
Input: 
n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
src = 0, dst = 2, k = 0
Output: 500
Explanation:
The graph looks like this:

The cheapest price from city 0 to city 2 with at most 0 stop costs 500, as marked blue in the picture.

Note:

  • The number of nodes n will be in range [1, 100], with nodes labeled from 0 to n`` - 1.
  • The size of flights will be in range [0, n * (n - 1) / 2].
  • The format of each flight will be (src, dst, price).
  • The price of each flight will be in the range [1, 10000].
  • k is in the range of [0, n - 1].
  • There will not be any duplicated flights or self cycles.

这道题给了我们一些航班信息,包括出发地,目的地,和价格,然后又给了我们起始位置和终止位置,说是最多能转K次机,让我们求出最便宜的航班价格。那么实际上这道题就是一个有序图的遍历问题,博主最先尝试的递归解法由于没有做优化,TLE了,实际上我们可以通过剪枝处理,从而压线过OJ。首先我们要建立这个图,选取的数据结构就是邻接链表的形式,具体来说就是建立每个结点和其所有能到达的结点的集合之间的映射,然后就是用DFS来遍历这个图了,用变量cur表示当前遍历到的结点序号,还是当前剩余的转机次数K,访问过的结点集合visited,当前累计的价格out,已经全局的最便宜价格res。在递归函数中,首先判断如果当前cur为目标结点dst,那么结果res赋值为out,并直接返回。你可能会纳闷为啥不是取二者中较小值更新结果res,而是直接赋值呢?原因是我们之后做了剪枝处理,使得out一定会小于结果res。然后判断如果K小于0,说明超过转机次数了,直接返回。然后就是遍历当前结点cur能到达的所有结点了,对于遍历到的结点,首先判断如果当前结点已经访问过了,直接跳过。或者是当前价格out加上到达这个结点需要的价格之和大于结果res的话,那么直接跳过。这个剪枝能极大的提高效率,是压线过OJ的首要功臣。之后就是标记结点访问,调用递归函数,以及还原结点状态的常规操作了,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int K) {
int res = INT_MAX;
unordered_map<int, vector<vector<int>>> m;
unordered_set<int> visited{{src}};
for (auto flight : flights) {
m[flight[0]].push_back({flight[1], flight[2]});
}
helper(m, src, dst, K, visited, 0, res);
return (res == INT_MAX) ? -1 : res;
}
void helper(unordered_map<int, vector<vector<int>>>& m, int cur, int dst, int K, unordered_set<int>& visited, int out, int& res) {
if (cur == dst) {res = out; return;}
if (K < 0) return;
for (auto a : m[cur]) {
if (visited.count(a[0]) || out + a[1] > res) continue;
visited.insert(a[0]);
helper(m, a[0], dst, K - 1, visited, out + a[1], res);
visited.erase(a[0]);
}
}
};

下面这种解法是用BFS来做的,还是来遍历图,不过这次是一层一层的遍历,需要使用queue来辅助。前面建立图的数据结构的操作和之前相同,BFS的写法还是经典的写法,但需要注意的是这里也同样的做了剪枝优化,当当前价格加上新到达位置的价格之和大于结果res的话直接跳过。最后注意如果超过了转机次数就直接break,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int K) {
int res = INT_MAX, cnt = 0;
unordered_map<int, vector<vector<int>>> m;
queue<vector<int>> q{{{src, 0}}};
for (auto flight : flights) {
m[flight[0]].push_back({flight[1], flight[2]});
}
while (!q.empty()) {
for (int i = q.size(); i > 0; --i) {
auto t = q.front(); q.pop();
if (t[0] == dst) res = min(res, t[1]);
for (auto a : m[t[0]]) {
if (t[1] + a[1] > res) continue;
q.push({a[0], t[1] + a[1]});
}
}
if (cnt++ > K) break;
}
return (res == INT_MAX) ? -1 : res;
}
};

再来看使用Bellman Ford算法的解法,关于此算法的detail可以上网搜帖子看看。核心思想还是用的动态规划Dynamic Programming,最核心的部分就是松弛操作Relaxation,也就是DP的状态转移方程。这里我们使用一个二维DP数组,其中dp[i][j]表示最多飞i次航班到达j位置时的最少价格,那么dp[0][src]初始化为0,因为飞0次航班的价格都为0,转机K次,其实就是飞K+1次航班,我们开始遍历,i从1到K+1,每次dp[i][src]都初始化为0,因为在起点的价格也为0,然后即使遍历所有的航班x,更新dp[i][x[1]],表示最多飞i次航班到达航班x的目的地的最低价格,用最多飞i-1次航班,到达航班x的起点的价格加上航班x的价格之和,二者中取较小值更新即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int K) {
vector<vector<int>> dp(K + 2, vector<int>(n, 1e9));
dp[0][src] = 0;
for (int i = 1; i <= K + 1; ++i) {
dp[i][src] = 0;
for (auto x : flights) {
dp[i][x[1]] = min(dp[i][x[1]], dp[i - 1][x[0]] + x[2]);
}
}
return (dp[K + 1][dst] >= 1e9) ? -1 : dp[K + 1][dst];
}
};

Leetcode788. Rotated Digits

X is a good number if after rotating each digit individually by 180 degrees, we get a valid number that is different from X. Each digit must be rotated - we cannot choose to leave it alone.

A number is valid if each digit remains a digit after rotation. 0, 1, and 8 rotate to themselves; 2 and 5 rotate to each other (on this case they are rotated in a different direction, in other words 2 or 5 gets mirrored); 6 and 9 rotate to each other, and the rest of the numbers do not rotate to any other number and become invalid.

Now given a positive number N, how many numbers X from 1 to N are good?

Example:

1
2
3
4
5
Input: 10
Output: 4
Explanation:
There are four good numbers in the range [1, 10] : 2, 5, 6, 9.
Note that 1 and 10 are not good numbers, since they remain unchanged after rotating.

输入N,判断数字是否是good数字。旋转180度变成另一个数,2 5 6 9旋转后是good数字。0 1 8旋转后是本身。含有3 4 7的不是good数字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
int rotatedDigits(int N) {
int count=0;
for(int i = 1; i <= N; i ++)
if(judge(i))
count ++;
return count;
}
bool judge(int N){
bool flag = false;
while(N){
int k = N % 10;
if(k == 3 || k == 4 || k == 7){
flag = false;
break;
}
if(k == 2 || k == 5 || k == 6 || k == 9)
flag = true;
N /= 10;
}
return flag;
}
};

Leetcode790. Domino and Tromino Tiling

You have two types of tiles: a 2 x 1 domino shape and a tromino shape. You may rotate these shapes.

Given an integer n, return the number of ways to tile an 2 x n board. Since the answer may be very large, return it modulo 109 + 7.

In a tiling, every square must be covered by a tile. Two tilings are different if and only if there are two 4-directionally adjacent cells on the board such that exactly one of the tilings has both squares occupied by a tile.

说是有一个2xN大小的棋盘,我们需要用这些多米诺和三格骨牌来将棋盘填满,问有多少种不同的填充方法,结果需要对一个超大数取余。

首先就来设计dp数组,这里我们就用一个一维的dp数组就行了,其中dp[i]表示填满前i列的不同填法总数对超大数10e^9+7取余后的结果。那么DP解法的难点就是求状态转移方程了,没什么太好的思路的时候,就从最简单的情况开始罗列吧。题目中给了N的范围是[1, 1000],那么我们来看:

当N=1时,那么就是一个2x1大小的棋盘,只能放一个多米诺骨牌,只有一种情况。

当N=2时,那么就是一个2x2大小的棋盘,如下图所示,我们有两种放置方法,可以将两个多米诺骨牌竖着并排放,或者是将其横着并排放。

当N=3时,那么就是一个3x2大小的棋盘,我们共用五种放置方法,如下图所示。仔细观察这五种情况,我们发现其时时跟上面的情况有联系的。前两种情况其实是N=2的两种情况后面加上了一个竖着的多米诺骨牌,第三种情况其实是N=1的那种情况后面加上了两个平行的横向的多米诺骨牌,后两种情况是N=0(空集)再加上两种三格骨牌对角摆开的情况。

当N=4时,那么就是一个4x2大小的棋盘,我们共用十一种放置方法,太多了就不一一画出来了,但是其也是由之前的情况组合而成的。首先是N=3的所有情况后面加上一个竖着多米诺骨牌,然后是N=2的所有情况加上两个平行的横向的多米诺骨牌,然后N=1再加上两种三格骨牌对角摆开的情况,然后N=0(空集)再加上两种三格骨牌和一个横向多米诺骨牌组成的情况。

根据目前的状况,我们可以总结一个很重要的规律,就是dp[n]是由之前的dp值组成的,其中 dp[n-1] 和 dp[n-2] 各自能贡献一种组成方式,而dp[n-3],一直到dp[0],都能各自贡献两种组成方式,所以状态转移方程呼之欲出:

1
2
3
4
5
6
7
dp[n] = dp[n-1] + dp[n-2] + 2 * (dp[n-3] + … + dp[0])

= dp[n-1] + dp[n-3] + dp[n-2] + dp[n-3] + 2 * (dp[n-4] + … dp[0])

= dp[n-1] + dp[n-3] + dp[n-1]

= 2 * dp[n-1] + dp[n-3]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int numTilings(int n) {
int M = 1e9 + 7;
vector<long> dp(n+2, 0);
dp[0] = 1;
dp[1] = 1;
dp[2] = 2;
if (n < 3)
return dp[n];
for (int i = 3; i <= n; i ++)
dp[i] = (2 * dp[i-1] + dp[i-3]) % M;
return dp[n];
}
};

Leetcode791. Custom Sort String

S and T are strings composed of lowercase letters. In S, no letter occurs more than once.

S was sorted in some custom order previously. We want to permute the characters of T so that they match the order that S was sorted. More specifically, if x occurs before y in S, then x should occur before y in the returned string.

Return any permutation of T (as a string) that satisfies this property.

Example :

1
2
3
4
5
6
7
Input: 
S = "cba"
T = "abcd"
Output: "cbad"
Explanation:
"a", "b", "c" appear in S, so the order of "a", "b", "c" should be "c", "b", and "a".
Since "d" does not appear in S, it can be at any position in T. "dcba", "cdba", "cbda" are also valid outputs.

Note:

  • S has length at most 26, and no character is repeated in S.
  • T has length at most 200.
  • S and T consist of lowercase letters only.

这道题给了我们两个字符串S和T,让我们将T按照S的顺序进行排序,就是说在S中如果字母x在字母y之前,那么排序后的T中字母x也要在y之前,其他S中未出现的字母位置无所谓。那么我们其实关心的是S中的字母,只要按S中的字母顺序遍历,对于遍历到的每个字母,如果T中有的话,就先排出来,这样等S中的字母遍历完了,再将T中剩下的字母加到后面即可。所以我们先用HashMap统计T中每个字母出现的次数,然后遍历S中的字母,只要T中有,就将该字母重复其出现次数个,加入结果res中,然后将该字母出现次数重置为0。之后再遍历一遍HashMap,将T中其他字母加入结果res中即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
string customSortString(string order, string s) {
map<char, int> m;
string res;
for (int i = 0; i < s.length(); i ++)
m[s[i]] ++;
for (int i = 0; i < order.length(); i ++) {
while(m[order[i]] --)
res += order[i];
}
for (auto it = m.begin(); it != m.end(); it ++)
while(it->second > 0) {
res += it->first;
it->second --;
}
return res;
}
};

Leetcode792. Number of Matching Subsequences

Given a string s and an array of strings words, return the number of words[i] that is a subsequence of s.

A subsequence of a string is a new string generated from the original string with some characters (can be none) deleted without changing the relative order of the remaining characters.

For example, “ace” is a subsequence of “abcde”.

Example 1:

1
2
3
Input: s = "abcde", words = ["a","bb","acd","ace"]
Output: 3
Explanation: There are three strings in words that are a subsequence of s: "a", "acd", "ace".

Example 2:

1
2
Input: s = "dsahjpjauf", words = ["ahjpjau","ja","ahbwzgqnuk","tnmlanowax"]
Output: 2

这道题给了我们一个字符串S,又给了一个单词数组,问我们数组有多少个单词是字符串S的子序列。注意这里是子序列,而不是子串,子序列并不需要连续。那么只要我们知道如何验证一个子序列的方法,那么就可以先尝试暴力搜索法,就是对数组中的每个单词都验证一下是否是字符串S的子序列。验证子序列的方法就是用两个指针,对于子序列的每个一个字符,都需要在母字符中找到相同的,在母字符串所有字符串遍历完之后或之前,只要子序列中的每个字符都在母字符串中按顺序找到了,那么就验证成功了。

其实是words数组里有大量相同的单词,而且字符串S巨长无比,那么为了避免相同的单词被不停的重复检验,我们用两个HashSet来记录验证过的单词,为啥要用两个呢?因为验证的结果有两种,要么通过,要么失败,我们要分别存在两个HashSet中,这样再遇到每种情况的单词时,我们就知道要不要结果增1了。如果单词没有验证过的话,那么我们就用双指针的方法进行验证,然后根据结果的不同,存到相应的HashSet中去。

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
class Solution {
public:
int numMatchingSubseq(string s, vector<string>& words) {
int res = 0, len = s.length();
unordered_set<string> pass, out;
for (int i = 0; i < words.size(); i ++) {
if (pass.count(words[i])) {
res ++;
continue;
}
if (out.count(words[i]))
continue;
int j = 0, k = 0;
for (; j < len && k < words[i].length(); j ++)
if (s[j] == words[i][k])
k ++;
if (k == words[i].length()) {
res++;
pass.insert(words[i]);
}
else
out.insert(words[i]);
}
return res;
}
};

Leetcode794. Valid Tic-Tac-Toe State

A Tic-Tac-Toe board is given as a string array board. Return True if and only if it is possible to reach this board position during the course of a valid tic-tac-toe game.

The board is a 3 x 3 array, and consists of characters “ “, “X”, and “O”. The “ “ character represents an empty square.

Here are the rules of Tic-Tac-Toe:

  • Players take turns placing characters into empty squares (“ “).
  • The first player always places “X” characters, while the second player always places “O” characters.
  • “X” and “O” characters are always placed into empty squares, never filled ones.
  • The game ends when there are 3 of the same (non-empty) character filling any row, column, or diagonal.
  • The game also ends if all squares are non-empty.
  • No more moves can be played if the game is over.

Example 1:

1
2
3
Input: board = ["O  ", "   ", "   "]
Output: false
Explanation: The first player always plays "X".

Example 2:

1
2
3
Input: board = ["XOX", " X ", "   "]
Output: false
Explanation: Players take turns making moves.

Example 3:

1
2
Input: board = ["XXX", "   ", "OOO"]
Output: false

Example 4:

1
2
Input: board = ["XOX", "O O", "XOX"]
Output: true

Note:

  • board is a length-3 array of strings, where each string board[i] has length 3.
  • Each board[i][j] is a character in the set {“ “, “X”, “O”}.

这道题又是关于井字棋游戏的,之前也有一道类似的题 Design Tic-Tac-Toe,不过那道题是模拟游戏进行的,而这道题是让验证当前井字棋的游戏状态是否正确。这题的例子给的比较好,cover 了很多种情况:

情况一:

1
2
3
0 _ _
_ _ _
_ _ _

这是不正确的状态,因为先走的使用X,所以只出现一个O,是不对的。

情况二:

1
2
3
X O X
_ X _
_ _ _

这个也是不正确的,因为两个 player 交替下棋,X最多只能比O多一个,这里多了两个,肯定是不对的。

情况三:

1
2
3
X X X
_ _ _
O O O

这个也是不正确的,因为一旦第一个玩家的X连成了三个,那么游戏马上结束了,不会有另外一个O出现。

情况四:

1
2
3
X O X
O _ O
X O X

这个状态没什么问题,是可以出现的状态。

好,那么根据给的这些例子,可以分析一下规律,根据例子1和例子2得出下棋顺序是有规律的,必须是先X后O,不能破坏这个顺序,那么可以使用一个 turns 变量,当是X时,turns 自增1,反之若是O,则 turns 自减1,那么最终 turns 一定是0或者1,其他任何值都是错误的,比如例子1中,turns 就是 -1,例子2中,turns 是2,都是不对的。根据例子3,可以得出结论,只能有一个玩家获胜,可以用两个变量 xwin 和 owin,来记录两个玩家的获胜状态,由于井字棋的制胜规则是横竖斜任意一个方向有三个连续的就算赢,那么分别在各个方向查找3个连续的X,有的话 xwin 赋值为 true,还要查找3个连续的O,有的话 owin 赋值为 true,例子3中 xwin 和 owin 同时为 true 了,是错误的。还有一种情况,例子中没有 cover 到的是:

情况五:

1
2
3
X X X
O O _
O _ _

这里虽然只有 xwin 为 true,但是这种状态还是错误的,因为一旦第三个X放下后,游戏立即结束,不会有第三个O放下,这么检验这种情况呢?这时 turns 变量就非常的重要了,当第三个O放下后,turns 自减1,此时 turns 为0了,而正确的应该是当 xwin 为 true 的时候,第三个O不能放下,那么 turns 不减1,则还是1,这样就可以区分情况五了。当然,可以交换X和O的位置,即当 owin 为 true 时,turns 一定要为0。现在已经覆盖了搜索的情况了,参见代码如下:

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
class Solution {
public:
bool validTicTacToe(vector<string>& board) {
bool xwin = false, owin = false;
vector<int> row(3), col(3);
int diag = 0, antidiag = 0, turns = 0;
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
if (board[i][j] == 'X') {
++row[i]; ++col[j]; ++turns;
if (i == j) ++diag;
if (i + j == 2) ++antidiag;
} else if (board[i][j] == 'O') {
--row[i]; --col[j]; --turns;
if (i == j) --diag;
if (i + j == 2) --antidiag;
}
}
}
xwin = row[0] == 3 || row[1] == 3 || row[2] == 3 ||
col[0] == 3 || col[1] == 3 || col[2] == 3 ||
diag == 3 || antidiag == 3;
owin = row[0] == -3 || row[1] == -3 || row[2] == -3 ||
col[0] == -3 || col[1] == -3 || col[2] == -3 ||
diag == -3 || antidiag == -3;
if ((xwin && turns == 0) || (owin && turns == 1)) return false;
return (turns == 0 || turns == 1) && (!xwin || !owin);
}
};

Leetcode795. Number of Subarrays with Bounded Maximum

We are given an array A of positive integers, and two positive integers L and R (L <= R).

Return the number of (contiguous, non-empty) subarrays such that the value of the maximum array element in that subarray is at least L and at most R.

Example :

1
2
3
4
5
6
Input: 
A = [2, 1, 4, 3]
L = 2
R = 3
Output: 3
Explanation: There are three subarrays that meet the requirements: [2], [2, 1], [3].

Note:

  • L, R and A[i] will be an integer in the range [0, 10^9].
  • The length of A will be in the range of [1, 50000].

这道题给了我们一个数组,又给了我们两个数字L和R,表示一个区间范围,让我们求有多少个这样的子数组,使得其最大值在[L, R]区间的范围内。既然是求子数组的问题,那么最直接,最暴力的方法就是遍历所有的子数组,然后维护一个当前的最大值,只要这个最大值在[L, R]区间的范围内,结果res自增1即可。但是这种最原始,最粗犷的暴力搜索法,OJ不答应。但是其实我们略作优化,就可以通过了。优化的方法是,首先,如果当前数字大于R了,那么其实后面就不用再遍历了,不管当前这个数字是不是最大值,它都已经大于R了,那么最大值可能会更大,所以没有必要再继续遍历下去了。同样的剪枝也要加在内层循环中加,当curMax大于R时,直接break掉内层循环即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int numSubarrayBoundedMax(vector<int>& A, int L, int R) {
int res = 0, n = A.size();
for (int i = 0; i < n; ++i) {
if (A[i] > R) continue;
int curMax = INT_MIN;
for (int j = i; j < n; ++j) {
curMax = max(curMax, A[j]);
if (curMax > R) break;
if (curMax >= L) ++res;
}
}
return res;
}
};

虽然上面的方法做了剪枝后能通过OJ,但是我们能不能在线性的时间复杂度内完成呢。答案是肯定的,我们先来看一种官方解答贴中的方法,这种方法是用一个子函数来算出最大值在[-∞, x]范围内的子数组的个数,而这种区间只需要一个循环就够了,为啥呢?我们来看数组[2, 1, 4, 3]的最大值在[-∞, 4]范围内的子数组的个数。当遍历到2时,只有一个子数组[2],遍历到1时,有三个子数组,[2], [1], [2,1]。当遍历到4时,有六个子数组,[2], [1], [4], [2,1], [1,4], [2,1,4]。当遍历到3时,有十个子数组。其实如果长度为n的数组的最大值在范围[-∞, x]内的话,其所有子数组都是符合题意的,而长度为n的数组共有n(n+1)/2个子数组,刚好是等差数列的求和公式。所以我们在遍历数组的时候,如果当前数组小于等于x,则cur自增1,然后将cur加到结果res中;如果大于x,则cur重置为0。这样我们可以正确求出最大值在[-∞, x]范围内的子数组的个数。而要求最大值在[L, R]范围内的子数组的个数,只需要用最大值在[-∞, R]范围内的子数组的个数,减去最大值在[-∞, L-1]范围内的子数组的个数即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
int numSubarrayBoundedMax(vector<int>& A, int L, int R) {
return count(A, R) - count(A, L - 1);
}
int count(vector<int>& A, int bound) {
int res = 0, cur = 0;
for (int x : A) {
cur = (x <= bound) ? cur + 1 : 0;
res += cur;
}
return res;
}
};

下面这种解法也是线性时间复杂度的,跟上面解法的原理很类似,只不过没有写子函数而已。我们使用left和right来分别标记子数组的左右边界,使得其最大值在范围[L,R]内。那么当遍历到的数字大于等于L时,right赋值为当前位置i,那么每次res加上right - left,随着right的不停自增1,每次加上的right - left,实际上也是一个等差数列,跟上面解法中的子函数本质时一样的。当A[i]大于R的时候,left = i,那么此时A[i]肯定也大于等于L,于是rihgt=i,那么right - left为0,相当于上面的cur重置为0的操作,发现本质联系了吧,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int numSubarrayBoundedMax(vector<int>& A, int L, int R) {
int res = 0, left = -1, right = -1;
for (int i = 0; i < A.size(); ++i) {
if (A[i] > R) left = i;
if (A[i] >= L) right = i;
res += right - left;
}
return res;
}
};

Leetcode796. Rotate String

We are given two strings, A and B.

A shift on A consists of taking string A and moving the leftmost character to the rightmost position. For example, if A = ‘abcde’, then it will be ‘bcdea’ after one shift on A. Return True if and only if A can become B after some number of shifts on A.

Example 1:

1
2
Input: A = 'abcde', B = 'cdeab'
Output: true

Example 2:

1
2
Input: A = 'abcde', B = 'abced'
Output: false

这道题给了我们两个字符串A和B,定义了一种偏移操作,以某一个位置将字符串A分为两截,并将两段调换位置,如果此时跟字符串B相等了,就说明字符串A可以通过偏移得到B。现在就是让我们判断是否存在这种偏移,那么最简单最暴力的方法就是遍历所有能将A分为两截的位置,然后用取子串的方法将A断开,交换顺序,再去跟B比较,如果相等,返回true即可,遍历结束后,返回false,参见代码如下:

1
2
3
4
5
6
7
8
9
10
class Solution {
public:
bool rotateString(string A, string B) {
if (A.size() != B.size()) return false;
for (int i = 0; i < A.size(); ++i) {
if (A.substr(i, A.size() - i) + A.substr(0, i) == B) return true;
}
return false;
}
};

可以在A之后再加上一个A,这样如果新的字符串(A+A)中包含B的话,说明A一定能通过偏移得到B。就比如题目中的例子,A=”abcde”, B=”bcdea”,那么A+A=”abcdeabcde”,里面是包括B的,所以返回true即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
bool rotateString(string A, string B) {
if(A=="" && B == "")
return true;
if(A.length() != B.length())
return false;
string AA = A + A;
return AA.find(B) < A.length();
}
};

Leetcode797. All Paths From Source to Target

Given a directed, acyclic graph of N nodes. Find all possible paths from node 0 to node N-1, and return them in any order.

The graph is given as follows: the nodes are 0, 1, …, graph.length - 1. graph[i] is a list of all nodes j for which the edge (i, j) exists.

Example:

1
2
3
4
5
6
7
8
Input: [[1,2], [3], [3], []] 
Output: [[0,1,3],[0,2,3]]
Explanation: The graph looks like this:
0--->1
| |
v v
2--->3
There are two paths: 0 -> 1 -> 3 and 0 -> 2 -> 3.

Note:
The number of nodes in the graph will be in the range [2, 15].
You can print different paths in any order, but you should keep the order of nodes inside one path.

这道题给了我们一个无回路有向图,包含N个结点,然后让我们找出所有可能的从结点0到结点N-1的路径。

这个图的数据是通过一个类似邻接链表的二维数组给的,最开始的时候博主没看懂输入数据的意思,其实很简单,我们来看例子中的input,[[1,2], [3], [3], []],这是一个二维数组,最外层的数组里面有四个小数组,每个小数组其实就是和当前结点相通的邻结点,由于是有向图,所以只能是当前结点到邻结点,反过来不一定行。那么结点0的邻结点就是结点1和2,结点1的邻结点就是结点3,结点2的邻结点也是3,结点3没有邻结点。

那么其实这道题的本质就是遍历邻接链表,由于要列出所有路径情况,那么递归就是不二之选了。我们用cur来表示当前遍历到的结点,初始化为0,然后在递归函数中,先将其加入路径path,如果cur等于N-1了,那么说明到达结点N-1了,将path加入结果res。否则我们再遍历cur的邻接结点,调用递归函数即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
vector<vector<int>> res;
helper(graph, 0, {}, res);
return res;
}
void helper(vector<vector<int>>& graph, int cur, vector<int> path, vector<vector<int>>& res) {
path.push_back(cur);
if (cur == graph.size() - 1) res.push_back(path);
else for (int neigh : graph[cur]) helper(graph, neigh, path, res);
}
};

Leetcode799. Champagne Tower

We stack glasses in a pyramid, where the first row has 1 glass, the second row has 2 glasses, and so on until the 100th row. Each glass holds one cup (250ml) of champagne.

Then, some champagne is poured in the first glass at the top. When the top most glass is full, any excess liquid poured will fall equally to the glass immediately to the left and right of it. When those glasses become full, any excess champagne will fall equally to the left and right of those glasses, and so on. (A glass at the bottom row has it’s excess champagne fall on the floor.)

For example, after one cup of champagne is poured, the top most glass is full. After two cups of champagne are poured, the two glasses on the second row are half full. After three cups of champagne are poured, those two cups become full - there are 3 full glasses total now. After four cups of champagne are poured, the third row has the middle glass half full, and the two outside glasses are a quarter full, as pictured below.

Now after pouring some non-negative integer cups of champagne, return how full the j-th glass in the i-th row is (both i and j are 0 indexed.)

Example 1:

1
2
3
Input: poured = 1, query_glass = 1, query_row = 1
Output: 0.0
Explanation: We poured 1 cup of champange to the top glass of the tower (which is indexed as (0, 0)). There will be no excess liquid so all the glasses under the top glass will remain empty.

Example 2:

1
2
3
Input: poured = 2, query_glass = 1, query_row = 1
Output: 0.5
Explanation: We poured 2 cups of champange to the top glass of the tower (which is indexed as (0, 0)). There is one cup of excess liquid. The glass indexed as (1, 0) and the glass indexed as (1, 1) will share the excess liquid equally, and each will get half cup of champange.

Note:

  • poured will be in the range of [0, 10 ^ 9].
  • query_glass and query_row will be in the range of [0, 99].

这道题用高脚杯摆了个金字塔,貌似在电影里见过这种酷炫的效果,不过好像还是3D的,组了个立体的酒杯金字塔。这道题中的金字塔是2D的,降低了一些难度。在我们最开始没有什么思路的时候,我们就从最简单的开始分析吧:

当只倒一杯酒的时候,只有最顶端的酒杯被填满。

当倒二杯酒的时候,最顶端的酒杯被填满,且第二层的两个酒杯各自装了一半。

当倒三杯酒的时候,最顶端的酒杯被填满,且第二层的两个酒杯也被填满。

当倒四杯酒的时候,最顶端的酒杯被填满,且第二层的两个酒杯也被填满,第三层的三个酒杯分别被填了四分之一,二分之一,和四分之一。

当倒五杯酒的时候,最顶端的酒杯被填满,且第二层的两个酒杯也被填满,第三层的三个酒杯分别被填了二分之一,填满,和二分之一。

如果酒是无限的,那么最终每个酒杯就会被填满,所以难点就是怎么知道在倒K杯酒后,当前的酒杯还剩多少。不管酒量又多大,当前酒杯最多只能装一杯,多余的酒都会流到下一行的两个酒杯。那么比如我们总共倒了五杯酒,那么最顶端的酒杯只能留住一杯,剩下的四杯全部均分到下行的酒杯中了,而离其最近的下一行的两个酒杯会平均分到其多出来的酒量。那么第二层的酒杯分别会得到(5-1)/2=2杯。而第二层的两个酒杯也分别只能留住一杯,各自多余的一杯还要往第三层流,那么第三层的第一个杯子接住了第二层的第一个杯子流下的半杯,而第三层的第二个杯子接住了第二层的两个杯子各自流下的半杯,于是填满了。第三层的第三个杯子接住了第二层的第二个杯子流下的半杯。那么我们的思路应该就是处理每一个杯子,将多余的酒量均分到其下一层对应的两个酒杯中,我们只需要处理到query_row那一行即可,如果地query_glass中的酒量超过一杯了,那么我们返回1就行了,因为多余的还会往下流,但我们不需要再考虑了。

我们建立一个二维的dp数组,其中dp[i][j]表示第i行第j列的杯子将要接住的酒量(可能大于1,因为此时还没有进行多余往下流的处理),那么我们就逐个遍历即可,将多余的酒量均分加入下一行的两个酒杯中即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
double champagneTower(int poured, int query_row, int query_glass) {
vector<vector<double>> dp(101, vector<double>(101, 0));
dp[0][0] = poured;
for (int i = 0; i <= query_row; ++i) {
for (int j = 0; j <= i; ++j) {
if (dp[i][j] >= 1) {
dp[i + 1][j] += (dp[i][j] - 1) / 2.0;
dp[i + 1][j + 1] += (dp[i][j] - 1) / 2.0;
}
}
}
return min(1.0, dp[query_row][query_glass]);
}
};

我们可以对上面的代码进行空间上的优化,只用一个一维数组即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
double champagneTower(int poured, int query_row, int query_glass) {
vector<double> dp(101, 0);
dp[0] = poured;
for (int i = 1; i <= query_row; ++i) {
for (int j = i; j >= 0; --j) {
dp[j + 1] += dp[j] = max(0.0, (dp[j] - 1) / 2.0);
}
}
return min(1.0, dp[query_glass]);
}
};

Leetcode1103. Distribute Candies to People

We distribute some number of candies, to a row of n = num_people people in the following way:

We then give 1 candy to the first person, 2 candies to the second person, and so on until we give n candies to the last person.

Then, we go back to the start of the row, giving n + 1 candies to the first person, n + 2 candies to the second person, and so on until we give 2 * n candies to the last person.

This process repeats (with us giving one more candy each time, and moving to the start of the row after we reach the end) until we run out of candies. The last person will receive all of our remaining candies (not necessarily one more than the previous gift).

Return an array (of length num_people and sum candies) that represents the final distribution of candies.

Example 1:

1
2
3
4
5
6
7
Input: candies = 7, num_people = 4
Output: [1,2,3,1]
Explanation:
On the first turn, ans[0] += 1, and the array is [1,0,0,0].
On the second turn, ans[1] += 2, and the array is [1,2,0,0].
On the third turn, ans[2] += 3, and the array is [1,2,3,0].
On the fourth turn, ans[3] += 1 (because there is only one candy left), and the final array is [1,2,3,1].

Example 2:

1
2
3
4
5
6
7
Input: candies = 10, num_people = 3
Output: [5,2,3]
Explanation:
On the first turn, ans[0] += 1, and the array is [1,0,0].
On the second turn, ans[1] += 2, and the array is [1,2,0].
On the third turn, ans[2] += 3, and the array is [1,2,3].
On the fourth turn, ans[0] += 4, and the final array is [5,2,3].

只考虑每次分配的糖果数,分配的糖果数为1,2,3,4,5,…, 依次加1。再考虑到分配的轮数,可以利用 i % num_people 来求得第i次应该分配到第几个人。

最后要注意的是,如果当前糖果数小于本应该分配的糖果数,则将当前糖果全部给予,也就是要判断剩余糖果数 candies 与本该分配糖果数 i+1 的大小,谁小分配谁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
vector<int> distributeCandies(int candies, int num_people) {
vector<int> res(num_people, 0);
int temp = 1, i = 0;
while(candies > 0) {
res[i%num_people] += min(candies, i+1);
candies -= min(candies, i+1);
i ++;
}
return res;
}
};

Leetcode1104. Path In Zigzag Labelled Binary Tree

In an infinite binary tree where every node has two children, the nodes are labelled in row order.

In the odd numbered rows (ie., the first, third, fifth,…), the labelling is left to right, while in the even numbered rows (second, fourth, sixth,…), the labelling is right to left.

Given the label of a node in this tree, return the labels in the path from the root of the tree to the node with that label.

Example 1:

Input: label = 14
Output: [1,3,4,14]
Example 2:

Input: label = 26
Output: [1,2,6,10,26]

Constraints:

1 <= label <= 10^6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
vector<int> res;
vector<int> pathInZigZagTree(int label) {
build(label);
return res;
}

void build(int label){
int level;
int lastMin;
res.insert(res.begin(), label);
if(label != 1){
level = (int)(log(label)/log(2));
lastMin = pow(2, level)/2;
build( lastMin + (lastMin*2)-1 - label/2 );
}
}
};

因为不管是奇数行还是偶数行,该行与上一行的方向都是反着来的

可以先求出顺着来时这个结点对应的父结点,再求出对应父结点在它所在行对称的结点

这里有个求对称的方法:按顺序排列且每个数都能找到对称数的一系列数,每一对对陈数的和都相同,所以求某个数的在某一行的对称数,只用找出这一行两端的数,求出和,再减去这个数就能得到这个数的对称数

所以只用从传进来的这个结点递归,每次递归求出自己对应的父结点,递归到1时结束,每次递归记录一次当前结点的号码

最后得到的一系列结点号码就是路径

Leetcode1105. Filling Bookcase Shelves

You are given an array books where books[i] = [thicknessi, heighti] indicates the thickness and height of the ith book. You are also given an integer shelfWidth.

We want to place these books in order onto bookcase shelves that have a total width shelfWidth.

We choose some of the books to place on this shelf such that the sum of their thickness is less than or equal to shelfWidth, then build another level of the shelf of the bookcase so that the total height of the bookcase has increased by the maximum height of the books we just put down. We repeat this process until there are no more books to place.

Note that at each step of the above process, the order of the books we place is the same order as the given sequence of books.

For example, if we have an ordered list of 5 books, we might place the first and second book onto the first shelf, the third book on the second shelf, and the fourth and fifth book on the last shelf.
Return the minimum possible height that the total bookshelf can be after placing shelves in this manner.

Example 1:

1
2
3
4
5
Input: books = [[1,1],[2,3],[2,3],[1,1],[1,1],[1,1],[1,2]], shelf_width = 4
Output: 6
Explanation:
The sum of the heights of the 3 shelves is 1 + 3 + 2 = 6.
Notice that book number 2 does not have to be on the first shelf.

Example 2:

1
2
Input: books = [[1,3],[2,4],[3,2]], shelfWidth = 6
Output: 4

这道题说是让用书来填书架,每本书有其固定的宽和高,需要按给定的顺序来排列书,要么排在新的一行,要么排在之前的层,注意每层的宽度不能超过给定的 shelf_width 的限制,每层的高度按照最高的那本书来计算,问怎么安排才能使得整个书架的高度最小。这种数组玩极值的题目,大概率就是贪婪算法或者动态规划 Dynamic Programming,但是这里贪婪算法就不太合适,因为书的高度是不确定的,就算尽量每行尽可能的多放书,并不能保证整体的高度是最小的。所以只能祭出动态规划了,先来定义 DP 数组,这里使用一个一维的 dp 数组,其中dp[i]表示前i本书可以组成的最小高度,大小初始化为n+1。接下来找动态转移方程,对于每一本新的书,最差的结果就是放到新的一行中,这样整个高度就增加了当前书的高度,所以dp[i]可以先赋值为dp[i-1] + height,然后再进行优化。方法是不停加上之前的书,条件是总宽度不能超过给定值,高度选其中最高的一个,每次用dp[j] + height来更新dp[i],最终返回dp[n]即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int minHeightShelves(vector<vector<int>>& books, int sw) {
int len = books.size();
vector<int> dp(len+1, INT_MAX);
for (int i = 0; i < len; i ++) {
int h = 0, w = 0;
for (int j = i; j >= 0; j --) {
if ((w += books[j][0]) > sw)
break;
h = max(h, books[j][1]);
dp[i] = min(dp[i], (j == 0 ? 0 : dp[j-1]) + h);
}
}
return dp[len-1];
}
};

Leetcode1108. Defanging an IP Address

Given a valid (IPv4) IP address, return a defanged version of that IP address.

A defanged IP address replaces every period “.” with “[.]”.

Example 1:

1
2
Input: address = "1.1.1.1"
Output: "1[.]1[.]1[.]1"

Example 2:

1
2
Input: address = "255.100.50.0"
Output: "255[.]100[.]50[.]0"

Constraints:

  • The given address is a valid IPv4 address.

把IP地址中的“.”换成“[.]”,没有难度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
string defangIPaddr(string address) {
string answer(address.length()+6,'\0');
for(int i=0, j=0;i<address.length();i++){
if(address[i]=='.'){
answer[j++]='[';
answer[j++]='.';
answer[j++]=']';
}
else
answer[j++]=address[i];
}
return answer;
}
};

Leetcode1109. Corporate Flight Bookings

There are n flights that are labeled from 1 to n.

You are given an array of flight bookings bookings, where bookings[i] = [firsti, lasti, seatsi] represents a booking for flights firsti through lasti (inclusive) with seatsi seats reserved for each flight in the range.

Return *an array answer of length n, where answer[i] is the total number of seats reserved for flight *i.

Example 1:

1
2
3
4
5
6
7
8
9
Input: bookings = [[1,2,10],[2,3,20],[2,5,25]], n = 5
Output: [10,55,45,25,25]
Explanation:
Flight labels: 1 2 3 4 5
Booking 1 reserved: 10 10
Booking 2 reserved: 20 20
Booking 3 reserved: 25 25 25 25
Total seats: 10 55 45 25 25
Hence, answer = [10,55,45,25,25]

Example 2:

1
2
3
4
5
6
7
8
Input: bookings = [[1,2,10],[2,2,15]], n = 2
Output: [10,25]
Explanation:
Flight labels: 1 2
Booking 1 reserved: 10 10
Booking 2 reserved: 15
Total seats: 10 25
Hence, answer = [10,25]

Constraints:

  • 1 <= n <= 2 * 104
  • 1 <= bookings.length <= 2 * 104
  • bookings[i].length == 3
  • 1 <= firsti <= lasti <= n
  • 1 <= seatsi <= 104

这道题说是有n个航班,标号从1到n,每次公司可以连续预定多个航班上的座位,用一个三元数组 [i, j, k],表示分别预定航班i到j上的k个座位,最后问每个航班上总共被预定了多少个座位。博主先试了一下暴力破解,毫无意外的超时了,想想为啥会超时,因为对于每个预定的区间,都遍历一次的话,最终可能达到n的平方级的复杂度。所以就需要想一些节省运算时间的办法,其实这道的解法很巧妙,先来想想,假如只有一个预定,是所有航班上均订k个座位,那么暴力破解的方法就是从1遍历到n,然后每个都加上k,但还有一种方法,就是只在第一天加上k,然后计算累加和数组,这样之后的每一天都会被加上k。如果是预定前一半的航班,那么暴力破解的方法就是从1遍历到 n/2,而这里的做法是在第一个天加上k,在第 n/2 + 1 天减去k,这样再求累加和数组时,后一半的航班就不会加上k了。对于所有的预定都可以采用这种做法,在起始位置加上k,在结束位置加1处减去k,最后再整体算累加和数组,这样就把平方级的时间复杂度缩小到了线性,完美通过 OJ,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
vector<int> res(n);
for (auto booking : bookings) {
res[booking[0] - 1] += booking[2];
if (booking[1] < n) res[booking[1]] -= booking[2];
}
for (int i = 1; i < n; ++i) {
res[i] += res[i - 1];
}
return res;
}
};

Leetcode1110. Delete Nodes And Return Forest

Given the root of a binary tree, each node in the tree has a distinct value.

After deleting all nodes with a value in to_delete, we are left with a forest (a disjoint union of trees).

Return the roots of the trees in the remaining forest. You may return the result in any order.

Example 1:

1
2
Input: root = [1,2,3,4,5,6,7], to_delete = [3,5]
Output: [[1,2,null,4],[6],[7]]

Example 2:

1
2
Input: root = [1,2,4,null,3], to_delete = [3]
Output: [[1,2,4]]

Constraints:

  • The number of nodes in the given tree is at most 1000.
  • Each node has a distinct value between 1 and 1000.
  • to_delete.length <= 1000
  • to_delete contains distinct values between 1 and 1000.

这道题给了一棵二叉树,说了每个结点值均不相同,现在让删除一些结点,由于删除某些位置的结点会使原来的二叉树断开,从而会形成多棵二叉树,形成一片森林,让返回森林中所有二叉树的根结点。对于二叉树的题,十有八九都是用递归来做的,这道题也不例外,先来想一下这道题的难点在哪里,去掉哪些点会形成新树,显而易见的是,去掉根结点的话,左右子树若存在的话一定会形成新树,同理,去掉子树的根结点,也可能会形成新树,只有去掉叶结点时才不会生成新树,所以当前结点是不是根结点就很重要了,这个需要当作一个参数传入。由于需要知道当前结点是否需要被删掉,每次都遍历 to_delete 数组显然不高效,那就将其放入一个 HashSet 中,从而到达常数级的搜索时间。这样递归函数就需要四个参数,当前结点,是否是根结点的布尔型变量,HashSet,还有结果数组 res。在递归函数中,首先判空,然后判断当前结点值是否在 HashSet,用一个布尔型变量 deleted 来记录。若当前是根结点,且不需要被删除,则将这个结点加入结果 res 中。然后将左子结点赋值为对左子结点调用递归函数的返回值,右子结点同样赋值为对右子结点调用递归的返回值,最后判断当前结点是否被删除了,是的话返回空指针,否则就返回当前指针,这样的话每棵树的根结点都在递归的过程中被存入结果 res 中了,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
vector<TreeNode*> delNodes(TreeNode* root, vector<int>& to_delete) {
vector<TreeNode*> res;
unordered_set<int> st(to_delete.begin(), to_delete.end());
helper(root, true, st, res);
return res;
}
TreeNode* helper(TreeNode* node, bool is_root, unordered_set<int>& st, vector<TreeNode*>& res) {
if (!node) return nullptr;
bool deleted = st.count(node->val);
if (is_root && !deleted) res.push_back(node);
node->left = helper(node->left, deleted, st, res);
node->right = helper(node->right, deleted, st, res);
return deleted ? nullptr : node;
}
};

Leetcode1111. Maximum Nesting Depth of Two Valid Parentheses Strings

A string is a valid parentheses string (denoted VPS) if and only if it consists of “(“ and “)” characters only, and:

It is the empty string, or

  • It can be written as AB (A concatenated with B), where A and B are VPS’s, or
  • It can be written as (A), where A is a VPS.

We can similarly define the nesting depth depth(S) of any VPS S as follows:

  • depth(“”) = 0
  • depth(A + B) = max(depth(A), depth(B)), where A and B are VPS’s
  • depth(“(“ + A + “)”) = 1 + depth(A), where A is a VPS.

For example, “”, “()()”, and “()(()())” are VPS’s (with nesting depths 0, 1, and 2), and “)(“ and “(()” are not VPS’s.

Given a VPS seq, split it into two disjoint subsequences A and B, such that A and B are VPS’s (and A.length + B.length = seq.length).

Now choose any such A and B such that max(depth(A), depth(B)) is the minimum possible value.

Return an answer array (of length seq.length) that encodes such a choice of A and B: answer[i] = 0 if seq[i] is part of A, else answer[i] = 1. Note that even though multiple answers may exist, you may return any of them.

Example 1:

1
2
Input: seq = "(()())"
Output: [0,1,1,1,1,0]

Example 2:

1
2
Input: seq = "()(())()"
Output: [0,0,0,1,1,0,1,1]

Constraints:

  • 1 <= seq.size <= 10000

题目很简单,就是将一个集合拆分为两个depth最接近的两个集合。所以我们需要先计算出总的depth(S)是多少,然后将其除2就得到了其中一个集合的depth(A),然后就可以计算出另外一个集合的depth(B)=depth(S)-depth(A)。

接着考虑如何将两个集合挑选出来,也是非常容易的,我们只需要再次遍历seq,记录遍历的’(‘的数目,如果’(‘的数目超过了As(A集合的depth)的话,我们就将对应的字符标记为B集合的即可(也就是标记为1)。

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
class Solution {
public:
vector<int> maxDepthAfterSplit(string seq) {
int ds=0,cur=0;
for(int i=0;i<seq.length();i++){
if(seq[i]=='('){
cur+=1;
ds=max(ds,cur);
}
else
cur-=1;
}
int as=ds/2;
vector<int> res(seq.length(),0);
for(int i=0;i<seq.length();i++){
if(seq[i]=='('){
cur+=1;
if(cur>as)
res[i]=1;
}
else{
if(cur>as)
res[i]=1;
cur-=1;
}
}
return res;
}
};

Leetcode1114. Print in Order

Suppose we have a class:

1
2
3
4
5
public class Foo {
public void first() { print("first"); }
public void second() { print("second"); }
public void third() { print("third"); }
}

The same instance of Foo will be passed to three different threads. Thread A will call first(), thread B will call second(), and thread C will call third(). Design a mechanism and modify the program to ensure that second() is executed after first(), and third() is executed after second().

Example 1:

1
2
3
Input: [1,2,3]
Output: "firstsecondthird"
Explanation: There are three threads being fired asynchronously. The input [1,2,3] means thread A calls first(), thread B calls second(), and thread C calls third(). "firstsecondthird" is the correct output.

Example 2:

1
2
3
Input: [1,3,2]
Output: "firstsecondthird"
Explanation: The input [1,3,2] means thread A calls first(), thread B calls third(), and thread C calls second(). "firstsecondthird" is the correct output.

现在三个线程,每个线程分别调用三个函数中的一个。无论线程的产生和调用关系怎么样,最终输出的结果要求都是”firstsecondthird”。如何设计是三个函数。这个是Leetcode的新题型,也就是说并发类型,我觉得很实用,工作中能用到。一般情况下,最简单的协调不同线程之间的调度关系,都可以使用mutex来做,本质是信号量。

std::mutex的成员函数有四个:

  • 构造函数,std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。
  • lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:
    • (1). 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。
    • (2). 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。
    • (3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。
  • unlock(), 解锁,释放对互斥量的所有权。
  • try_lock(),尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。线程调用该函数也会出现下面 3 种情况,
    • (1). 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量。
    • (2). 如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉。
    • (3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。

也就是说一个锁能控制两个线程的执行顺序。这个题中我们需要保持三个函数是按顺序执行的,则需要两个锁m1和m2。在开始的时候,两个锁都锁起来。first()可以直接执行,second()等待m1释放之后执行,third()等待m2释放之后执行。first()结束之后释放m1,second()结束之后释放m2.因此三个的顺序都协调一致了。C++代码如下:

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
class Foo {
private:
mutex m1, m2;
public:
Foo() {
m1.lock();
m2.lock();
}

void first(function<void()> printFirst) {
// printFirst() outputs "first". Do not change or remove this line.
printFirst();
m1.unlock();
}

void second(function<void()> printSecond) {
m1.lock();
// printSecond() outputs "second". Do not change or remove this line.
printSecond();
m1.unlock();
m2.unlock();
}


void third(function<void()> printThird) {
m2.lock();
// printThird() outputs "third". Do not change or remove this line.
printThird();
m2.unlock();
}
};

Leetcode1122. Relative Sort Array

Given two arrays arr1 and arr2, the elements of arr2 are distinct, and all elements in arr2 are also in arr1.

Sort the elements of arr1 such that the relative ordering of items in arr1 are the same as in arr2. Elements that don’t appear in arr2 should be placed at the end of arr1 in ascending order.

Example 1:

1
2
Input: arr1 = [2,3,1,3,2,4,6,7,9,2,19], arr2 = [2,1,4,3,9,6]
Output: [2,2,2,1,4,3,3,9,6,7,19]

Constraints:

  • arr1.length, arr2.length <= 1000
  • 0 <= arr1[i], arr2[i] <= 1000
  • Each arr2[i] is distinct.
  • Each arr2[i] is in arr1.

arr2 中的元素各不相同,arr2 中的每个元素都出现在 arr1 中,对 arr1 中的元素进行排序,使 arr1 中项的相对顺序和 arr2 中的相对顺序相同。未在 arr2 中出现过的元素需要按照升序放在 arr1 的末尾。

基本思路是:

  1. 首先题目的意思是按照arr2的元素顺序返回arr1的元素,假定返回的新数组为arr3,然后把剩余的arr1元素按照升序顺序拼接到arr3后边返回
  2. 遍历一遍arr1使用map [Int:Int] 记录每一个元素的次数
  3. 遍历arr2,把在arr2出现的元素当做key取出value值,arr3 add value次key值
  4. 把剩余的字典键值对所对应的key值排序,添加到arr3后边
  5. 时间复杂度 O(nlogn)
  6. 空间复杂度 O(n)

注意map是有序的,内部是用平衡树存储,而unordered_map是用hash做的,也不能保证插入的顺序,因此这里使用了大佬的做法

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
class Solution {
public:
vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
int count_arr[1001];
vector<int> ans;
memset(count_arr, 0, sizeof(count_arr));
for(int i=0;i<arr1.size();i++)
count_arr[arr1[i]]++;

for(int i=0;i<arr2.size();i++){
int len = count_arr[arr2[i]];
for(int j=0;j<len;j++)
ans.push_back(arr2[i]);
count_arr[arr2[i]]=-1;
}
vector<int> s;
for(int i=0; i<arr1.size(); i++){
if(count_arr[arr1[i]] > 0) s.push_back(arr1[i]);
}
sort(s.begin(), s.end());
for(int i=0;i<s.size();i++)
ans.push_back(s[i]);
return ans;
}
};

Leetcode1123. Lowest Common Ancestor of Deepest Leaves

Given a rooted binary tree, return the lowest common ancestor of its deepest leaves.

Recall that:

  • The node of a binary tree is a leaf if and only if it has no children
  • The depth of the root of the tree is 0, and if the depth of a node is d, the depth of each of its children is d+1.
  • The lowest common ancestor of a set S of nodes is the node A with the largest depth such that every node in S is in the subtree with root A.

Example 1:

1
2
3
4
5
6
Input: root = [1,2,3]
Output: [1,2,3]
Explanation:
The deepest leaves are the nodes with values 2 and 3.
The lowest common ancestor of these leaves is the node with value 1.
The answer returned is a TreeNode object (not an array) with serialization "[1,2,3]".

Example 2:

1
2
Input: root = [1,2,3,4]
Output: [4]

Example 3:

1
2
Input: root = [1,2,3,4,5]
Output: [2,4,5]

Constraints:

  • The given tree will have between 1 and 1000 nodes.
  • Each node of the tree will have a distinct value between 1 and 1000.

写一个递归函数,返回(LCA, 最大深度),然后对左右子树分别调用这个函数。如果两棵子树的高度不同,则显然最大深度的叶子只存在更深的子树中,那么另一棵子树就不用管了,LCA也不变;否则LCA是当前树根。

就是,他不是要求最大深度公共子树么,就求左右子树的深度,如果相等了,说明找到了,因为是从上往下的,这就是最大的深度;否则的话对左右子树分别搞一搞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:

int solve(TreeNode* root){
if(root == NULL)
return 0;
return 1 + max(solve(root->left), solve(root->right));

}

TreeNode* lcaDeepestLeaves(TreeNode* root) {
if(root==NULL)
return 0;
int l = solve(root->left);
int r = solve(root->right);
if(l == r)
return root;
if(l < r)
return lcaDeepestLeaves(root->right);
else
return lcaDeepestLeaves(root->left);
}
};

Leetcode1124. Longest Well-Performing Interval

We are given hours, a list of the number of hours worked per day for a given employee.

A day is considered to be a tiring day if and only if the number of hours worked is (strictly) greater than 8.

A well-performing interval is an interval of days for which the number of tiring days is strictly larger than the number of non-tiring days.

Return the length of the longest well-performing interval.

Example 1:

1
2
3
Input: hours = [9,9,6,0,6,6,9]
Output: 3
Explanation: The longest well-performing interval is [9,9,6].

Constraints:

  • 1 <= hours.length <= 10000
  • 0 <= hours[i] <= 16

把所有大于8的转成1,小于8的转成-1,找到最长的字串,字串的和大于等于1,最优解的字串的和肯定是1,因为如果大于1的话肯定可以往后走。

存储可能的target_sum的序列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
int longestWPI(vector<int>& hours) {
for(int i=0;i<hours.size();i++)
hours[i]=hours[i]>8?1:-1;
unordered_map<int, int> idx;
int r = 0,inx=0;
int last=0;
int maxx=0;
for(int i=0;i<hours.size();i++){
r += hours[i];
if(r>0){
maxx = i+1;
}
if (!idx.count(r))
idx[r] = i;
if (idx.count(r - 1))
maxx = max(maxx, i - idx[r - 1]);
}
return maxx;
}
};

LeetCode] 1125. Smallest Sufficient Team

In a project, you have a list of required skills req_skills, and a list of people. The ith person people[i] contains a list of skills that the person has.

Consider a sufficient team: a set of people such that for every required skill in req_skills, there is at least one person in the team who has that skill. We can represent these teams by the index of each person.

For example, team = [0, 1, 3] represents the people with skills people[0], people[1], and people[3].
Return any sufficient team of the smallest possible size, represented by the index of each person. You may return the answer in any order.

It is guaranteed an answer exists.

Example 1:

1
2
Input: req_skills = ["java","nodejs","reactjs"], people = [["java"],["nodejs"],["nodejs","reactjs"]]
Output: [0,2]

Example 2:

1
2
Input: req_skills = ["algorithms","math","java","reactjs","csharp","aws"], people = [["algorithms","math","java"],["algorithms","math","reactjs"],["java","csharp","aws"],["reactjs","csharp"],["csharp","math"],["aws","java"]]
Output: [1,2]

Constraints:

  • 1 <= req_skills.length <= 16
  • 1 <= req_skills[i].length <= 16
  • req_skills[i] consists of lowercase English letters.
  • All the strings of req_skills are unique.
  • 1 <= people.length <= 60
  • 0 <= people[i].length <= 16
  • 1 <= people[i][j].length <= 16
  • people[i][j] consists of lowercase English letters.
  • All the strings of people[i] are unique.
  • Every skill in people[i] is a skill in req_skills.
  • It is guaranteed a sufficient team exists.

这道题给了一个技能数组,是完成某一个项目所需要的必备技能。又给了一个候选人的数组,每个人都有不同的技能,现在问最少需要多少人可以完成这个项目。由于每个人的技能点不同,为了能完成这个项目,所选的人的技能点的并集要正好包含所有的项目必备技能,而且还要求人数尽可能的少,这就是一道典型的动态规划 Dynamic Programming 的题。这道题敢标 Hard 是有其一定的道理的,首先 DP 数组的定义就是一个难点,因为我们也不知道最少需要多少个人可以拥有所有的必备技能。另一个难点是如何表示这些技能,总不能每次都跟 req_skills 数组一一对比吧,太不高效了。一个比较好的方法是使用二进制来表示,有多少个技能就对应多少位,某人拥有某技能,则对应位上为1,否则为0。若总共有n个必备技能,实际上只用一个 2^n-1 的数字就可以表示了。这里我们的 dp 数组定义为 HashMap,建立技能集合的位表示数和拥有这些技能的人(最少的人数)的集合之间的映射,那么最终的结果就是 dp[(1<<n)-1] 对应的数组的长度了。首先将 dp[0] 映射为空数组,因为0表示没有任何技能,自然也不需要任何人,这个初始化是一定要做的,之后会讲原因。这里再用另一个 HashMap,将每个技能映射到其在技能数组中的坐标,这样方便之后快速的翻转技能集合二进制的对应位。先用一个 for 循环来建立这个 skillMap 的映射,然后就是遍历每个候选人了,使用一个整型变量 skill,然后根据 skillMap 查找这个人所有的技能,并将其对应位翻为1,这样此时的 skill 就 encode 了该人的所有的技能。现在就该尝试更新 dp 了,遍历此时 dp 的所有映射,此时之前加入的那个初始化的映射就发挥作用了,就像很多其他 DP 的题都要给 dp[0] 初始化一样,没有这个引子,后面的更新都不会发生,整个 for 都进不去。将当前的 key 值或上 skill,则表示将当前这个人加到了某个映射的人的集合中了,这样就可能会生出现一个新的技能集合的位表示数(也可能不出现,即当前这个人的所有技能已经被之前集合中的所有人包括了),此时看若 dp 中不存在这个技能集合的位表示数,或者新的技能集合的位表示数对应的人的集合长度大于原来的人的集合长度加1,说明 dp 需要被更新了,将新的位表示数映射到加入这个人后的新的人的集合,这样更新下来,就能保证最终 dp[(1<<n)-1] 的值最小,因为题目中说了一定会有解,参见代码如下:

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
class Solution {
public:
vector<int> smallestSufficientTeam(vector<string>& req_skills, vector<vector<string>>& people) {
int n = req_skills.size();
unordered_map<int, vector<int>> dp(1 << n);
dp[0] = {};
unordered_map<string, int> skillMap;
for (int i = 0; i < n; ++i) {
skillMap[req_skills[i]] = i;
}
for (int i = 0; i < people.size(); ++i) {
int skill = 0;
for (string str : people[i]) {
skill |= 1 << skillMap[str];
}
for (auto a : dp) {
int cur = a.first | skill;
if (!dp.count(cur) || dp[cur].size() > 1 + dp[a.first].size()) {
dp[cur] = a.second;
dp[cur].push_back(i);
}
}
}
return dp[(1 << n) - 1];
}
};
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
class Solution {
public:
vector<int> smallestSufficientTeam(vector<string>& req_skills, vector<vector<string>>& people) {
int m = req_skills.size(), n = people.size();
int maxstate = 1 << m;
// f[i][s] = 考虑前i个人的时候,状态为s所需的最小人数
vector<vector<int>> f(n+1, vector<int>(maxstate, 1e9));
// path[i][s] = 前i个人的时候,选人方案,
vector<vector<long long>> path(n+1, vector<long long>(maxstate, 0));

f[0][0] = 0;

for (int i = 1; i <= n; i ++) {
int state = 0;
for (const auto& str : people[i-1]) {
int id = find(req_skills.begin(), req_skills.end(), str) - req_skills.begin();
state |= (1 << id);
}

for (int j = 0; j < maxstate; j ++) {
if (f[i-1][j] < f[i][j]) {
f[i][j] = f[i-1][j];
path[i][j] = path[i-1][j];
}

int news = j | state;
if (f[i-1][j] + 1 < f[i][news]) {
f[i][news] = f[i-1][j] + 1;
path[i][news] = path[i-1][j] | (1LL << i);
}
}
}

vector<int> ret;
for (int i = 1; i <= n; i ++)
if ((path[n][maxstate-1] >> i) & 1)
ret.push_back(i-1);
return ret;
}
};

Leetcode1128. Number of Equivalent Domino Pairs

Given a list of dominoes, dominoes[i] = [a, b] is equivalent to dominoes[j] = [c, d] if and only if either (a==c and b==d), or (a==d and b==c) - that is, one domino can be rotated to be equal to another domino.

Return the number of pairs (i, j) for which 0 <= i < j < dominoes.length, and dominoes[i] is equivalent to dominoes[j].

Example 1:

1
2
Input: dominoes = [[1,2],[2,1],[3,4],[5,6]]
Output: 1

给你一个由一些多米诺骨牌组成的列表 dominoes。如果其中某一张多米诺骨牌可以通过旋转 0 度或 180 度得到另一张多米诺骨牌,我们就认为这两张牌是等价的。形式上,dominoes[i] = [a, b] 和 dominoes[j] = [c, d],等价的前提是 a==c 且 b==d,或是 a==d 且 b==c。在 0 <= i < j < dominoes.length 的前提下,找出满足 dominoes[i] 和 dominoes[j] 等价的骨牌对 (i, j) 的数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
int numEquivDominoPairs(vector<vector<int>>& dominoes) {
int temp;
int ans = 0;
map<int, int> mp;

for(int i = 0; i < dominoes.size(); i ++) {
if(dominoes[i][0] > dominoes[i][1]) {
temp = dominoes[i][0];
dominoes[i][0] = dominoes[i][1];
dominoes[i][1] = temp;
}
temp = dominoes[i][0]*10 + dominoes[i][1];
mp[temp] ++;
}
for(pair<int, int> i : mp) {
int v = i.second;
ans += (v*(v-1))/2;
}
return ans;
}
};

Leetcode1129. Shortest Path with Alternating Colors

Consider a directed graph, with nodes labelled 0, 1, …, n-1. In this graph, each edge is either red or blue, and there could be self-edges or parallel edges.

Each [i, j] in red_edges denotes a red directed edge from node i to node j. Similarly, each [i, j] in blue_edges denotes a blue directed edge from node i to node j.

Return an array answer of length n, where each answer[X] is the length of the shortest path from node 0 to node X such that the edge colors alternate along the path (or -1 if such a path doesn’t exist).

Example 1:

1
2
Input: n = 3, red_edges = [[0,1],[1,2]], blue_edges = []
Output: [0,1,-1]

Example 2:

1
2
Input: n = 3, red_edges = [[0,1]], blue_edges = [[2,1]]
Output: [0,1,-1]

Example 3:

1
2
Input: n = 3, red_edges = [[1,0]], blue_edges = [[2,1]]
Output: [0,-1,-1]

Example 4:

1
2
Input: n = 3, red_edges = [[0,1]], blue_edges = [[1,2]]
Output: [0,1,2]

Example 5:

1
2
Input: n = 3, red_edges = [[0,1],[0,2]], blue_edges = [[1,0]]
Output: [0,1,1]

Constraints:

  • 1 <= n <= 100
  • red_edges.length <= 400
  • blue_edges.length <= 400
  • red_edges[i].length == blue_edges[i].length == 2
  • 0 <= red_edges[i][j], blue_edges[i][j] < n

这道题给了一个有向图,跟以往不同的是,这里的边分为两种不同颜色,红和蓝,现在让求从结点0到所有其他结点的最短距离,并且要求路径必须是红蓝交替,即不能有相同颜色的两条边相连。这种遍历图求最短路径的题目的首选解法应该是广度优先遍历 Breadth-first Search,就像迷宫遍历的问题一样,由于其遍历的机制,当其第一次到达某个结点时,当前的步数一定是最少的。不过这道题还有一个难点,就是如何保证路径是红蓝交替的,这就跟以往有些不同了,必须要建立两个图的结构,分别保存红边和蓝边,为了方便起见,使用一个二维数组,最外层用0表示红边,1表示蓝边。内层是一个大小为n的数组,因为有n个结点,数组中的元素是一个 HashSet,因为每个结点可能可以连到多个其他的结点,这个图的结构可以说是相当的复杂了。

接下来就是给图结构赋值了,分别遍历红边和蓝边的数组,将对应的结点连上,就是将相连的结点加到 HashSet 中。由于到达每个结点可能通过红边或者蓝边,所以就有两个状态,这里用一个二维的 dp 数组来记录这些状态,其中 dp[i][j] 表示最后由颜色i的边到达结点j的最小距离,除了结点0之外,均初始化为 2n,因为即便是有向图,到达某个结点的最小距离也不可能大于 2n。由于是 BFS 遍历,需要用到 queue,这里的 queue 中的元素需要包含两个信息,当前的结点值,到达该点的边的颜色,所以初始化时分别将 (0,0) 和 (0,1) 放进去,前一个0表示结点值,后一个表示到达该点的边的颜色。接下来就可以进行 BFS 遍历了,进行 while 循环,将队首元素取出,将结点值 cur 和颜色值 color 取出。由于到达当前结点的边的颜色是 color,接下来就只能选另一种颜色了,则可以用 1-color 来选另一种颜色,并且在该颜色下遍历和 cur 相连的所有结点,若其对应的 dp 值仍为 2n,说明是第一次到达该结点,可用当前 dp 值加1来更新其 dp 值,并且将新的结点值与其颜色加入到队列中以便下次遍历其相连结点。当循环结束之后,只需要遍历一次 dp 值,将每个结点值对应的两个 dp 值中的较小的那个放到结果 res 中即可,注意要进行一下判断,若 dp 值仍为 2n,说明无法到达该结点,需要换成 -1,参见代码如下:

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> shortestAlternatingPaths(int n, vector<vector<int>>& red_edges, vector<vector<int>>& blue_edges) {
vector<int> res(n);
vector<vector<int>> dp(2, vector<int>(n));
vector<vector<unordered_set<int>>> graph(2, vector<unordered_set<int>>(n));
for (auto &edge : red_edges) {
graph[0][edge[0]].insert(edge[1]);
}
for (auto &edge : blue_edges) {
graph[1][edge[0]].insert(edge[1]);
}
for (int i = 1; i < n; ++i) {
dp[0][i] = 2 * n;
dp[1][i] = 2 * n;
}
queue<vector<int>> q;
q.push({0, 0});
q.push({0, 1});
while (!q.empty()) {
int cur = q.front()[0], color = q.front()[1]; q.pop();
for (int next : graph[1 - color][cur]) {
if (dp[1 - color][next] == 2 * n) {
dp[1 - color][next] = 1 + dp[color][cur];
q.push({next, 1 - color});
}
}
}
for (int i = 0; i < n; ++i) {
int val = min(dp[0][i], dp[1][i]);
res[i] = val == 2 * n ? -1 : val;
}
return res;
}
};

Leetcode1130. Minimum Cost Tree From Leaf Values

Given an array arr of positive integers, consider all binary trees such that:

  • Each node has either 0 or 2 children;
  • The values of arr correspond to the values of each leaf in an in-order traversal of the tree. (Recall that a node is a leaf if and only if it has 0 children.)
  • The value of each non-leaf node is equal to the product of the largest leaf value in its left and right subtree respectively.

Among all possible binary trees considered, return the smallest possible sum of the values of each non-leaf node. It is guaranteed this sum fits into a 32-bit integer.

Example 1:

1
2
3
4
5
6
7
8
9
10
Input: arr = [6,2,4]
Output: 32
Explanation:
There are two possible trees. The first has non-leaf node sum 36, and the second has non-leaf node sum 32.

24 24
/ \ /\
12 4 6 8
/ \ /\
6 2 2 4

Constraints:

  • 2 <= arr.length <= 40
  • 1 <= arr[i] <= 15
  • It is guaranteed that the answer fits into a 32-bit signed integer (ie. it is less than 2^31).

这道题给了一个数组,说是里面都是一棵树的叶结点,说是其组成的树是一棵满二叉树,且这些叶结点值是通过中序遍历得到的,树中的非叶结点值是是其左右子树中最大的两个叶结点值的乘积,满足这些条件的二叉树可能不止一个,现在让找出非叶结点值之和最小的那棵树,并返回这个最小值。

通过观察例子,可以发现叶结点值 6,2,4 的顺序是不能变的,但是其组合方式可能很多,若有很多个叶结点,那么其组合方式就非常的多了。题目中给的提示是用动态规划 Dynamic Programming 来做,用一个二维的 dp 数组,其中 dp[i][j] 表示在区间 [i, j] 内的子数组组成的二叉树得到非叶结点值之和的最小值,接下来想状态转移方程怎么写。首先,若只有一个叶结点的话,是没法形成非叶结点的,所以 dp[i][i] 是0,最少得有两个叶结点,才有非0的值,即dp[i][i+1] = arr[i] * arr[i+1],而一旦区间再大一些,就要遍历其中所有的小区间的情况,用其中的最小值来更新大区间的 dp 值。

这种用区间dp做,第一层循环是区间长度,第二层枚举起点,第三层枚举终点。这里的区间长度从1到n,长度为1,表示至少有两个叶结点,i从0遍历到 n-len,j可以直接确定出来为 i+len,然后用k来将区间 [i, j] 分为两个部分,由于分开的小区间在之前都已经更新过了,所以其 dp 值可以直接得到,然后再加上这两个区间中各自的最大结点值的乘积。为了不每次都遍历小区间来获得最大值,可以提前计算好任意区间的最大值,保存在 maxVec 中,这样就可以快速获取了,最后返回的结果保存在 dp[0][n-1] 中,参见代码如下:

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
class Solution {
public:
int mctFromLeafValues(vector<int>& arr) {
int n = arr.size();
vector<vector<int>> dp(n, vector<int>(n));
vector<vector<int>> maxVec(n, vector<int>(n));
for (int i = 0; i < n; ++i) {
int curMax = 0;
for (int j = i; j < n; ++j) {
curMax = max(curMax, arr[j]);
maxVec[i][j] = curMax;
}
}
for (int len = 1; len < n; ++len) {
for (int i = 0; i + len < n; ++i) {
int j = i + len;
dp[i][j] = INT_MAX;
for (int k = i; k < j; ++k) {
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + maxVec[i][k] * maxVec[k + 1][j]);
}
}
}
return dp[0][n - 1];
}
};

下面的这种解法是参见了大神 lee215 的帖子,是一种利用单调栈来解的方法,将时间复杂度优化到了线性,惊为天人。思路是这样的,当两个叶结点生成一个父结点值,较小的那个数字使用过一次之后就不再被使用了,因为之后形成的结点是要子树中最大的那个结点值。所以问题实际上可以转化为在一个数组中,每次选择两个相邻的数字a和b,移除较小的那个数字,代价是 a*b,问当移除到数组只剩下一个数字的最小的代价。Exactly same problem,所以b是有可能复用的,要尽可能的 minimize,数字a可以是一个局部最小值,那么b就是a两边的那个较小的数字,这里使用一个单调栈来做是比较方便的。关于单调栈,博主之前也有写过一篇总结 LeetCode Monotonous Stack Summary 单调栈小结,在 LeetCode 中的应用也非常多,是一种必须要掌握的方法。这里维护一个最小栈,当前栈顶的元素是最小的,一旦遍历到一个较大的数字,此时当前栈顶的元素其实是一个局部最小值,它就需要跟旁边的一个较小的值组成一个左右叶结点,这样形成的父结点才是最小的,然后将较小的那个数字移除,符合上面的分析。然后继续比较新的栈顶元素,若还是小,则继续相同的操作,否则退出循环,将当前的数字压入栈中。最后若栈中还有数字剩余,则一定是从大到小的,只需将其按顺序两两相乘即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int mctFromLeafValues(vector<int>& arr) {
int res = 0, n = arr.size();
vector<int> st{INT_MAX};
for (int num : arr) {
while (!st.empty() && st.back() <= num) {
int mid = st.back();
st.pop_back();
res += mid * min(st.back(), num);
}
st.push_back(num);
}
for (int i = 2; i < st.size(); ++i) {
res += st[i] * st[i - 1];
}
return res;
}
};

Leetcode1137. N-th Tribonacci Number

The Tribonacci sequence Tn is defined as follows: T0 = 0, T1 = 1, T2 = 1, and Tn+3 = Tn + Tn+1 + Tn+2 for n >= 0. Given n, return the value of Tn.

Example 1:

1
2
3
4
5
Input: n = 4
Output: 4
Explanation:
T_3 = 0 + 1 + 1 = 2
T_4 = 1 + 1 + 2 = 4

Example 2:

1
2
Input: n = 25
Output: 1389537

类似斐波那契数列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int tribonacci(int n) {
vector<int> v(n+1, 0);
v[0] = 0;
v[1] = 1;
v[2] = 1;
if(n < 3)
return v[n];
for(int i = 3; i <= n; i ++) {
v[i] = v[i-1] + v[i-2] + v[i-3];
}
return v[n];
}
};

Leetcode1138. Alphabet Board Path

On an alphabet board, we start at position (0, 0), corresponding to character board[0][0].

Here, board = [“abcde”, “fghij”, “klmno”, “pqrst”, “uvwxy”, “z”], as shown in the diagram below.

We may make the following moves:

  • ‘U’ moves our position up one row, if the position exists on the board;
  • ‘D’ moves our position down one row, if the position exists on the board;
  • ‘L’ moves our position left one column, if the position exists on the board;
  • ‘R’ moves our position right one column, if the position exists on the board;
  • ‘!’ adds the character board[r][c] at our current position (r, c) to the answer.

(Here, the only positions that exist on the board are positions with letters on them.)

Return a sequence of moves that makes our answer equal to target in the minimum number of moves. You may return any path that does so.

Example 1:

1
2
Input: target = "leet"
Output: "DDR!UURRR!!DDD!"

Example 2:

1
2
Input: target = "code"
Output: "RR!DDRR!UUL!R!"

Constraints:

  • 1 <= target.length <= 100
  • target consists only of English lowercase letters.

这道题给了一个字母表盘,就是 26 个小写字母按每行五个排列,形成一个二维数组,共有六行,但第六行只有一个字母z。然后给了一个字符串 target,起始位置是在a,现在让分别按顺序走到 target 上的所有字符,问经过的最短路径是什么。

由于表盘上的字母位置是固定的,所以不需要进行遍历来找特定的字母,而是可以根据字母直接确定其在表盘的上的坐标,这样当前字母和目标字母的坐标都确定了,就可以直接找路径了,其实就是个曼哈顿距离。由于路径有很多条,只要保证距离最短都对,那么就可以先走横坐标,或先走纵坐标。其实这里选方向挺重要,因为有个很 tricky 的情况,就是字母z,因为最后一行只有一个字母z,其不能往右走,只能往上走,所以这里定一个规则,就是先往上走,再向右走。同理,从别的字母到z的话,也应该先往左走到头,再往下走。顺序确定好了,就可以想怎么正确的生成路径,往上的走的话,说明目标点在上方,则说明当前的x坐标大,则用 curX - x,由于不一定需要向上走,所以这个差值有可能是负数,则需要跟0比较大小,取较大的那个。其他情况,都是同理的,往右走用目标y坐标减去当前y坐标;往左走,用当前y坐标减去目标y坐标;往下走,用目标x坐标减去当前x坐标,最后再加上感叹号。结束一轮后,别忘了更新 curX 和 curY,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
string alphabetBoardPath(string target) {
string res;
int curX = 0, curY = 0;
for (char c : target) {
int x = (c - 'a') / 5, y = (c - 'a') % 5;
res += string(max(0, curX - x), 'U') + string(max(0, y - curY), 'R') + string(max(0, curY - y), 'L') + string(max(0, x - curX), 'D') + '!';
curX = x;
curY = y;
}
return res;
}
};

Leetcode1139. Largest 1-Bordered Square

Given a 2D grid of 0s and 1s, return the number of elements in the largest square subgrid that has all 1s on its border, or 0 if such a subgrid doesn’t exist in the grid.

Example 1:

1
2
Input: grid = [[1,1,1],[1,0,1],[1,1,1]]
Output: 9

Example 2:

1
2
Input: grid = [[1,1,0,0]]
Output: 1

Constraints:

  • 1 <= grid.length <= 100
  • 1 <= grid[0].length <= 100
  • grid[i][j] is 0 or 1

这道题给了一个只有0和1的二维数组 grid,现在让找出边长均为1的最大正方形的元素个数,实际上就是这个正方形的面积,也就是边长的平方。给定的 grid 不一定是个正方形,首先来想,如何确定一个正方形,由于边长的是相同的,只要知道了边长,和其中的一个顶点,那么这个正方形也就确定了。如何才能快速的知道其边长是否均为1呢,每次都一个一个的遍历检查的确太不高效了,比较好的方法是统计连续1的个数,注意这里不是累加和数组,而且到当前位置为止的连续1的个数,需要分为两个方向,水平和竖直。这里用left表示水平,top表示竖直。若left[i][j]为k,则表示从grid[i][j-k]grid[i][j]的数字均为1,同理,若top[i][j]为k,则表示grid[i-k][j]grid[i][j]的数字均为1,则表示找到了一个边长为k的正方形。由于grid不一定是正方形,那么其可以包含的最大的正方形的边长为grid的长和宽中的较小值。边长确定了,只要遍历左上顶点的就行了,然后通过连续1数组topleft来快速判断四条边是否为1,只要找到了这个正方形,就可以直接返回了,否则就将边长减少1,继续查找,参见代码如下:

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
int largest1BorderedSquare(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
vector<vector<int>> left(m, vector<int>(n)), top(m, vector<int>(n));
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] == 0) continue;
left[i][j] = j == 0 ? 1 : left[i][j - 1] + 1;
top[i][j] = i == 0 ? 1 : top[i - 1][j] + 1;
}
}
for (int len = min(m, n); len > 0; --len) {
for (int i = 0; i < m - len + 1; ++i) {
for (int j = 0; j < n - len + 1; ++j) {
if (top[i + len - 1][j] >= len && top[i + len - 1][j + len - 1] >= len && left[i][j + len - 1] >= len && left[i + len - 1][j + len - 1] >= len) return len * len;
}
}
}
return 0;
}
};

上面的方法是根据边长进行遍历,若数组很大,而其中的1很少,这种遍历方法将不是很高效。我们从 grid 数组的右下角往左上角遍历,即从每个潜在的正方形的右下角开始遍历,根据右下顶点的位置取到的 top 和 left 值,分别是正方形的右边和下边的边长,取其中较小的那个为目标正方形的边长,然后现在就要确定是否存在相应的左边和上边,存在话的更新 mx,否则将目标边长减1,继续查找,直到目标边长小于 mx 了停止。继续这样的操作直至遍历完所有的右下顶点,这种遍历的方法要高效不少,参见代码如下:

解法二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public:
int largest1BorderedSquare(vector<vector<int>>& grid) {
int mx = 0, m = grid.size(), n = grid[0].size();
vector<vector<int>> left(m, vector<int>(n)), top(m, vector<int>(n));
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] == 0) continue;
left[i][j] = j == 0 ? 1 : left[i][j - 1] + 1;
top[i][j] = i == 0 ? 1 : top[i - 1][j] + 1;
}
}
for (int i = m - 1; i >= 0; --i) {
for (int j = n - 1; j >= 0; --j) {
int small = min(left[i][j], top[i][j]);
while (small > mx) {
if (top[i][j - small + 1] >= small && left[i - small + 1][j] >= small) mx = small;
--small;
}
}
}
return mx * mx;
}
};

Leetcode1140. Stone Game II

Alice and Bob continue their games with piles of stones. There are a number of piles arranged in a row, and each pile has a positive integer number of stones piles[i]. The objective of the game is to end with the most stones.

Alice and Bob take turns, with Alice starting first. Initially, M = 1.

On each player’s turn, that player can take all the stones in the first X remaining piles, where 1 <= X <= 2M. Then, we set M = max(M, X).

The game continues until all the stones have been taken.

Assuming Alice and Bob play optimally, return the maximum number of stones Alice can get.

Example 1:

1
2
3
Input: piles = [2,7,9,4,4]
Output: 10
Explanation: If Alice takes one pile at the beginning, Bob takes two piles, then Alice takes 2 piles again. Alice can get 2 + 4 + 4 = 10 piles in total. If Alice takes two piles at the beginning, then Bob can take all three piles left. In this case, Alice get 2 + 7 = 9 piles in total. So we return 10 since it's larger.

Example 2:

1
2
Input: piles = [1,2,3,4,5,100]
Output: 104

Constraints:

  • 1 <= piles.length <= 100
  • 1 <= piles[i] <= 104

这道题是石头游戏系列的第二道,跟之前那道 Stone Game 不同的是终于换回了 Alice 和 Bob!还有就是取石子的方法,不再是只能取首尾两端的石子堆,而是可以取 [1, 2M] 范围内的任意X堆,M是个变化的量,初始化为1,每次取完X堆后,更新为 M = max(M, X)。这种取石子的方法比之前的要复杂很多,由于X的值非常的多,而且其不同的选择还可能影响到M值,那么整体的情况就特别的多,暴力搜索基本上是行不通的。这种不同状态之间转移的问题用动态规划 Dynamic Programming 是比较合适的,首先来考虑 DP 数组的定义,题目要求的是 Alice 最多能拿到的石子个数,拿石子的方式是按顺序的,不能跳着拿,所以决定某个状态的是两个变量,一个是当前还剩多少石子堆,可以通过当前位置坐标i来表示,另一个是当前的m值,只有知道了当前的m值,那么选手才知道能拿的堆数的范围,所以 DP 就是个二维数组,其 dp[i][m] 表示的意义在上面已经解释了。接下来考虑状态转移方程,由于在某个状态时已经知道了m值,则当前选手能拿的堆数在范围 [1, 2m] 之间,为了更新这个 dp 值,所有x的情况都要遍历一遍,即在剩余堆数中拿x堆,但此时x堆必须小于等于剩余的堆数,即 i + x <= n,i为当前的位置。由于每个选手都是默认选最优解的,若能知道下一个选手该拿的最大石子个数,就能知道当前选手能拿的最大石子个数了,因为二者之和为当前剩余的石子个数。由于当前选手拿了x堆,则下个选手的位置是 i+x,且m更新为 max(m,x),所以其 dp 值为 dp[i + x][max(m, x)])。为了快速得知当前剩余的石子总数,需要建立累加和数组,注意这里是建立反向的累加和数组,其中 sums[i] 表示范围 [i, n-1] 之和。分析到这里就可以写出状态状态转移方程如下:

1
dp[i][m] = max(dp[i][m], sums[i] - dp[i + x][max(m, x)])

接下来就是一些初始化和边界定义的问题需要注意的了,dp 数组大小为 n+1 by n+1,因为选手是可能一次将n堆都拿了,比如 n=1 时,所以 dp[i][n] 是存在的,且需要用 sums[i] 来初始化。更新 dp 时需要用三个 for 循环,分别控制i,m,和 x,注意更新从后往前遍历i和m,因为我们要先更新小区间,再更新大区间。x的范围要设定为 x <= 2 * m && i + x <= n,前面也讲过原因了,最后的答案保存在 dp[0][1] 中返回即可,参见代码如下:

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
int stoneGameII(vector<int>& piles) {
int n = piles.size();
vector<int> sums = piles;
vector<vector<int>> dp(n + 1, vector<int>(n + 1));
for (int i = n - 2; i >= 0; --i) {
sums[i] += sums[i + 1];
}
for (int i = 0; i < n; ++i) {
dp[i][n] = sums[i];
}
for (int i = n - 1; i >= 0; --i) {
for (int m = n - 1; m >= 1; --m) {
for (int x = 1; x <= 2 * m && i + x <= n; ++x) {
dp[i][m] = max(dp[i][m], sums[i] - dp[i + x][max(m, x)]);
}
}
}
return dp[0][1];
}
};

我们再来用递归加记忆数组的方式来实现一下,其实核心思想跟上面完全一样,这里就不过多的讲解了,直接看代码吧:

解法二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
int stoneGameII(vector<int>& piles) {
int n = piles.size();
vector<int> sums = piles;
vector<vector<int>> memo(n, vector<int>(n));
for (int i = n - 2; i >= 0; --i) {
sums[i] += sums[i + 1];
}
return helper(sums, 0, 1, memo);
}
int helper(vector<int>& sums, int i, int m, vector<vector<int>>& memo) {
if (i + 2 * m >= sums.size()) return sums[i];
if (memo[i][m] > 0) return memo[i][m];
int res = 0;
for (int x = 1; x <= 2 * m; ++x) {
int cur = sums[i] - sums[i + x];
res = max(res, cur + sums[i + x] - helper(sums, i + x, max(x, m), memo));
}
return memo[i][m] = res;
}
};

Leetcode1143. Longest Common Subsequence

Given two strings text1 and text2, return the length of their longest common subsequence.

A subsequence of a string is a new string generated from the original string with some characters(can be none) deleted without changing the relative order of the remaining characters. (eg, “ace” is a subsequence of “abcde” while “aec” is not). A common subsequence of two strings is a subsequence that is common to both strings.

If there is no common subsequence, return 0.

Example 1:

1
2
3
Input: text1 = "abcde", text2 = "ace"
Output: 3
Explanation: The longest common subsequence is "ace" and its length is 3.

Example 2:

1
2
3
Input: text1 = "abc", text2 = "abc"
Output: 3
Explanation: The longest common subsequence is "abc" and its length is 3.

Example 3:

1
2
3
Input: text1 = "abc", text2 = "def"
Output: 0
Explanation: There is no such common subsequence, so the result is 0.

Constraints:

  • 1 <= text1.length <= 1000
  • 1 <= text2.length <= 1000
  • The input strings consist of lowercase English characters only.

这道题让求最长相同的子序列。使用一个二维数组 dp,其中dp[i][j]表示text1的前i个字符和text2的前j个字符的最长相同的子序列的字符个数,这里大小初始化为(m+1)x(n+1),这里的m和n分别是text1text2的长度。接下来就要找状态转移方程了,如何来更新dp[i][j],若二者对应位置的字符相同,表示当前的LCS又增加了一位,所以可以用dp[i-1][j-1] + 1来更新dp[i][j]。否则若对应位置的字符不相同,由于是子序列,还可以错位比较,可以分别从text1或者text2去掉一个当前字符,那么其dp值就是dp[i-1][j]dp[i][j-1],取二者中的较大值来更新dp[i][j]即可,最终的结果保存在了dp[m][n]中,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
int longestCommonSubsequence(string word1, string word2) {
int m = word1.length(), n = word2.length();
vector<vector<int>> dp(m+1, vector(n+1, 0));
for (int i = 1; i <= m; i ++)
for (int j = 1; j <= n; j ++)
if (word1[i-1] == word2[j-1])
dp[i][j] = dp[i-1][j-1] + 1;
else
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
return dp[m][n];
}
};

Leetcode1144. Decrease Elements To Make Array Zigzag

Given an array nums of integers, a move consists of choosing any element and decreasing it by 1.

An array A is a zigzag array if either:

Every even-indexed element is greater than adjacent elements, ie. A[0] > A[1] < A[2] > A[3] < A[4] > …
OR, every odd-indexed element is greater than adjacent elements, ie. A[0] < A[1] > A[2] < A[3] > A[4] < …
Return the minimum number of moves to transform the given array nums into a zigzag array.

Example 1:

1
2
3
Input: nums = [1,2,3]
Output: 2
Explanation: We can decrease 2 to 0 or 3 to 1.

Example 2:

1
2
Input: nums = [9,6,1,6,2]
Output: 4

Constraints:

  • 1 <= nums.length <= 1000
  • 1 <= nums[i] <= 1000

这道题说是每次可以给数组中的任意数字减小1,现在想将数组变为之字形,就是数字大和小交替出现,有两种,一种是偶数坐标的数字均大于其相邻两个位置的数字,一种是奇数坐标的数字均大于其相邻的两个位置的数字。对于第一种情况来说,其奇数坐标位置的数字就均小于其相邻两个位置的数字,同理,对于第二种情况,其偶数坐标位置的数字就均小于其相邻两个位置的数字。这里我们可以分两种情况来统计减少次数,一种是减小所有奇数坐标上的数字,另一种是减小所有偶数坐标上的数字。减小的方法是找到相邻的两个数字中的较小那个,然后比其小1即可,即可用 nums[i] - min(left, right) + 1 来得到,若得到了个负数,说明当前数字已经比左右的数字小了,不需要再减小了,所以需要跟0比较,取较大值。这里用了一个大小为2的 res 数组,这用直接根据当前坐标i,通过 i%2 就可以更新对应的次数了,最终取二者中的较小值返回即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int movesToMakeZigzag(vector<int>& nums) {
int n = nums.size(), res[2] = {0, 0};
for (int i = 0; i < n; ++i) {
int left = i > 0 ? nums[i - 1] : 1001;
int right = i < n - 1 ? nums[i + 1] : 1001;
res[i % 2] += max(0, nums[i] - min(left, right) + 1);
}
return min(res[0], res[1]);
}
};

Leetcode1146. Snapshot Array

Implement a SnapshotArray that supports the following interface:

SnapshotArray(int length) initializes an array-like data structure with the given length. Initially, each element equals 0.

  • void set(index, val) sets the element at the given index to be equal to val.
  • int snap() takes a snapshot of the array and returns the snap_id: the total number of times we called snap() minus 1.
  • int get(index, snap_id) returns the value at the given index, at the time we took the snapshot with the given snap_id

Example 1:

1
2
3
4
5
6
7
8
9
Input: ["SnapshotArray","set","snap","set","get"]
[[3],[0,5],[],[0,6],[0,0]]
Output: [null,null,0,null,5]
Explanation:
SnapshotArray snapshotArr = new SnapshotArray(3); // set the length to be 3
snapshotArr.set(0,5); // Set array[0] = 5
snapshotArr.snap(); // Take a snapshot, return snap_id = 0
snapshotArr.set(0,6);
snapshotArr.get(0,0); // Get the value of array[0] with snap_id = 0, return 5

Constraints:

  • 1 <= length <= 50000
  • At most 50000 calls will be made to set, snap, and get.
  • 0 <= index < length
  • 0 <= snap_id < (the total number of times we call snap())
  • 0 <= val <= 10^9

这道题让实现一个 SnapshotArray 的类,具有给数组拍照的功能,就是说在某个时间点spapId拍照后,当前数组的值需要都记录下来,同理,每一次调用snap()函数时,都需要记录整个数组的状态,这是为了之后可以查询任意一个时间点上的任意一个位置上的值。最简单粗暴的方法当前就是用一个二维数组,每次拍照的时候,都把整个数组都存到二维数组中,其坐标就是snapId。但是这种方法不高效,而且占用了巨大的空间,被 OJ 豪不留情的抹杀掉了。来分析一下不高效的原因,这是因为每次拍照时,可能数组的大部分数据并没有变动,每次都再存一遍整个数组是浪费的。这里我们关心的是调用set()函数,因为这会改变数组的值,若能建立snapId和更新值之间的映射,就可以根据二分法来快速定位某一个snapId的值了,因为snapId是按顺序递增的。这样就可以用一个Vector of Map 或者 Map of Map 的数据结构来实现,外层的 TreeMap 是映射建立数组坐标到内层 TreeMap 之间的映射,内层的 TreeMap 是建立 snapId 和更新值之间的映射。初始化时,要将 0->0 这个映射对儿加到每一个位置,因为初始化时数组的每个元素都是0。在set()函数中就可以更新HashMap中的映射值,snap()就直接累加snapId,比较麻烦的就是get()函数,给定的snapId可能在内层的HashMap中不存在,需要查找第一个不大于给定snapId的映射值,那么就先找第一个大于snapId的位置,再回退一位就好了,参见代码如下:

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
class SnapshotArray {
public:
SnapshotArray(int length) {
for (int i = 0; i < length; ++i) {
snapMap[i] = {{0, 0}};
}
}

void set(int index, int val) {
snapMap[index][snapId] = val;
}

int snap() {
return snapId++;
}

int get(int index, int snap_id) {
auto it = snapMap[index].upper_bound(snap_id);
--it;
return it->second;
}

private:
int snapId = 0;
map<int, map<int, int>> snapMap;
};

Leetcode1147. Longest Chunked Palindrome Decomposition

You are given a string text. You should split it to k substrings (subtext1, subtext2, …, subtextk) such that:

  • subtexti is a non-empty string.
  • The concatenation of all the substrings is equal to text (i.e., subtext1 + subtext2 + … + subtextk == text).
  • subtexti == subtextk - i + 1 for all valid values of i (i.e., 1 <= i <= k).

Return the largest possible value of k.

Example 1:

1
2
3
Input: text = "ghiabcdefhelloadamhelloabcdefghi"
Output: 7
Explanation: We can split the string on "(ghi)(abcdef)(hello)(adam)(hello)(abcdef)(ghi)".

Example 2:

1
2
3
Input: text = "merchant"
Output: 1
Explanation: We can split the string on "(merchant)".

Example 3:

1
2
3
Input: text = "antaprezatepzapreanta"
Output: 11
Explanation: We can split the string on "(a)(nt)(a)(pre)(za)(tpe)(za)(pre)(a)(nt)(a)".

Example 4:

1
2
3
Input: text = "aaa"
Output: 3
Explanation: We can split the string on "(a)(a)(a)".

Constraints:

  • 1 <= text.length <= 1000
  • text consists only of lowercase English characters.

这道题是关于段式回文的,想必大家对回文串都不陌生,就是前后字符对应相同的字符串,比如 noon 和 bob。这里的段式回文相等的不一定是单一的字符,而是可以是字串,参见题目中的例子,现在给了一个字符串,问可以得到的段式回文串的最大长度是多少。由于段式回文的特点,你可以把整个字符串都当作一个子串,则可以得到一个长度为1的段式回文,所以答案至少是1,不会为0。而最好情况就是按字符分别相等,那就变成了一般的回文串,则长度就是原字符串的长度。比较的方法还是按照经典的验证回文串的方式,用双指针来做,一前一后。不同的是遇到不相等的字符不是立马退出,而是累加两个子串 left 和 right,每累加一个字符,都比较一下 left 和 right 是否相等,这样可以保证尽可能多的分出来相等的子串,一旦分出了相等的子串,则 left 和 right 重置为空串,再次从小到大比较,参见代码如下:

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int longestDecomposition(string text) {
int res = 0, n = text.size();
string left, right;
for (int i = 0; i < n; ++i) {
left += text[i], right = text[n - i - 1] + right;
if (left == right) {
++res;
left = right = "";
}
}
return res;
}
};

我们也可以使用递归来做,写法更加简洁一些,i从1遍历到 n/2,代表的是子串的长度,一旦超过一半了,说明无法分为两个了,最终做个判断即可。为了不每次都提取出子串直接进行比较,这里可以先做个快速的检测,即判断两个子串的首尾字符是否对应相等,只有相等了才会提取整个子串进行比较,这样可以省掉一些不必要的计算,参见代码如下:

解法二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
bool is_equal(string text, int x, int y, int len) {
for (int i = 0; i < len; i ++)
if (text[x+i] != text[y+i])
return false;
return true;
}

int longestDecomposition(string text) {
int n = text.length();
for (int i = 1; i <= n/2; i ++) {
if (is_equal(text, 0, n-i, i))
return 2 + longestDecomposition(text.substr(i, n-i*2));
}
return n > 0 ? 1 : 0;
}
};

Leetcode1154. Day of the Year

Given a string date representing a Gregorian calendar date formatted as YYYY-MM-DD, return the day number of the year.

Example 1:

1
2
3
Input: date = "2019-01-09"
Output: 9
Explanation: Given date is the 9th day of the year in 2019.

Example 2:

1
2
Input: date = "2019-02-10"
Output: 41

Example 3:

1
2
Input: date = "2003-03-01"
Output: 60

Example 4:

1
2
Input: date = "2004-03-01"
Output: 61

判断一个日期是一年中的第几天。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int dayOfYear(string date) {
int days[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
int year = stoi(date.substr(0, 4));
int month = stoi(date.substr(5,7));
int day = stoi(date.substr(8, 10));
int ans = 0;
for(int i = 0; i < month; i ++)
ans += days[i];
ans += day;
if((year%100==0 && year%400 == 0) || (year%100 != 0 && year%4 == 0))
if(month > 2)
ans += 1;
return ans;
}
};

Leetcode1156. Swap For Longest Repeated Character Substring

You are given a string text. You can swap two of the characters in the text.

Return the length of the longest substring with repeated characters.

Example 1:

1
2
3
Input: text = "ababa"
Output: 3
Explanation: We can swap the first 'b' with the last 'a', or the last 'b' with the first 'a'. Then, the longest repeated character substring is "aaa" with length 3.

Example 2:

1
2
3
Input: text = "aaabaaa"
Output: 6
Explanation: Swap 'b' with the last 'a' (or the first 'a'), and we get longest repeated character substring "aaaaaa" with length 6.

Example 3:

1
2
3
Input: text = "aaaaa"
Output: 5
Explanation: No need to swap, longest repeated character substring is "aaaaa" with length is 5.

核心是如下几种情况

  • 仅有一段连续的字符
  • 两段及以上的连续的字符,它们之间仅有有一个不符合,可以连接在一起
  • 两段及以上的连续的字符,但是中间超过1个,按照要求,没办法连接

思路

  • 遍历一次字符串,构建不同的连续字符串的起点到终点的数组
  • 按照每个字符去遍历,考虑上面3种情况,同时需要额外考虑
  • 对于情况2,如果有多一个同样的字符,交换后就是a+b+1,额外一个字符交换过来
  • 对于情况3,额外交换一个,即a+1
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
class Solution {
public:
int maxRepOpt1(string text) {
vector<vector<pair<int, int> > > mapp(26, vector<pair<int, int>>());;
int i = 0, n = text.length();
while(i < n) {
int j = i+1;
while(j < n && text[i] == text[j])
j ++;
mapp[text[i]-'a'].push_back(make_pair(i, j-1));
i = j;
}
int res = 0;
for (i = 0; i < 26; i ++) {
if (mapp[i].size() == 0)
continue;
int len = mapp[i].size();
bool has_equal = len > 1;
for (int j = 0; j < len; j ++) {
res = max(res, mapp[i][j].second - mapp[i][j].first + 1 + has_equal);
}

if (len >= 2) {
has_equal = len > 2;
for (int j = 0; j < len-1; j ++)
if (mapp[i][j+1].first - mapp[i][j].second == 2)
res = max(res, mapp[i][j].second-mapp[i][j].first+1 + mapp[i][j+1].second-mapp[i][j+1].first+1 + has_equal);
}
}
return res;
}
};

Leetcode1155. Number of Dice Rolls With Target Sum

You have d dice and each die has f faces numbered 1, 2, …, f.

Return the number of possible ways (out of fd total ways) modulo 109 + 7 to roll the dice so the sum of the face-up numbers equals target.

Example 1:

1
2
3
4
Input: d = 1, f = 6, target = 3
Output: 1
Explanation:
You throw one die with 6 faces. There is only one way to get a sum of 3.

Example 2:

1
2
3
4
5
Input: d = 2, f = 6, target = 7
Output: 6
Explanation:
You throw two dice, each with 6 faces. There are 6 ways to get a sum of 7:
1+6, 2+5, 3+4, 4+3, 5+2, 6+1.

Example 3:

1
2
3
4
Input: d = 2, f = 5, target = 10
Output: 1
Explanation:
You throw two dice, each with 5 faces. There is only one way to get a sum of 10: 5+5.

Example 4:

1
2
3
4
Input: d = 1, f = 2, target = 3
Output: 0
Explanation:
You throw one die with 2 faces. There is no way to get a sum of 3.

Example 5:

1
2
3
4
Input: d = 30, f = 30, target = 500
Output: 222616187
Explanation:
The answer must be returned modulo 10^9 + 7.

Constraints:

  • 1 <= d, f <= 30
  • 1 <= target <= 1000

这道题题说是给了d个骰子,每个骰子有f个面,现在给了一个目标值 target,问同时投出这d个骰子,共有多少种组成目标值的不同组合,结果对超大数字 1e9+7 取余。首先来考虑 dp 数组该如何定义,根据硬币找零系列的启发,目标值本身肯定是占一个维度的,因为这个是要求的东西,另外就是当前骰子的个数也是要考虑的因素,所以这里使用一个二维的 dp 数组,其中dp[i][j]表示使用i个骰子组成目标值为j的所有组合个数,大小为d+1 by target+1,并初始化dp[0][0]为1。接下来就是找状态转移方程了,当前某个状态dp[i][k]跟什么相关呢,其表示为使用i个骰子组成目标值k,那么拿最后一个骰子的情况分析,其可能会投出[1,f]中的任意一个数字j,那么之前的目标值就是k-j,且用了i-1个骰子,其dp值就是dp[i-1][k-j],当前投出的点数可以跟之前所有的情况组成一种新的组合,所以当前的dp[i][k]就要加上dp[i-1][k-j],那么状态转移方程就呼之欲出了:

1
dp[i][k] = (dp[i][k] + dp[i - 1][k - j]) % M;

其中i的范围是 [1, d],j的范围是 [1, f],k的范围是 [j, target],总共三个 for 循环嵌套在一起,最终返回 dp[d][target] 即可,参见代码如下:

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int numRollsToTarget(int d, int f, int target) {
int M = 1e9 + 7;
vector<vector<int>> dp(d + 1, vector<int>(target + 1));
dp[0][0] = 1;
for (int i = 1; i <= d; ++i) {
for (int j = 1; j <= f; ++j) {
for (int k = j; k <= target; ++k) {
dp[i][k] = (dp[i][k] + dp[i - 1][k - j]) % M;
}
}
}
return dp[d][target];
}
};

我们可以进行空间上的优化,由于当前使用i个骰子的状态值依赖于使用 i-1 个骰子的状态,所以没必要保存所有的骰子个数的 dp 值,可以在遍历i的时候,新建一个临时的数组t,来保存使用i个骰子的 dp 值,并在最后交换 dp 和 t 即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int numRollsToTarget(int d, int f, int target) {
int M = 1e9 + 7;
vector<int> dp(target + 1);
dp[0] = 1;
for (int i = 1; i <= d; ++i) {
vector<int> t(target + 1);
for (int j = 1; j <= f; ++j) {
for (int k = j; k <= target; ++k) {
t[k] = (t[k] + dp[k - j]) % M;
}
}
swap(dp, t);
}
return dp[target];
}
};

Leetcode1160. Find Words That Can Be Formed by Characters

You are given an array of strings words and a string chars.

A string is good if it can be formed by characters from chars (each character can only be used once).

Return the sum of lengths of all good strings in words.

Example 1:

1
2
3
4
Input: words = ["cat","bt","hat","tree"], chars = "atach"
Output: 6
Explanation:
The strings that can be formed are "cat" and "hat" so the answer is 3 + 3 = 6.

Example 2:

1
2
3
4
Input: words = ["hello","world","leetcode"], chars = "welldonehoneyr"
Output: 10
Explanation:
The strings that can be formed are "hello" and "world" so the answer is 5 + 5 = 10.

Note:

  • 1 <= words.length <= 1000
  • 1 <= words[i].length, chars.length <= 100
  • All strings contain lowercase English letters only.

一道简单题卡了这么久,我真的是太弱鸡了,不过它卡时间很扯淡。。。chars中每个字符出现的次数要大于等于word中每个字符出现的次数,即表示可以组成这个word。

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
class Solution {
public:
int countCharacters(vector<string>& words, string chars) {
int char_len[26];
for(int i=0;i<26;i++)
char_len[i]=0;
for(int i=0;i<chars.length();i++)
char_len[chars[i]-'a']++;
int res=0;
int word_len[26];
for(int i=0;i<words.size();i++){
bool flag=true;
for(int j=0;j<26;j++)
word_len[j]=0;
for(int j=0;j<words[i].length();j++)
if(++word_len[words[i][j]-'a'] > char_len[words[i][j]-'a']){
flag=false;
break;
}
if(flag)
res += words[i].length();
}
return res;
}
};

Leetcode1161. Maximum Level Sum of a Binary Tree

Given the root of a binary tree, the level of its root is 1, the level of its children is 2, and so on.

Return the smallest level X such that the sum of all the values of nodes at level X is maximal.

Example 1:

1
2
3
4
5
6
7
Input: [1,7,0,7,-8,null,null]
Output: 2
Explanation:
Level 1 sum = 1.
Level 2 sum = 7 + 0 = 7.
Level 3 sum = 7 + -8 = -1.
So we return the level with the maximum sum which is level 2.

很简单的层次遍历,就当熟悉一下queue的用法。

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
class Solution {
public:
int maxLevelSum(TreeNode* root) {
queue<TreeNode*> q;
q.push(root);
int maxx = -99999;
int layer = 0, max_layer = 0;
while(!q.empty()){
int len = q.size();
int sum = 0;
layer++;
for(int i = 0; i < len; i ++){
TreeNode* temp = q.front();
q.pop();
if(temp->left != NULL)
q.push(temp->left);
if(temp->right != NULL)
q.push(temp->right);
sum += temp->val;
}
if(sum > maxx){
maxx = sum;
max_layer = layer;
}
}
return max_layer;
}
};

Leetcode1162. As Far from Land as Possible

Given an n x n grid containing only values 0 and 1, where 0 represents water and 1 represents land, find a water cell such that its distance to the nearest land cell is maximized, and return the distance. If no land or water exists in the grid, return -1.

The distance used in this problem is the Manhattan distance: the distance between two cells (x0, y0) and (x1, y1) is |x0 - x1| + |y0 - y1|.

Example 1:

1
2
3
Input: grid = [[1,0,1],[0,0,0],[1,0,1]]
Output: 2
Explanation: The cell (1, 1) is as far as possible from all the land with distance 2.

Example 2:

1
2
3
Input: grid = [[1,0,0],[0,0,0],[0,0,0]]
Output: 4
Explanation: The cell (2, 2) is as far as possible from all the land with distance 4.

Constraints:

  • n == grid.length
  • n == grid[i].length
  • 1 <= n <= 100
  • grid[i][j] is 0 or 1

这道题给了一个只有0和1的二维数组,说是0表示水,1表示陆地,现在让找出一个0的位置,使得其离最近的1的距离最大,这里的距离用曼哈顿距离表示。这里最暴力的方法就是遍历每个0的位置,对于每个遍历到的0,再遍历每个1的位置,计算它们的距离,找到最小的距离保存为该0位置的距离,然后在所有0位置的距离中找出最大的。这种方法不是很高效,目测无法通过 OJ,博主都没有尝试。其实这道题的比较好的解法是建立距离场,即每个大于1的数字表示该位置到任意一个1位置的最短距离,在之前那道 Shortest Distance from All Buildings 就用过这种方法。建立距离场用 BFS 比较方便,因为其是一层一层往外扩散的遍历,这里需要注意的是要一次把所有1的位置都加入 queue 中一起遍历,而不是对每个1都进行 BFS,否则还是过不了 OJ。这里先把位置1都加入 queue,然后这里先做个剪枝,若位置1的个数为0,或者为 n^2,表示没有陆地,或者没有水,直接返回 -1。否则进行 while 循环,步数 step 加1,然后用 for 循环确保进行层序遍历,一定要将 q.size() 放到初始化中,因为其值可能改变。然后就是标准的 BFS 写法了,取队首元素,遍历其相邻四个结点,若没有越界且值为0,则将当前位置值更新为 step,然后将这个位置加入 queue 中继续遍历。循环退出后返回 step-1 即可,参见代码如下:

解法一:

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
class Solution {
public:
int maxDistance(vector<vector<int>>& grid) {
int step = 0, n = grid.size();
vector<vector<int>> dirs{{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
queue<vector<int>> q;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] == 0) continue;
q.push(vector<int>{i, j});
}
}
if (q.size() == 0 || q.size() == n * n) return -1;
while (!q.empty()) {
++step;
for (int k = q.size(); k > 0; --k) {
auto t = q.front(); q.pop();
for (auto dir : dirs) {
int x = t[0] + dir[0], y = t[1] + dir[1];
if (x < 0 || x >= n || y < 0 || y >= n || grid[x][y] != 0) continue;
grid[x][y] = step;
q.push({x, y});
}
}
}
return step - 1;
}
};

我们也可以强行用 DFS 来做,这里对于每一个值为1的点都调用递归函数,更新跟其相连的所有0位置的距离,最终也能算出整个距离场,从而查找出最大的距离,参见代码如下:

解法二:

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
class Solution {
public:
int maxDistance(vector<vector<int>>& grid) {
int res = -1, n = grid.size();
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] == 1) {
grid[i][j] = 0;
helper(grid, i, j);
}
}
}
for (int i = n - 1; i >= 0; --i) {
for (int j = n - 1; j >= 0; --j) {
if (grid[i][j] > 1) res = max(res, grid[i][j] - 1);
}
}
return res;
}
void helper(vector<vector<int>>& grid, int i, int j, int dist = 1) {
int n = grid.size();
if (i < 0 || j < 0 || i >= n || j >= n || (grid[i][j] != 0 && grid[i][j] <= dist)) return;
grid[i][j] = dist;
helper(grid, i - 1, j, dist + 1);
helper(grid, i + 1, j, dist + 1);
helper(grid, i, j - 1, dist + 1);
helper(grid, i, j + 1, dist + 1);
}
};

其实这道题的最优解法并不是 BFS 或者 DFS,而是下面这种两次扫描的方法,在之前那道 01 Matrix 中就使用过。有点像动态规划的感觉,还是建立距离场,先从左上遍历到右下,遇到1的位置跳过,然后初始化0位置的值为 201(因为n不超过 100,所以距离不会超过 200),然后用左边和上边的值加1来更新当前位置的,注意避免越界。然后从右边再遍历到左上,还是遇到1的位置跳过,然后用右边和下边的值加1来更新当前位置的,注意避免越界,同时还要更新结果 res 的值。最终若 res 为 201,则返回 -1,否则返回 res-1,参见代码如下:

解法三:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
int maxDistance(vector<vector<int>>& grid) {
int res = 0, n = grid.size();
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] == 1) continue;
grid[i][j] = 201;
if (i > 0) grid[i][j] = min(grid[i][j], grid[i - 1][j] + 1);
if (j > 0) grid[i][j] = min(grid[i][j], grid[i][j - 1] + 1);
}
}
for (int i = n - 1; i >= 0; --i) {
for (int j = n - 1; j >= 0; --j) {
if (grid[i][j] == 1) continue;
if (i < n - 1) grid[i][j] = min(grid[i][j], grid[i + 1][j] + 1);
if (j < n - 1) grid[i][j] = min(grid[i][j], grid[i][j + 1] + 1);
res = max(res, grid[i][j]);
}
}
return res == 201 ? -1 : res - 1;
}
};

Leetcode1165. Single-Row Keyboard

There is a special keyboard with all keys in a single row.

Given a string keyboard of length 26 indicating the layout of the keyboard (indexed from 0 to 25), initially your finger is at index 0. To type a character, you have to move your finger to the index of the desired character. The time taken to move your finger from index i to index j is |i - j|.

You want to type a string word. Write a function to calculate how much time it takes to type it with one finger.

Example 1:

1
2
3
4
Input: keyboard = "abcdefghijklmnopqrstuvwxyz", word = "cba"
Output: 4
Explanation: The index moves from 0 to 2 to write 'c' then to 1 to write 'b' then to 0 again to write 'a'.
Total time = 2 + 1 + 1 = 4.

Example 2:

1
2
Input: keyboard = "pqrstuvwxyzabcdefghijklmno", word = "leetcode"
Output: 73

Constraints:

  • keyboard.length == 26
  • keyboard contains each English lowercase letter exactly once in some order.
  • 1 <= word.length <= 10^4
  • word[i] is an English lowercase letter.

简单打表即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int calculateTime(string keyboard, string word) {
vector<int> pos(26,-1);
int k=0;
for(int i=0;i<keyboard.length();i++)
pos[keyboard[i]-'a'] = k++;
int res=pos[word[0]-'a'];
for(int i=1;i<word.length();i++)
res += abs(pos[word[i]-'a']-pos[word[i-1]-'a']);
return res;
}
};

Leetcode1170. Compare Strings by Frequency of the Smallest Character

Let’s define a function f(s) over a non-empty string s, which calculates the frequency of the smallest character in s. For example, if s = “dcce” then f(s) = 2 because the smallest character is “c” and its frequency is 2.

Now, given string arrays queries and words, return an integer array answer, where each answer[i] is the number of words such that f(queries[i]) < f(W), where W is a word in words.

Example 1:

1
2
3
Input: queries = ["cbd"], words = ["zaaaz"]
Output: [1]
Explanation: On the first query we have f("cbd") = 1, f("zaaaz") = 3 so f("cbd") < f("zaaaz").

Example 2:

1
2
3
Input: queries = ["bbb","cc"], words = ["a","aa","aaa","aaaa"]
Output: [1,2]
Explanation: On the first query only f("bbb") < f("aaaa"). On the second query both f("aaa") and f("aaaa") are both > f("cc").

定义f(s)为一个字符串中最小的字符(按字母序abcd…xyz)出现的次数。对于每个query的f(query),求出words中f(word) > f(query)有多少个。

第一步,肯定要把每个query和word的f(s)求出来,求每个字符的次数,然后找出最小的字符出现的次数。
第二步,找出words中f(word) > f(query)有多少个时,对于2000*2000的量级,可以暴力两重循环,即对每个query都去遍历一次words的f(word)结果。

时间复杂度O(N^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
class Solution {
public:
vector<int> numSmallerByFrequency(vector<string>& queries, vector<string>& words) {
vector<int> qs, ws;
for(string query : queries)
qs.push_back(getfreq(query));
for(string word : words)
ws.push_back(getfreq(word));
vector<int> res;
for(int i : qs) {
int count = 0;
for(int j : ws) {
if(j > i)
count ++;
}
res.push_back(count);
}
return res;
}

int getfreq(string query) {
vector<int> q(26, 0);
for(char c : query)
q[c-'a'] ++;
for(int i : q)
if(i != 0)
return i;
return 0;
}
};

Leetcode1171. Remove Zero Sum Consecutive Nodes from Linked List

Given the head of a linked list, we repeatedly delete consecutive sequences of nodes that sum to 0 until there are no such sequences.

After doing so, return the head of the final linked list. You may return any such answer.

(Note that in the examples below, all sequences are serializations of ListNode objects.)

Example 1:

1
2
3
Input: head = [1,2,-3,3,1]
Output: [3,1]
Note: The answer [1,2,1] would also be accepted.

Example 2:

1
2
Input: head = [1,2,3,-3,4]
Output: [1,2,4]

Example 3:

1
2
Input: head = [1,2,3,-3,-2]
Output: [1]

对于两个前缀和相等部分,表示里面就是0,所以可以消除,在加入map的时候还能把之前的ListNode覆盖掉,达到消除的目标。消除时候就是把next指针指向和的下一个结点。在第二次遍历的时候发现了现在有的sum和表示当前的节点和后边某个节点的前缀和都是sum,中间的和就是0,可以消掉。

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
class Solution {
public:
ListNode* removeZeroSumSublists(ListNode* head) {
// 第一次遍历构建 前缀和 到 结点的映射
// 如果有多个,则保存最后一个,即右边界,尽可能覆盖更多
unordered_map<int, ListNode*> sum2node;
// 构建个虚结点来解决结点删除边缘情况
ListNode* dummy = new ListNode();
dummy->next = head;
int sum = 0;
ListNode* curr = dummy;
while (curr != nullptr)
{
sum += curr->val;
sum2node[sum] = curr;
curr = curr->next;
}

curr = dummy;
// 第二次遍历去发现相等为0情况则忽略结点
sum = 0;
while (curr != nullptr)
{
sum += curr->val;
// 因为前面计算过没有sum,所以sum必然存在sum2node里
// 如果正好出现,可能是正常的自己的结点
// 如果就是下一个相同的结点,那么就是把这一段结点忽略掉
curr->next = sum2node[sum]->next;
curr = curr->next;
}
return dummy->next;
}
};

Leetcode1175. Prime Arrangements

Return the number of permutations of 1 to n so that prime numbers are at prime indices (1-indexed.)

(Recall that an integer is prime if and only if it is greater than 1, and cannot be written as a product of two positive integers both smaller than it.)

Since the answer may be large, return the answer modulo 10^9 + 7.

Example 1:

1
2
3
Input: n = 5
Output: 12
Explanation: For example [1,2,5,4,3] is a valid permutation, but [5,2,3,4,1] is not because the prime number 5 is at index 1.

Example 2:

1
2
Input: n = 100
Output: 682289015

题目的意思是计算一个由1到n组成的数列中,质数恰好位于质数索引上的排列组合个数,本质上是一个数学问题。结合n = 5的例子来看,1到5中,只有2,3,5是质数,1和4不是质数,因此排列质数就有321 = 6种可能,分别是:

1
[2,3,5],[2,5,3],[3,2,5],[3,5,2],[5,2,3],[5,3,2]

不是质数的1和4,只有两种可能,分别是

1
[1,4],[4,1]

因此,将质数和非质数组合起来,就是6*2 = 12种可能,分别是

1
2
[1,2,3,4,5],[1,2,5,4,3],[1,3,2,4,5],[1,3,5,4,2],[1,5,2,4,3],[1,5,3,4,2]
[4,2,3,1,5],[4,2,5,1,3],[4,3,2,1,5],[4,3,5,1,2],[4,5,2,1,3],[4,5,3,1,2]

因此,我们只需要计算出n中有多少个质数和非质数,再计算两者的阶乘即可,为了防止溢出,题目要求我们将计算结果对1000000007取余。

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
class Solution {
public:

bool isprime(int n) {
int nn = sqrt(n);
for(int i = 2; i <= nn; i ++) {
if(n % i == 0)
return false;
}
return true;
}

int numPrimeArrangements(int n) {
int count1 = 0, count2;
for(int i = 2; i <= n; i ++) {
if(isprime(i))
count1 ++;
}
count2 = n - count1;
long res = 1;
for(int i = 1; i <= count1; i ++)
res = (res*i)%(1000000007);
for(int i = 1; i <= count2; i ++)
res = (res*i)%(1000000007);
return res;
}
};

Leetcode1177. Can Make Palindrome from Substring

Given a string s, we make queries on substrings of s.

For each query queries[i] = [left, right, k], we may rearrange the substring s[left], …, s[right], and then choose up to k of them to replace with any lowercase English letter.

If the substring is possible to be a palindrome string after the operations above, the result of the query is true. Otherwise, the result is false.

Return an array answer[], where answer[i] is the result of the i-th query queries[i].

Note that: Each letter is counted individually for replacement so if for example s[left..right] = “aaa”, and k = 2, we can only replace two of the letters. (Also, note that the initial string s is never modified by any query.)

Example :

1
2
3
4
5
6
7
8
Input: s = "abcda", queries = [[3,3,0],[1,2,0],[0,3,1],[0,3,2],[0,4,1]]
Output: [true,false,false,true,true]
Explanation:
queries[0] : substring = "d", is palidrome.
queries[1] : substring = "bc", is not palidrome.
queries[2] : substring = "abcd", is not palidrome after replacing only 1 character.
queries[3] : substring = "abcd", could be changed to "abba" which is palidrome. Also this can be changed to "baab" first rearrange it "bacd" then replace "cd" with "ab".
queries[4] : substring = "abcda", could be changed to "abcba" which is palidrome.

Constraints:

  • 1 <= s.length, queries.length <= 10^5
  • 0 <= queries[i][0] <= queries[i][1] < s.length
  • 0 <= queries[i][2] <= s.length
  • s only contains lowercase English letters.

这道题给了一个只有小写字母的字符串s,让对s对子串进行查询。查询块包含三个信息,left,right 和k,其中 left 和 right 定义了子串的范围,k表示可以进行替换字母的个数。这里希望通过替换可以将子串变为回文串。题目中还说了可以事先给子串进行排序,这个条件一加,整个性质就不一样了,若不能排序,那么要替换的字母可能就很多了,因为对应的位置上的字母要相同才行。而能排序之后,只要把相同的字母尽可能的排列到对应的位置上,就可以减少要替换的字母,比如 hunu,若不能重排列,则至少要替换两个字母才行,而能重排顺序的话,可以先变成 uhnu,再替换中间的任意一个字母就可以了。

需要替换的情况都是字母出现次数为奇数的情况,偶数的字母完全不用担心,所以只要统计出出现次数为奇数的字母的个数,其除以2就是要替换的次数。那可能有的童鞋会问了,万一是奇数怎么办,除以2除不尽怎么办,这是个好问题。若出现次数为奇数的字母的个数为奇数,则表明该子串的长度为奇数,而奇数回文串最中间的字母是不需要有对称位置的,所以自然可以少替换一个,所以除不尽的部分就自动舍去了,并不影响最终的结果。

这里是对每个子串都建立字母出现次数的映射,所以这里用一个二维数组,大小为 n+1 by 26,因为限定了只有小写字母。然后遍历字符串s进行更新,每次先将 cnt[i+1] 赋值为 cnt[i],然后在对应的字母位置映射值自增1。累加好了之后,对于任意区间 [i, j] 的次数映射数组就可以通过 cnt[j+1] - cnt[i] 来表示,但数组之间不好直接做减法,可以再进一步访问每个字母来分别进行处理,快速得到每个字母的出现次数后除以2,将结果累加到 sum 中,就是出现奇数次字母的个数了,再除以2和k比较即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
vector<bool> canMakePaliQueries(string s, vector<vector<int>>& queries) {
vector<bool> res;
vector<vector<int>> cnt(s.size() + 1, vector<int>(26));
for (int i = 0; i < s.size(); ++i) {
cnt[i + 1] = cnt[i];
++cnt[i + 1][s[i] - 'a'];
}
for (auto &query : queries) {
int sum = 0;
for (int i = 0; i < 26; ++i) {
sum += (cnt[query[1] + 1][i] - cnt[query[0]][i]) % 2;
}
res.push_back(sum / 2 <= query[2]);
}
return res;
}
};

Leetcode1179. Reformat Department Table

Table: Department

1
2
3
4
5
6
7
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| id | int |
| revenue | int |
| month | varchar |
+---------------+---------+

(id, month) is the primary key of this table.
The table has information about the revenue of each department per month.
The month has values in [“Jan”,”Feb”,”Mar”,”Apr”,”May”,”Jun”,”Jul”,”Aug”,”Sep”,”Oct”,”Nov”,”Dec”].

Write an SQL query to reformat the table such that there is a department id column and a revenue column for each month.

The query result format is in the following example:

1
2
3
4
5
6
7
8
9
10
Department table:
+------+---------+-------+
| id | revenue | month |
+------+---------+-------+
| 1 | 8000 | Jan |
| 2 | 9000 | Jan |
| 3 | 10000 | Feb |
| 1 | 7000 | Feb |
| 1 | 6000 | Mar |
+------+---------+-------+
1
2
3
4
5
6
7
8
Result table:
+------+-------------+-------------+-------------+-----+-------------+
| id | Jan_Revenue | Feb_Revenue | Mar_Revenue | ... | Dec_Revenue |
+------+-------------+-------------+-------------+-----+-------------+
| 1 | 8000 | 7000 | 6000 | ... | null |
| 2 | 9000 | null | null | ... | null |
| 3 | null | 10000 | null | ... | null |
+------+-------------+-------------+-------------+-----+-------------+

Note that the result table has 13 columns (1 for the department id + 12 for the months).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Write your MySQL query statement below
select id, sum(case when month ="Jan" then revenue else null end) as Jan_Revenue,
sum(case when month = "Feb" then revenue else null end) as Feb_Revenue,
sum(case when month = "Mar" then revenue else null end) as Mar_Revenue,
sum(case when month = "Apr" then revenue else null end) as Apr_Revenue,
sum(case when month = "May" then revenue else null end) as May_Revenue,
sum(case when month = "Jun" then revenue else null end) as Jun_Revenue,
sum(case when month = "Jul" then revenue else null end) as Jul_Revenue,
sum(case when month = "Aug" then revenue else null end) as Aug_Revenue,
sum(case when month = "Sep" then revenue else null end) as Sep_Revenue,
sum(case when month = "Oct" then revenue else null end) as Oct_Revenue,
sum(case when month = "Nov" then revenue else null end) as Nov_Revenue,
sum(case when month = "Dec" then revenue else null end) as Dec_Revenue
from Department
group by id
order by id

Leetcode1184. Distance Between Bus Stops

A bus has n stops numbered from 0 to n - 1 that form a circle. We know the distance between all pairs of neighboring stops where distance[i] is the distance between the stops number i and (i + 1) % n.

The bus goes along both directions i.e. clockwise and counterclockwise.

Return the shortest distance between the given start and destination stops.

Example 1:

1
2
3
Input: distance = [1,2,3,4], start = 0, destination = 1
Output: 1
Explanation: Distance between 0 and 1 is 1 or 9, minimum is 1.

Example 2:

1
2
3
Input: distance = [1,2,3,4], start = 0, destination = 2
Output: 3
Explanation: Distance between 0 and 2 is 3 or 7, minimum is 3.

Example 3:

1
2
3
Input: distance = [1,2,3,4], start = 0, destination = 3
Output: 4
Explanation: Distance between 0 and 3 is 6 or 4, minimum is 4.

求环状图的最短路,求两个距离并求最小值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int distanceBetweenBusStops(vector<int>& distance, int start, int destination) {
int n = distance.size();
int dis1= 0, dis2 = 0;
int start1 = start;
while(start1 != destination) {
dis1 += distance[start1];
start1 = (start1 + 1) % n;
}
start1 = start;
while(start1 != destination) {
start1 = (start1 - 1 + n) % n;
dis2 += distance[start1];
}
return min(dis1, dis2);
}
};

Leetcode1185. Day of the Week

Given a date, return the corresponding day of the week for that date.

The input is given as three integers representing the day, month and year respectively.

Return the answer as one of the following values {“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday”}.

Example 1:

1
2
Input: day = 31, month = 8, year = 2019
Output: "Saturday"

Example 2:

1
2
Input: day = 18, month = 7, year = 1999
Output: "Sunday"

Example 3:

1
2
Input: day = 15, month = 8, year = 1993
Output: "Sunday"

判断一个日期是一个周的第几天。直接套公式。

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:
string dayOfTheWeek(int day, int month, int year) {
static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
year -= month < 3;
// Sakamoto's Method: https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week#Sakamoto's_methods
int code = (year + year/4 - year/100 + year/400 + t[month-1] + day) % 7;

switch(code)
{
case 0:
return "Sunday";
break;
case 1:
return "Monday";
break;
case 2:
return "Tuesday";
break;
case 3:
return "Wednesday";
break;
case 4:
return "Thursday";
break;
case 5:
return "Friday";
break;
case 6:
return "Saturday";
break;
};
return "Error";
}
};

Leetcode1186. Maximum Subarray Sum with One Deletion 删除一次得到子数组最大和

Given an array of integers, return the maximum sum for a non-empty subarray (contiguous elements) with at most one element deletion. In other words, you want to choose a subarray and optionally delete one element from it so that there is still at least one element left and the sum of the remaining elements is maximum possible.

Note that the subarray needs to be non-empty after deleting one element.

Example 1:

Input: arr = [1,-2,0,3]
Output: 4
Explanation: Because we can choose [1, -2, 0, 3] and drop -2, thus the subarray [1, 0, 3] becomes the maximum value.
Example 2:

Input: arr = [1,-2,-2,3]
Output: 3
Explanation: We just choose [3] and it’s the maximum sum.
Example 3:

Input: arr = [-1,-1,-1,-1]
Output: -1
Explanation: The final subarray needs to be non-empty. You can’t choose [-1] and delete -1 from it, then get an empty subarray to make the sum equals to 0.
Constraints:

1 <= arr.length <= 105
-104 <= arr[i] <= 104

这道题给了一个整型数组,让返回最大的非空子数组之和,应该算是之前那道 Maximum Subarray 的拓展,与之不同的是,这里允许有一次删除某个数字的机会。当然,删除数字操作是可用可不用的,用之的目的也是为了让子数组之和变的更大,所以基本上要删除的数字应该是个负数,毕竟负数才会让和变小。若整个数组都是正数,则完全没有必要删除了。所以这道题还是要像之前那道题的一样,肯定要求出不删除情况下的最大子数组之和。该算法的核心思路是一种动态规划 Dynamic Programming,对于每个位置i,要计算出以该位置为结束位置时的最大子数组的之和,且该位置上的数字一定会被使用。大多情况下,为了节省空间,都用一个变量来代替数组,因为不需要保存之前的状态。而这道题因为允许删除操作的存在,还是要记录每个位置的状态。为啥呢,若将i位置上的数字删除了,实际上原数组就被分为两个部分:[0, i-1][i+1, n-1],由于是子数组,则arr[i-1]arr[i+1]这两个数字一定要出现在子数组中,用个maxEndHere[i]表示在 [0, i] 范围内以arr[i]结尾的最大子数组之和,用maxStartHere[i]表示在[i, n-1]范围内以arr[i]为起始的最大子数组之和,那么移除数字i的最大子数组之和就是maxEndHere[i-1] + maxStartHere[i+1]了,分析到这里,代码就不难写了吧,注意别忘了统计不需要删除数字时的最大子数组之和,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int maximumSum(vector<int>& arr) {
int res = arr[0], n = arr.size();
vector<int> maxEndHere(n), maxStartHere(n);
maxEndHere[0] = arr[0];
for (int i = 1; i < n; ++i) {
maxEndHere[i] = max(arr[i], maxEndHere[i - 1] + arr[i]);
res = max(res, maxEndHere[i]);
}
maxStartHere[n - 1] = arr[n - 1];
for (int i = n - 2; i >= 0; --i) {
maxStartHere[i] = max(arr[i], maxStartHere[i + 1] + arr[i]);
}
for (int i = 1; i < n - 1; ++i) {
res = max(res, maxEndHere[i - 1] + maxStartHere[i + 1]);
}
return res;
}
};

Leetcode1189. Maximum Number of Balloons

Given a string text, you want to use the characters of text to form as many instances of the word “balloon” as possible.

You can use each character in text at most once. Return the maximum number of instances that can be formed.

Example 1:

1
2
Input: text = "nlaebolko"
Output: 1

Example 2:

1
2
Input: text = "loonbalxballpoon"
Output: 2

Example 3:

1
2
Input: text = "leetcode"
Output: 0

判断一个字符串能组成多少个“balloon”,求出text中b,a,l,o,n这五个字符的出现次数的最小值即可。但有两点需要注意,一是text必须同时包含这五个字符,而是l和o是要算双份。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int maxNumberOfBalloons(string text) {
unordered_map<char, int> mp;
for(char c : text)
mp[c] ++;
int aa[5] = {mp['b'], mp['a'], mp['l']/2, mp['o']/2, mp['n']};
int minn = 99999;
for(int i = 0; i < 5; i ++)
minn = min(minn, aa[i]);
return minn;
}
};

Leetcode1190. Reverse Substrings Between Each Pair of Parentheses

You are given a string s that consists of lower case English letters and brackets.

Reverse the strings in each pair of matching parentheses, starting from the innermost one.

Your result should not contain any brackets.

Example 1:

1
2
Input: s = "(abcd)"
Output: "dcba"

Example 2:

1
2
3
Input: s = "(u(love)i)"
Output: "iloveu"
Explanation: The substring "love" is reversed first, then the whole string is reversed.

Example 3:

1
2
3
Input: s = "(ed(et(oc))el)"
Output: "leetcode"
Explanation: First, we reverse the substring "oc", then "etco", and finally, the whole string.

Example 4:

1
2
Input: s = "a(bcdefghijkl(mno)p)q"
Output: "apmnolkjihgfedcbq"

Constraints:

  • 0 <= s.length <= 2000
  • s only contains lower case English characters and parentheses.
  • It’s guaranteed that all parentheses are balanced.

这道题给了一个只含有小写字母和括号的字符串s,现在让从最内层括号开始,每次都反转括号内的所有字符,然后可以去掉该内层括号,依次向外层类推,直到去掉所有的括号为止。可能有的童鞋拿到题后第一反应可能是递归到最内层,翻转,然后再一层一层的出来。这样的做的话就有点麻烦了,而且保不齐还有可能超时。比较好的做法就是直接遍历这个字符串,当遇到字母时,将其加入结果 res,当遇到左括号时,将当前 res 的长度加入一个数组 pos,当遇到右括号时,取出 pos 数组中的最后一个数字,并翻转 res 中该位置到结尾的所有字母,因为这个区间刚好就是当前最内层的字母,这样就能按顺序依次翻转所有括号中的内容,最终返回结果 res 即可,参见代码如下:

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
string reverseParentheses(string s) {
string res = "";
stack<int> st;
int len = s.length();
for (int i = 0; i < len; i ++)
if (s[i] == '(')
st.push(i);
else if (s[i] == ')') {
int last = st.top();
st.pop();
reverse(s.begin()+last+1, s.begin()+i);
}
for (int i = 0; i < len; i ++)
if (s[i] != '(' && s[i] != ')')
res += s[i];
return res;

}
};

这道题居然还有 O(n) 的解法。这种解法首先要建立每一对括号位置之间的映射,而且是双向映射,即左括号位置映射到右括号位置,同时右括号位置也要映射到左括号位置,这样在第一次遇到左括号时,就可以直接跳到对应的右括号,然后往前遍历,当下次遇到右括号时,就直接跳到其对应的左括号,然后往后遍历,这样实际相当于在嵌套了两层的括号中,是不用翻转的,因为只有奇数嵌套才需要翻转,用这种逻辑遍历,就可以串出最终正确的结果,由于遍历顺序不停在改变,所以用一个变量d来控制方向,初始化为1,当需要变换了就取相反数即可,参见代码如下:

解法二:

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
class Solution {
public:
string reverseParentheses(string s) {
string res;
int n = s.size();
vector<int> pos, pair(n);
for (int i = 0; i < n; ++i) {
if (s[i] == '(') {
pos.push_back(i);
} else if (s[i] == ')') {
int idx = pos.back();
pos.pop_back();
pair[i] = idx;
pair[idx] = i;
}
}
for (int i = 0, d = 1; i < n; i += d) {
if (s[i] == '(' || s[i] == ')') {
i = pair[i];
d = -d;
} else {
res += s[i];
}
}
return res;
}
};

Leetcode1191. K-Concatenation Maximum Sum

Given an integer array arr and an integer k, modify the array by repeating it k times.

For example, if arr = [1, 2] and k = 3 then the modified array will be [1, 2, 1, 2, 1, 2].

Return the maximum sub-array sum in the modified array. Note that the length of the sub-array can be 0 and its sum in that case is 0.

As the answer can be very large, return the answer modulo 109 + 7.

Example 1:

1
2
Input: arr = [1,2], k = 3
Output: 9

Example 2:

1
2
Input: arr = [1,-2,1], k = 5
Output: 2

Example 3:

1
2
Input: arr = [-1,-2], k = 7
Output: 0

Constraints:

  • 1 <= arr.length <= 10^5
  • 1 <= k <= 10^5
  • -10^4 <= arr[i] <= 10^4

这道题给了一个数组 arr 和一个正整数k,说是数组可以重复k次,让找出最大的子数组之和。例子1中数组全是正数,则最大和的子数组就是其本身,那么重复几次,就要都加上,就是原数组所有数字之和再乘以k。例子2中由于有负数存在,所以最大和只是某个子数组,这里就是单独的一个1,但是一旦可以重复了,那么首尾的1就可以连在一起,形成一个和为2的子数组了,但也不是连的越多越好,只有有首尾相连才可能使得正数相连,所以最多连2个就行了,因为这里整个数组之和为0,连再多跟没连一样。但如果把数组变为 [1,-2,2] 的话,那就不一样了,虽然说两个为 [1,-2,2,1,-2,2] 的最大子数组之和为3,但是由于原数组之和为1,只要尽可能多的连,就可以得到更大的值,所以这种情况也要考虑到。例子3中数组全是负数,则不管重复多少次,还是取空数组和为0。

根据k的大小,若等于1,则对原数组用 Kadane 算法,若大于1,则只拼接一个数组,那么这里就可以用 min(k, 2) 来合并这两种情况,不过在取数的时候,要用 arr[i % n] 来避免越界,这样就可以得到最大子数组之和了,不过这也还是针对 k 小于等于2的情况,对于 k 大于2的情况,还是要把减去2剩余的次数乘以整个数组之和的值加上,再一起比较,这样最终的结果就是三者之中的最大值了,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int kConcatenationMaxSum(vector<int>& arr, int k) {
int res = INT_MIN, curSum = 0, n = arr.size(), M = 1e9 + 7;
long total = accumulate(arr.begin(), arr.end(), 0);
for (int i = 0; i < n * min(k, 2); ++i) {
curSum = max(curSum + arr[i % n], arr[i % n]);
res = max(res, curSum);
}
return max<long>({0, res, total * max(0, k - 2) + res}) % M;
}
};

Leetcode1192. Critical Connections in a Network

There are n servers numbered from 0 to n - 1 connected by undirected server-to-server connections forming a network where connections[i] = [ai, bi] represents a connection between servers ai and bi. Any server can reach other servers directly or indirectly through the network.

A critical connection is a connection that, if removed, will make some servers unable to reach some other server.

Return all critical connections in the network in any order.

Example 1:

1
2
3
Input: n = 4, connections = [[0,1],[1,2],[2,0],[1,3]]
Output: [[1,3]]
Explanation: [[3,1]] is also accepted.

Example 2:

1
2
Input: n = 2, connections = [[0,1]]
Output: [[0,1]]

Constraints:

  • 2 <= n <= 10^5
  • n - 1 <= connections.length <= 10^5
  • 0 <= ai, bi <= n - 1
  • ai != bi
  • There are no repeated connections.

这道题说是有n个服务器互相连接,定义了一种关键连接,就是当去掉后,会有一部分服务无法访问另一些服务。说白了,就是让求无向图中的桥,对于求无向图中的割点或者桥的问题,需要使用塔里安算法 Tarjan’s Algorithm。该算法是图论中非常常用的算法之一,能解决强连通分量,双连通分量,割点和桥,求最近公共祖先(LCA)等问题,可以参见知乎上的这个贴子。Tarjan 算法是一种深度优先遍历 Depth-first Search 的算法,而且还带有一点联合查找 Union Find 的影子在里面。和一般的 DFS 遍历不同的是,这里用了一个类似时间戳的概念,就是用一个 time 数组来记录遍历到当前结点的时间,初始化为1,每遍历到一个新的结点,时间值就增加1。这里还用了另一个数组 low,来记录在能够访问到的所有节点中,时间戳最小的值,这样,在一个环上的结点最终 low 值都会被更新成一个最小值,就好像 Union Find 中同一个群组中都有相同的 root 值一样。这是非常重要且聪明的处理方式,因为若当前结点是割点的话,即其实桥一头的端点,当过桥之后,由于不存在其他的路径相连通(假设整个图只有一个桥),那么无法再回溯回来的,所以不管桥另一头的端点 next 的 low 值如何更新,其一定还是会大于 cur 的 low 值的,则桥就找到了。可能干讲不好理解,建议看下上面提到的知乎帖子,里面有图和视频可以很好的帮助理解。

这里首先根据给定的边来建立图的结构,这里使用 HashMap 来建立结点和其所有相连的结点集合之间的映射。对于每条边,由于是无向图,则两个方向都要建立映射,然后就调用递归,要传的参数还真不少,图结构,当前结点,前一个结点,cnt 值,time 和 low 数组,还有结果 res。在递归函数中,首先给当前结点 cur 的 time 和 low 值都赋值为 cnt,然后 cnt 自增1。接下来遍历和当前结点所有相邻的结点,若 next 的时间戳为0,表示这个结点没有被遍历过,则对该结点调用递归,然后用 next 结点的 low 值来更新当前结点的 low 值。若 next 结点已经之前访问过了,但不是前一个结点,则用 next 的 time 值来更新当前结点的 low 值。若回溯回来之后,next 的 low 值仍然大于 cur 的 time 值,说明这两个结点之间没有其他的通路,则一定是个桥,加入到结果 res 中即可,参见代码如下:

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
class Solution {
public:
vector<vector<int>> criticalConnections(int n, vector<vector<int>>& connections) {
int cnt = 1;
vector<vector<int>> res;
vector<int> time(n), low(n);
unordered_map<int, vector<int>> g;
for (auto conn : connections) {
g[conn[0]].push_back(conn[1]);
g[conn[1]].push_back(conn[0]);
}
helper(g, 0, -1, cnt, time, low, res);
return res;
}
void helper(unordered_map<int, vector<int>>& g, int cur, int pre, int& cnt, vector<int>& time, vector<int>& low, vector<vector<int>>& res) {
time[cur] = low[cur] = cnt++;
for (int next : g[cur]) {
if (time[next] == 0) {
helper(g, next, cur, cnt, time, low, res);
low[cur] = min(low[cur], low[next]);
} else if (next != pre) {
low[cur] = min(low[cur], time[next]);
}
if (low[next] > time[cur]) {
res.push_back({cur, next});
}
}
}
};

Leetcode1200. Minimum Absolute Difference

Given an array of distinct integers arr, find all pairs of elements with the minimum absolute difference of any two elements.

Return a list of pairs in ascending order(with respect to pairs), each pair [a, b] follows

  • a, b are from arr
  • a < b
  • b - a equals to the minimum absolute difference of any two elements in arr

Example 1:

1
2
3
Input: arr = [4,2,1,3]
Output: [[1,2],[2,3],[3,4]]
Explanation: The minimum absolute difference is 1. List all pairs with difference equal to 1 in ascending order.

Example 2:

1
2
Input: arr = [1,3,6,10,15]
Output: [[1,3]]

Example 3:

1
2
Input: arr = [3,8,-10,23,19,-4,-14,27]
Output: [[-14,-10],[19,23],[23,27]]

首先排序,然后找到最小值及与最小值对应的元组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
vector<vector<int>> minimumAbsDifference(vector<int>& arr) {
sort(arr.begin(), arr.end());
int minn = 99999;
for(int i = 0; i < arr.size()-1; i ++)
minn = min(minn, arr[i+1] - arr[i]);
vector<vector<int>> res;
for(int i = 0; i < arr.size()-1; i ++) {
if(arr[i+1] - arr[i] == minn)
res.push_back({arr[i], arr[i+1]});
}
return res;
}
};
0%