Hao Yu's blog

The program monkey was eaten by the siege lion.

0%

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个或是多个,就是说,字符串 a*b,可以表示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,由于对于两组不同的元素(共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]);
}
};

Leetcode101. Symmetric Tree

Given a binary tree, check whether it is a mirror of itself (ie, symmetric around its center).

For example, this binary tree [1,2,2,3,4,4,3] is symmetric:

1
2
3
4
5
    1
/ \
2 2
/ \ / \
3 4 4 3

But the following [1,2,2,null,3,null,3] is not:
1
2
3
4
5
  1
/ \
2 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:

bool digui(TreeNode* left, TreeNode* right) {
if(left == NULL && right == NULL)
return true;
if(left == NULL || right == NULL)
return false;
if(left->val != right->val)
return false;
return digui(left->left, right->right) && digui(left->right, right->left);
}

bool isSymmetric(TreeNode* root) {
if(root == NULL)
return true;
return digui(root->left, root->right);
}
};

Leetcode102. Binary Tree Level Order Traversal

Given a binary tree, return the level order traversal of its nodes’ values. (ie, from left to right, level by level).

For example:

1
2
3
4
5
6
7
8
9
10
11
12
Given binary tree [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
return its level order traversal as:
[
[3],
[9,20],
[15,7]
]

给定一个二叉树,返回其按层序遍历得到的节点值。层序遍历即逐层地、从左到右访问所有结点。在每一层遍历开始前,先记录队列中的结点数量 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
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> q;
vector<vector<int>> result;
if(root!=NULL)
q.push(root);
while(!q.empty()) {
int n = q.size();
vector<int> result_temp;
for(int i=0;i<n;i++) {
TreeNode* temp = q.front();
q.pop();
result_temp.push_back(temp->val);
if(temp->left != NULL)
q.push(temp->left);
if(temp->right != NULL)
q.push(temp->right);
}
result.push_back(result_temp);
}
return result;
}
};

Leetcode103. Binary Tree Zigzag Level Order Traversal

Given a binary tree, return the zigzag level order traversal of its nodes’ values. (ie, from left to right, then right to left for the next level and alternate between).

For example:

1
2
3
4
5
6
7
8
9
10
11
12
Given binary tree [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
return its zigzag level order traversal as:
[
[3],
[20,9],
[15,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
class Solution {
public:
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
if(root == NULL)
return {};
vector<vector<int>> res;
queue<TreeNode*> q;
q.push(root);
int rev = 1, lenn = 0;
while(!q.empty()) {
vector<TreeNode*> temp;
int len = q.size();
while(len --) {
TreeNode* tt = q.front();
temp.push_back(tt);
q.pop();
if(tt->left)
q.push(tt->left);
if(tt->right)
q.push(tt->right);
}
res.push_back({});
for(auto a : temp) {
res[lenn].push_back(a->val);
}

if(rev == -1)
reverse(res[lenn].begin(), res[lenn].end());
lenn ++;
rev = rev * -1;
}
return res;
}
};

Leetcode104. Maximum Depth of Binary Tree

Given a binary tree, find its maximum depth. The maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node.

Note: A leaf is a node with no children.

Example:

1
2
3
4
5
6
7
8
Given binary tree [3,9,20,null,null,15,7],

3
/ \
9 20
/ \
15 7
return its depth = 3.

找到二叉树的最大深度。
1
2
3
4
5
6
7
8
9
10
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root == NULL)
return 0;
if(root->left == NULL && root->right == NULL)
return 1;
return 1 + max(maxDepth(root->left), maxDepth(root->right));
}
};

Leetcode105. Construct Binary Tree from Preorder and Inorder Traversal

Given preorder and inorder traversal of a tree, construct the binary tree.

Note:
You may assume that duplicates do not exist in the tree.

For example, given

1
2
3
4
5
6
7
8
9
preorder = [3,9,20,15,7]
inorder = [9,3,15,20,7]
Return the following binary tree:

3
/ \
9 20
/ \
15 7

从前序和中序恢复一棵二叉树。
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:

TreeNode* dfs(vector<int>& preorder, int pLeft, int pRight, vector<int> &inorder, int iLeft, int iRight) {
if(pLeft > pRight || iLeft > iRight)
return NULL;
int i;
for(i = iLeft; i <= iRight; i ++) {
if(preorder[pLeft] == inorder[i])
break;
}
TreeNode *cur = new TreeNode(preorder[pLeft]);
cur->left = dfs(preorder, pLeft+1, pLeft+i-iLeft, inorder, iLeft, i-1);
cur->right = dfs(preorder, pLeft+i-iLeft+1, pRight, inorder, i+1, iRight);
return cur;
}

TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
return dfs(preorder, 0, preorder.size()-1, inorder, 0, inorder.size()-1);

}
};

Leetcode106. Construct Binary Tree from Inorder and Postorder Traversal

Given inorder and postorder traversal of a tree, construct the binary tree.

Note:
You may assume that duplicates do not exist in the tree.

For example, given

1
2
3
4
5
6
7
8
9
inorder = [9,3,15,20,7]
postorder = [9,15,7,20,3]
Return the following binary tree:

3
/ \
9 20
/ \
15 7

从中序和后序恢复一棵二叉树。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:

TreeNode* dfs(vector<int>& inorder, int iLeft, int iRight, vector<int> &postorder, int pLeft, int pRight) {
if(pLeft > pRight || iLeft > iRight)
return NULL;
int i;
for(i = iLeft; i <= iRight; i ++) {
if(postorder[pRight] == inorder[i])
break;
}
TreeNode *cur = new TreeNode(postorder[pRight]);
cur->left = dfs(inorder, iLeft, i-1, postorder, pLeft, pLeft+i-iLeft-1);
cur->right = dfs(inorder, i+1, iRight, postorder, pLeft+i-iLeft, pRight-1);
return cur;
}

TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
return dfs(inorder, 0, inorder.size()-1, postorder, 0, postorder.size()-1);
}
};

Leetcode107. Binary Tree Level Order Traversal II

Given a binary tree, return the bottom-up level order traversal of its nodes’ values. (ie, from left to right, level by level from leaf to root).

For example:

1
2
3
4
5
6
7
8
9
10
11
12
Given binary tree [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
return its bottom-up level order traversal as:
[
[15,7],
[9,20],
[3]
]

用BFS简单做做就ok
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<vector<int>> levelOrderBottom(TreeNode* root) {
if(root == NULL)
return {};
vector<vector<int>> res;

queue<TreeNode*> q;
q.push(root);
vector<TreeNode*> vec;
while(!q.empty()) {
vector<int> ress;
vec.clear();
ress.clear();
int n = q.size();
while(n--) {
TreeNode* temp = q.front();
q.pop();
vec.push_back(temp);
if(temp->left)
q.push(temp->left);
if(temp->right)
q.push(temp->right);
}

for(int i = 0; i < vec.size(); i++) {
ress.push_back(vec[i]->val);
}
res.insert(res.begin(), ress);
}
// reverse(res.begin(), res.end());如果不是之前在最前边插入而是在最后翻转的话,会更快。
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
33
34
35
36
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
vector<TreeNode *> aa;
vector<TreeNode *> ak;
vector<int> al;
vector<vector<int>> zero;
if(root == NULL){
return zero;
}
al.push_back(root->val);
aa.push_back(root);
zero.push_back(al);
while(aa.size() != 0){
ak.clear();
al.clear();
for(TreeNode* k:aa){
if(k->left != NULL){
al.push_back(k->left->val);
ak.push_back(k->left);
}
if(k->right != NULL){
al.push_back(k->right->val);
ak.push_back(k->right);
}
}
aa = ak;
if(al.size() !=0){
zero.push_back(al);
}
}
reverse(zero.begin(),zero.end());
return zero;

}
};

Leetcode108. Convert Sorted Array to Binary Search Tree

Given an array where elements are sorted in ascending order, convert it to a height balanced BST.

For this problem, a height-balanced binary tree is defined as a binary tree in which the depth of the two subtrees of every node never differ by more than 1.

Example:

1
2
3
4
5
6
7
8
9
Given the sorted array: [-10,-3,0,5,9],

One possible answer is: [0,-3,9,-10,null,5], which represents the following height balanced BST:

0
/ \
-3 9
/ /
-10 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
class Solution {
public:

TreeNode* bst(vector<int>& nums, int left, int right) {
TreeNode* root;
if(left > right) {
root = NULL;
} else if(left == right) {
root = new TreeNode(nums[left]);
} else {
int mid = (left + right) / 2;
root = new TreeNode(nums[mid]);
root->left = bst(nums, left, mid - 1);
root->right = bst(nums, mid+1, right);
}
return root;
}

TreeNode* sortedArrayToBST(vector<int>& nums) {
int n = nums.size();
if(n == 0)
return NULL;
TreeNode* root = bst(nums, 0, n - 1);
return root;
}
};

Leetcode109. Convert Sorted List to Binary Search Tree

Given a singly linked list where elements are sorted in ascending order, convert it to a height balanced BST.

For this problem, a height-balanced binary tree is defined as a binary tree in which the depth of the two subtrees of every node never differ by more than 1.

Example:

1
2
3
4
5
6
7
8
9
Given the sorted linked list: [-10,-3,0,5,9],

One possible answer is: [0,-3,9,-10,null,5], which represents the following height balanced BST:

0
/ \
-3 9
/ /
-10 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
class Solution {
public:
TreeNode* sortedListToBST(ListNode* head) {
if(head == NULL)
return NULL;
return dfs(head, NULL);
}

TreeNode* dfs(ListNode* head, ListNode* tail) {
if(head == tail) {
return NULL;
}
ListNode *fast = head, *slow = head;
while(fast != tail && fast->next != tail) {
fast = fast->next->next;
slow = slow->next;
}

TreeNode *root = new TreeNode(slow->val);
root->left = dfs(head, slow);
root->right = dfs(slow->next, tail);
return root;
}
};

Leetcode110. Balanced Binary Tree

Given a binary tree, determine if it is height-balanced. For this problem, a height-balanced binary tree is defined as:

  • a binary tree in which the left and right subtrees of every node differ in height by no more than 1.

Example 1:

1
2
3
4
5
6
7
8
Given the following tree [3,9,20,null,null,15,7]:

3
/ \
9 20
/ \
15 7
Return true.

Example 2:
1
2
3
4
5
6
7
8
9
10
Given the following tree [1,2,2,3,3,null,null,4,4]:

1
/ \
2 2
/ \
3 3
/ \
4 4
Return false.

求二叉树是否平衡,根据题目中的定义,高度平衡二叉树是每一个结点的两个子树的深度差不能超过1,那么我们肯定需要一个求各个点深度的函数,然后对每个节点的两个子树来比较深度差,时间复杂度为O(NlgN),代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
bool isBalanced(TreeNode* root) {
if(!root) return true;
if(abs(getLength(root->left) - getLength(root->right)) > 1) return false;
return isBalanced(root->left)&&isBalanced(root->right);
}
int getLength(TreeNode* root) {
if(!root) return 0;
return 1 + max(getLength(root->left), getLength(root->right));
}
};

Leetcode111. Minimum Depth of Binary Tree

Given a binary tree, find its minimum depth. The minimum depth is the number of nodes along the shortest path from the root node down to the nearest leaf node.

Note: A leaf is a node with no children.

Example:

1
2
3
4
5
6
7
8
Given binary tree [3,9,20,null,null,15,7],

3
/ \
9 20
/ \
15 7
return its minimum depth = 2.

如果说有个单支树 那么返回的高度 是 1+不为空子树的长度!在这里翻车了。
1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int minDepth(TreeNode* root) {
if(root == NULL)
return 0;
if(root->left == NULL && root->right == NULL)
return 1;
if(root->left == NULL) return 1+minDepth(root->right);
if(root->right == NULL) return 1+minDepth(root->left);
return 1 + min(minDepth(root->left), minDepth(root->right));
}
};

Leetcode112. Path Sum

Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up all the values along the path equals the given sum.

Note: A leaf is a node with no children.

Example:

1
2
3
4
5
6
7
8
9
10
Given the below binary tree and sum = 22,

5
/ \
4 8
/ / \
11 13 4
/ \ \
7 2 1
return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22.

简单dfs。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
bool dfs(TreeNode* root, int sum, int cur) {
if(root == NULL)
return false;
if(root->left == NULL && root->right == NULL) {
return sum == cur + root->val;
}
return dfs(root->left, sum, cur + root->val) || dfs(root->right, sum, cur + root->val);
}

bool hasPathSum(TreeNode* root, int sum) {
if(root == NULL)
return false;
return dfs(root, sum, 0);
}
};

Leetcode113. Path Sum II

Given a binary tree and a sum, find all root-to-leaf paths where each path’s sum equals the given sum.

Note: A leaf is a node with no children.

Example:

1
2
3
4
5
6
7
8
9
Given the below binary tree and sum = 22,

5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1

Return:
1
2
3
4
[
[5,4,11,2],
[5,8,4,5]
]

路径可能不止一条,所以必须要把整棵树遍历。所谓的路径是一个root-to-leaf path。所以从根节点开始,先保证有一个数组来存放路径。但是我们把这个数组当作栈来用,也就是使用push_back、pop_back。每进入一个节点就push,离开以这个节点为根的树就pop,到达叶子节点就判断是否是所求路径。
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(TreeNode* root, int sum, int cur_sum, vector<int> cur, vector<vector<int>>& res) {
if(root == NULL)
return;
cur_sum += root->val;
cur.push_back(root->val);
if(!root->left && !root->right) {
if(sum == cur_sum)
res.push_back(cur);
return;
}
dfs(root->left, sum, cur_sum, cur, res);
dfs(root->right, sum, cur_sum, cur, res);
cur_sum -= root->val;
cur.pop_back();
}

vector<vector<int>> pathSum(TreeNode* root, int sum) {
if(root == NULL)
return {};
vector<int> temp;
vector<vector<int> > res;
dfs(root, sum, 0, temp, res);
return res;
}
};

Leetcode114. Flatten Binary Tree to Linked List

Given a binary tree, flatten it to a linked list in-place.

For example, given the following tree:

1
2
3
4
5
    1
/ \
2 5
/ \ \
3 4 6

The flattened tree should look like:
1
2
3
4
5
6
7
8
9
10
11
1
\
2
\
3
\
4
\
5
\
6

这道题要求把二叉树展开成链表,根据展开后形成的链表的顺序分析出是使用先序遍历,递归版本的先利用 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
1
/ \
2 5
/ \ \
3 4 6

1
/ \
2 5
\ \
3 6
\
4

1
\
2
\
3
\
4
\
5
\
6

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

void flatten(TreeNode* root) {
if(root == NULL)
return;
if(root->left)
flatten(root->left);
if(root->right)
flatten(root->right);
TreeNode* tmp = root->right;
root->right = root->left;
root->left = NULL;

while(root->right)
root = root->right;
root->right = tmp;

}
};

Leetcode116. Populating Next Right Pointers in Each Node

You are given a perfect binary tree where all leaves are on the same level, and every parent has two children. The binary tree has the following definition:

1
2
3
4
5
6
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}

Populate each next pointer to point to its next right node. If there is no next right node, the next pointer should be set to NULL.

Initially, all next pointers are set to NULL.

Follow up:

  • You may only use constant extra space.
  • Recursive approach is fine, you may assume implicit stack space does not count as extra space for this problem.

Example 1:

1
2
Input: root = [1,2,3,4,5,6,7]
Output: [1,#,2,3,#,4,5,6,7,#]

Explanation: Given the above perfect binary tree (Figure A), your function should populate each next pointer to point to its next right node, just like in Figure B. The serialized output is in level order as connected by the next pointers, with ‘#’ signifying the end of each level.

层次遍历即可

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:
Node* connect(Node* root) {
if(root == NULL)
return NULL;
queue<Node*> q;
q.push(root);
int rev = 1, lenn = 0;
while(!q.empty()) {
vector<Node*> temp;
int len = q.size();
while(len --) {
Node* tt = q.front();
temp.push_back(tt);
q.pop();
if(tt->left)
q.push(tt->left);
if(tt->right)
q.push(tt->right);
}

for(int i = 0; i < temp.size()-1; i ++) {
temp[i]->next = temp[i+1];
}
temp[temp.size()-1]->next = NULL;
}
return root;
}
};

或者说,若节点的左子结点存在的话,其右子节点必定存在,所以左子结点的 next 指针可以直接指向其右子节点,对于其右子节点的处理方法是,判断其父节点的 next 是否为空,若不为空,则指向其 next 指针指向的节点的左子结点,若为空则指向 NULL。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
Node* connect(Node* root) {
if(!root)
return root;
if(root->left)
root->left->next = root->right;
if(root->right)
root->right->next = root->next ? root->next->left : NULL;
connect(root->left);
connect(root->right);
return root;

}
};

Leetcode117. Populating Next Right Pointers in Each Node II

Given a binary tree

1
2
3
4
5
6
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}

Populate each next pointer to point to its next right node. If there is no next right node, the next pointer should be set to NULL. Initially, all next pointers are set to NULL.

原本的完全二叉树的条件不再满足,但是整体的思路还是很相似,由于子树有可能残缺,故需要平行扫描父节点同层的节点,找到他们的左右子节点。

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:
Node* connect(Node* root) {
if(root == NULL) return root;
Node *ne = root->next;
while(ne) {
if(ne->left) {
ne = ne->left;
break;
}
if(ne->right) {
ne = ne->right;
break;
}
ne = ne->next;
}

if(root->right) root->right->next = ne;
if(root->left) root->left->next = root->right ? root->right : ne;
connect(root->right);
connect(root->left);
return root;
}
};

Leetcode118. Pascal’s Triangle

Given a non-negative integer numRows, generate the first numRows of Pascal’s triangle. In Pascal’s triangle, each number is the sum of the two numbers directly above it.

Example:

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

杨辉三角,简单。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> res;
for(int i = 0; i < numRows; i ++) {
vector<int> temp(i+1);
temp[0] = temp[i] = 1;
res.push_back(temp);
}
if(numRows == 0 || numRows == 1)
return res;
for(int i = 2; i < numRows; i ++) {
for(int j = 1; j < i; j ++ ) {
res[i][j] = res[i-1][j-1] + res[i-1][j];
}
}

return res;
}
};

Leetcode119. Pascal’s Triangle II

Given a non-negative index k where k ≤ 33, return the kth index row of the Pascal’s triangle.

Note that the row index starts from 0.(图跟上一个题一样)
是一个简单的dp,但是注意不能从前往后,因为从前往后的话就会覆盖掉之前迭代的结果。!!!!!!!

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

Leetcode120. Triangle

Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below. For example, given the following triangle

1
2
3
4
5
6
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]

The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).
简单dp,注意从后向前就行。
1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
vector<vector<int>> result = triangle;
for(int i = triangle.size()-2; i >= 0; i --) {
for(int j = 0; j < triangle[i].size(); j ++) {
result[i][j] = min(result[i+1][j], result[i+1][j+1]) + result[i][j];
}
}
return result[0][0];
}
};

Leetcode121. Best Time to Buy and Sell Stock

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

If you were only permitted to complete at most one transaction (i.e., buy one and sell one share of the stock), design an algorithm to find the maximum profit.

Note that you cannot sell a stock before you buy one.

Example 1:

1
2
Input: [7,1,5,3,6,4]
Output: 5

Explanation: Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5.
Not 7-1 = 6, as selling price needs to be larger than buying price.
Example 2:
1
2
Input: [7,6,4,3,1]
Output: 0

Explanation: In this case, no transaction is done, i.e. max profit = 0.

我们按照动态规划的思想来思考这道问题。

状态

有 买入(buy) 和 卖出(sell) 这两种状态。

转移方程

对于买来说,买之后可以卖出(进入卖状态),也可以不再进行股票交易(保持买状态)。

对于卖来说,卖出股票后不在进行股票交易(还在卖状态)。

只有在手上的钱才算钱,手上的钱购买当天的股票后相当于亏损。也就是说当天买的话意味着损失-prices[i],当天卖的话意味着增加prices[i],当天卖出总的收益就是 buy+prices[i] 。

所以我们只要考虑当天买和之前买哪个收益更高,当天卖和之前卖哪个收益更高。

buy = max(buy, -price[i]) (注意:根据定义 buy 是负数)
sell = max(sell, prices[i] + buy)
边界

第一天 buy = -prices[0], sell = 0,最后返回 sell 即可。

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

Leetcode122. Best Time to Buy and Sell Stock II

Say you have an array prices for which the ith 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 (i.e., buy one and sell one share of the stock multiple times).

Note: You may not engage in multiple transactions at the same time (i.e., you must sell the stock before you buy again).

Example 1:

1
2
3
4
Input: [7,1,5,3,6,4]
Output: 7
Explanation: Buy on day 2 (price = 1) and sell on day 3 (price = 5), profit = 5-1 = 4.
Then buy on day 4 (price = 3) and sell on day 5 (price = 6), profit = 6-3 = 3.

Example 2:
Input: [1,2,3,4,5]
Output: 4
Explanation: Buy on day 1 (price = 1) and sell on day 5 (price = 5), profit = 5-1 = 4.
Note that you cannot buy on day 1, buy on day 2 and sell them later, as you are
engaging multiple transactions at the same time. You must sell before buying again.
1
2
3
4
Example 3:
Input: [7,6,4,3,1]
Output: 0
Explanation: In this case, no transaction is done, i.e. max profit = 0.

这道题由于可以无限次买入和卖出。我们都知道炒股想挣钱当然是低价买入高价抛出,那么这里我们只需要从第二天开始,如果当前价格比之前价格高,则把差值加入利润中,因为我们可以昨天买入,今日卖出,若明日价更高的话,还可以今日买入,明日再抛出。
1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int maxProfit(vector<int>& prices) {
int res = 0;
for(int i = 1; i < prices.size();i ++) {
if(prices[i] > prices[i - 1]) {
res += (prices[i] - prices[i - 1]);
}
}
return res;
}
};

Leetcode123. Best Time to Buy and Sell Stock III

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 at most two transactions.

Note: You may not engage in multiple transactions at the same time (i.e., you must sell the stock before you buy again).

Example 1:

1
2
3
4
Input: [3,3,5,0,0,3,1,4]
Output: 6
Explanation: Buy on day 4 (price = 0) and sell on day 6 (price = 3), profit = 3-0 = 3.
Then buy on day 7 (price = 1) and sell on day 8 (price = 4), profit = 4-1 = 3.

Example 2:

1
2
3
4
5
Input: [1,2,3,4,5]
Output: 4
Explanation: Buy on day 1 (price = 1) and sell on day 5 (price = 5), profit = 5-1 = 4.
Note that you cannot buy on day 1, buy on day 2 and sell them later, as you are
engaging multiple transactions at the same time. You must sell before buying again.

Example 3:

1
2
3
Input: [7,6,4,3,1]
Output: 0
Explanation: In this case, no transaction is done, i.e. max profit = 0.

这道是要求最多交易两次,找到最大利润,还是需要用动态规划Dynamic Programming来解,而这里我们需要两个递推公式来分别更新两个变量local和global,我们其实可以求至少k次交易的最大利润,找到通解后可以设定 k = 2,即为本题的解答。我们定义local[i][j]为在到达第i天时最多可进行j次交易并且最后一次交易在最后一天卖出的最大利润,此为局部最优。然后我们定义global[i][j]为在到达第i天时最多可进行j次交易的最大利润,此为全局最优。它们的递推式为:

1
2
3
local[i][j] = max(global[i - 1][j - 1] + max(diff, 0), local[i - 1][j] + diff)

global[i][j] = max(local[i][j], global[i - 1][j])

其中局部最优值是比较前一天并少交易一次的全局最优加上大于0的差值,和前一天的局部最优加上差值中取较大值,而全局最优比较局部最优和前一天的全局最优,代码如下:

解法一:

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

Leetcode124. Binary Tree Maximum Path Sum

Given a non-empty binary tree, find the maximum path sum.

For this problem, a path is defined as any sequence of nodes from some starting node to any node in the tree along the parent-child connections. The path must contain at least one node and does not need to go through the root.

Example 1:

1
2
3
4
5
Input: [1,2,3]
1
/ \
2 3
Output: 6

Example 2:

1
2
3
4
5
6
7
8
9
10
11
12
Input: [-10,9,20,null,null,15,7]
-10
/ \
9 20
/ \
15 7
Output: 42
4
/ \
11 13
/ \
7 2

由于这是一个很简单的例子,很容易就能找到最长路径为 7->11->4->13,那么怎么用递归来找出正确的路径和呢?根据以往的经验,树的递归解法一般都是递归到叶节点,然后开始边处理边回溯到根节点。这里就假设此时已经递归到结点7了,其没有左右子节点,如果以结点7为根结点的子树最大路径和就是7。然后回溯到结点 11,如果以结点 11 为根结点的子树,最大路径和为 7+11+2=20。但是当回溯到结点4的时候,对于结点 11 来说,就不能同时取两条路径了,只能取左路径,或者是右路径,所以当根结点是4的时候,那么结点 11 只能取其左子结点7,因为7大于2。所以,对于每个结点来说,要知道经过其左子结点的 path 之和大还是经过右子节点的 path 之和大。递归函数返回值就可以定义为以当前结点为根结点,到叶节点的最大路径之和,然后全局路径最大值放在参数中,用结果 res 来表示。

在递归函数中,如果当前结点不存在,直接返回0。否则就分别对其左右子节点调用递归函数,由于路径和有可能为负数,这里当然不希望加上负的路径和,所以和0相比,取较大的那个,就是要么不加,加就要加正数。然后来更新全局最大值结果 res,就是以左子结点为终点的最大 path 之和加上以右子结点为终点的最大 path 之和,还要加上当前结点值,这样就组成了一个条完整的路径。而返回值是取 left 和 right 中的较大值加上当前结点值,因为返回值的定义是以当前结点为终点的 path 之和,所以只能取 left 和 right 中较大的那个值,而不是两个都要,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int maxPathSum(TreeNode* root) {
int res = INT_MIN;
helper(root, res);
return res;
}
int helper(TreeNode* node, int& res) {
if (!node) return 0;
int left = max(helper(node->left, res), 0);
int right = max(helper(node->right, res), 0);
res = max(res, left + right + node->val);
return max(left, right) + node->val;
}
};

Leetcode125. Valid Palindrome

Given a string, determine if it is a palindrome, considering only alphanumeric characters and ignoring cases.

Note: For the purpose of this problem, we define empty string as valid palindrome.

Example 1:

1
2
Input: "A man, a plan, a canal: Panama"
Output: true

Example 2:
1
2
Input: "race a car"
Output: false

这道题只考虑数字和字母(字母有大小写之分),所以遇到其它符号时,自动跳过。具体来说就是,分别从字符串两边向中间遍历,遇到非数字和字母的符号就跳过,直至左右两个指针遇到。这题有些隐藏的小细节需要注意。

  1. 字母有大小写之分,比较之前应该先统一转变为小写或大写。
  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
25
26
27
28
29
30
31
class Solution {
public:

bool islegal(char s) {
if ('A'<=s && s<='Z' || 'a'<=s && s<='z' || '0'<=s && s<='9')
return true;
return false;
}

bool isPalindrome(string s) {
if(s == "" || s.length()==1 || s.length()==0)
return true;
int left = 0, cur = 0, right = s.size();
while(left < right) {
while(left < right && !islegal(s[left]))
left ++;
while(left < right && !islegal(s[right]))
right --;
if('A'<=s[left] && s[left]<='Z')
s[left] += 32;
if('A'<=s[right] && s[right]<='Z')
s[right] += 32;

if(s[left] != s[right])
return false;
left ++;
right --;
}
return true;
}
};

Leetcode126. Word Ladder II

Given two words ( beginWord and endWord ), and a dictionary’s word list, find all shortest transformation sequence(s) from beginWord to endWord , such that:

  • Only one letter can be changed at a time
  • Each transformed word must exist in the word list. Note that beginWord is not a transformed word.

Note:

  • Return an empty list if there is no such transformation sequence.
  • All words have the same length.
  • All words contain only lowercase alphabetic characters.
  • You may assume no duplicates in the word list.
  • You may assume beginWord and endWord are non-empty and are not the same.

Example 1:

1
2
3
4
5
6
7
8
9
10
Input:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]

Output:
[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]

Example 2:

1
2
3
4
5
6
7
8
Input:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]

Output: []

Explanation: The endWord "cog" is not in wordList, therefore no possible transformation.

下面这种解法的核心思想是 BFS,大概思路如下:目的是找出所有的路径,这里建立一个路径集 paths,用以保存所有路径,然后是起始路径p,在p中先把起始单词放进去。然后定义两个整型变量 level,和 minLevel,其中 level 是记录循环中当前路径的长度,minLevel 是记录最短路径的长度,这样的好处是,如果某条路径的长度超过了已有的最短路径的长度,那么舍弃,这样会提高运行速度,相当于一种剪枝。还要定义一个 HashSet 变量 words,用来记录已经循环过的路径中的词,然后就是 BFS 的核心了,循环路径集 paths 里的内容,取出队首路径,如果该路径长度大于 level,说明字典中的有些词已经存入路径了,如果在路径中重复出现,则肯定不是最短路径,所以需要在字典中将这些词删去,然后将 words 清空,对循环对剪枝处理。然后取出当前路径的最后一个词,对每个字母进行替换并在字典中查找是否存在替换后的新词,这个过程在之前那道 Word Ladder 里面也有。如果替换后的新词在字典中存在,将其加入 words 中,并在原有路径的基础上加上这个新词生成一条新路径,如果这个新词就是结束词,则此新路径为一条完整的路径,加入结果中,并更新 minLevel,若不是结束词,则将新路径加入路径集中继续循环。

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<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
vector<vector<string>> res;
unordered_set<string> dict(wordList.begin(), wordList.end());
vector<string> p{beginWord};
queue<vector<string>> paths;
paths.push(p);
int level = 1, minLevel = INT_MAX;
unordered_set<string> words;
while (!paths.empty()) {
auto t = paths.front(); paths.pop();
if (t.size() > level) {
for (string w : words) dict.erase(w);
words.clear();
level = t.size();
if (level > minLevel) break;
}
string last = t.back();
for (int i = 0; i < last.size(); ++i) {
string newLast = last;
for (char ch = 'a'; ch <= 'z'; ++ch) {
newLast[i] = ch;
if (!dict.count(newLast)) continue;
words.insert(newLast);
vector<string> nextPath = t;
nextPath.push_back(newLast);
if (newLast == endWord) {
res.push_back(nextPath);
minLevel = level;
} else paths.push(nextPath);
}
}
}
return res;
}
};

先建图,再使用bfs建立最短路,因为在建立最短路时f[v] = f[u] + 1,所以在之后用dfs找到相差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
class Solution {
public:
unordered_map<string, int> data;
vector<vector<int>> g;
vector<int> f;
vector<vector<string>> res;
vector<string> wl;

void dfs(int u, int ed, vector<string>& ans){
ans.push_back(wl[u]);
if (u == ed) {
res.push_back(ans);
return;
}
for (int v : g[u]) {
if (f[v] + 1 == f[u]) {
dfs(v, ed, ans);
ans.pop_back();
}
}
}
vector<vector<string>> findLadders(string bw, string ew, vector<string>& wl) {
wl.push_back(bw);
for (int i = 0; i < wl.size(); i ++)
data[wl[i]] = i;

if (!data.count(ew))
return {};
int ed = data[ew];
int st = wl.size()-1;

g.assign(wl.size(), {});
for (int i = 0; i < wl.size(); i ++) {
int m = wl[i].length();
for (int j = 0; j < m; j ++) {
char c = wl[i][j];
for (int k = 0; k < 26; k ++) {
if (c - 'a' != k) {
wl[i][j] = k + 'a';
if (!data.count(wl[i]))
continue;
int v = data[wl[i]];
g[i].push_back(v);
}
}
wl[i][j] = c;
}
}

f.assign(wl.size(), 1e9);
queue<int> q;
f[ed] = 0;
q.push(ed);
while(!q.empty()) {
int t = q.front();
q.pop();
for (int v : g[t]) {
if (f[t] + 1 < f[v]) {
f[v] = f[t] + 1;
q.push(v);
}
}
}

this->wl = wl;
vector<string> ans;
dfs(st, ed, ans);
return res;
}
};

Leetcode127. Word Ladder

Given two words (beginWord and endWord), and a dictionary’s word list, find the length of shortest transformation sequence from beginWord to endWord, such that:

  • Only one letter can be changed at a time.
  • Each transformed word must exist in the word list.

Note:

  • Return 0 if there is no such transformation sequence.
  • All words have the same length.
  • All words contain only lowercase alphabetic characters.
  • You may assume no duplicates in the word list.
  • You may assume beginWord and endWord are non-empty and are not the same.

Example 1:

1
2
3
4
5
6
7
8
9
Input:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]

Output: 5

Explanation: As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",
return its length 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
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
unordered_set<string> S;
for (auto& word: wordList) S.insert(word);
unordered_map<string, int> dist;
dist[beginWord] = 0;
queue<string> q;
q.push(beginWord);

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

string r = t;
for (int i = 0; i < t.size(); i ++ ) {
t = r;
for (char j = 'a'; j <= 'z'; j ++ ) {
t[i] = j;
if (S.count(t) && !dist.count(t)) {
dist[t] = dist[r] + 1;
if (t == endWord) return dist[t] + 1;
q.push(t);
}
}
}
}
return 0;
}
};

Leetcode128. Longest Consecutive Sequence

Given an unsorted array of integers nums, return the length of the longest consecutive elements sequence.

You must write an algorithm that runs in O(n) time.

Example 1:

1
2
3
Input: nums = [100,4,200,1,3,2]
Output: 4
Explanation: The longest consecutive elements sequence is [1, 2, 3, 4]. Therefore its length is 4.

这道题要求求最长连续序列,并给定了O(n)复杂度限制,我们的思路是,使用一个集合HashSet存入所有的数字,然后遍历数组中的每个数字,如果其在集合中存在,那么将其移除,然后分别用两个变量pre和next算出其前一个数跟后一个数,然后在集合中循环查找,如果pre在集合中,那么将pre移除集合,然后pre再自减1,直至pre不在集合之中,对next采用同样的方法,那么next-pre-1就是当前数字的最长连续序列,更新res即可。这里再说下,为啥当检测某数字在集合中存在当时候,都要移除数字。这是为了避免大量的重复计算,就拿题目中的例子来说吧,我们在遍历到4的时候,会向下遍历3,2,1,如果都不移除数字的话,遍历到1的时候,还会遍历2,3,4。同样,遍历到3的时候,向上遍历4,向下遍历2,1,等等等。如果数组中有大量的连续数字的话,那么就有大量的重复计算,十分的不高效,所以我们要从HashSet中移除数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
unordered_set<int> s(nums.begin(), nums.end());
int res = 0;
int local_res;
for(int i : nums) {
if (!s.count(i))
continue;
int res1 = 1;
int prev = i-1, next = i+1;
s.erase(i);
while(s.count(prev)) s.erase(prev--);
while(s.count(next)) s.erase(next++);
res = max(res, next-prev-1);
}
return res;
}
};

Leetcode129. Sum Root to Leaf Numbers

Given a binary tree containing digits from 0-9 only, each root-to-leaf path could represent a number.

An example is the root-to-leaf path 1->2->3 which represents the number 123.

Find the total sum of all root-to-leaf numbers.

Note: A leaf is a node with no children.

Example:

1
2
3
4
5
6
7
8
9
Input: [1,2,3]
1
/ \
2 3
Output: 25
Explanation:
The root-to-leaf path 1->2 represents the number 12.
The root-to-leaf path 1->3 represents the number 13.
Therefore, sum = 12 + 13 = 25.

Example 2:
1
2
3
4
5
6
7
8
9
10
11
12
Input: [4,9,0,5,1]
4
/ \
9 0
/ \
5 1
Output: 1026
Explanation:
The root-to-leaf path 4->9->5 represents the number 495.
The root-to-leaf path 4->9->1 represents the number 491.
The root-to-leaf path 4->0 represents the number 40.
Therefore, sum = 495 + 491 + 40 = 1026.

跟1022一样的代码,只是变成了十进制
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 result;
void dfs(TreeNode* root ,int now){
if(!root)
return;
if(!root->left && !root->right){
result += (now*10) + root->val;
}
int val = (now*10) + root->val;
if(root->left)
dfs(root->left, val);
if(root->right)
dfs(root->right, val);
}

int sumNumbers(TreeNode* root) {
result=0;
dfs(root,0);
return result;
}
};

Leetcode130. Surrounded Regions

Given a 2D board containing ‘X’ and ‘O’ (the letter O), capture all regions surrounded by ‘X’. A region is captured by flipping all ‘O’s into ‘X’s in that surrounded region.

Example:

1
2
3
4
5
6
7
8
9
10
X X X X
X O O X
X X O X
X O X X
After running your function, the board should be:

X X X X
X X X X
X X X X
X O X X

Explanation: Surrounded regions shouldn’t be on the border, which means that any ‘O’ on the border of the board are not flipped to ‘X’. Any ‘O’ that is not on the border and it is not connected to an ‘O’ on the border will be flipped to ‘X’. Two cells are connected if they are adjacent cells connected horizontally or vertically.

将包住的O都变成X,但不同的是边缘的O不算被包围,都可以用 DFS 来解。在网上看到大家普遍的做法是扫矩阵的四条边,如果有O,则用 DFS 遍历,将所有连着的O都变成另一个字符,比如 A,这样剩下的O都是被包围的,然后将这些O变成X,把A变回O就行了。

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:
void solve(vector<vector<char>>& board) {
int m = board.size(), n = board[0].size();
for(int i = 0; i < m; i ++)
for(int j = 0; j < n; j ++)
if((i == 0 || i == m-1 || j == 0 || j == n - 1) && board[i][j] == 'O')
dfs(board, i, j);
for(int i = 0; i < m; i ++)
for(int j = 0; j < n; j ++)
if(board[i][j] == 'O')
board[i][j] = 'X';
else if(board[i][j] == 'A')
board[i][j] = 'O';

}

void dfs(vector<vector<char>>& board, int i, int j) {
if(board[i][j] == 'O') {
board[i][j] = 'A';
int dir[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
for(int ii = 0; ii < 4; ii ++) {
int xx = i + dir[ii][0];
int yy = j + dir[ii][1];
if(xx >= 0 && xx < board.size() && yy >= 0 && yy < board[0].size())
dfs(board, xx, yy);
}
}
}

};

Leetcode131. Palindrome Partitioning

Given a string s, partition s such that every substring of the partition is a palindrome. Return all possible palindrome partitioning of s.

Example:

1
2
3
4
5
6
Input: "aab"
Output:
[
["aa","b"],
["a","a","b"]
]

搜索题,用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
class Solution {
public:
vector<vector<string>> partition(string s) {
if(s.length() == 0)
return {};
vector<vector<string>> res;
vector<string> cur;
int index = 0;
dfs(res, cur, s, index);
return res;
}
void dfs(vector<vector<string>>& res, vector<string> cur, string s, int index) {
if(index >= s.length()) {
res.push_back(cur);
return;
}
for(int i = index; i < s.length(); i ++) {
string su = s.substr(index, i - index + 1);
if(isPalidrome(su)) {
cur.push_back(su);
dfs(res, cur, s, i + 1);
cur.pop_back();
}
}
}
bool isPalidrome(string su) {
int len = su.length();
for(int i = 0; i < len / 2; i ++)
if(su[i] != su[len - 1 - i])
return false;
return true;
}
};

Leetcode132. Palindrome Partitioning II

Given a string s, partition s such that every substring of the partition is a palindrome.

Return the minimum cuts needed for a palindrome partitioning of s.

Example 1:

1
2
3
Input: s = "aab"
Output: 1
Explanation: The palindrome partitioning ["aa","b"] could be produced using 1 cut.

Example 2:

1
2
Input: s = "a"
Output: 0

Example 3:

1
2
Input: s = "ab"
Output: 1

Constraints:

  • 1 <= s.length <= 2000
  • s consists of lower-case English letters only.

这道题是让找到把原字符串拆分成回文串的最小切割数,如果首先考虑用 brute force 来做的话就会十分的复杂,因为不但要判断子串是否是回文串,而且还要找出最小切割数,情况会非常的多,不好做。所以对于这种玩字符串且是求极值的题,就要祭出旷古神器动态规划 Dynamic Programming 了,秒天秒地秒空气,DP 在手天下我有。好,吹完一波后,开始做题。DP 解法的两个步骤,定义 dp 数组和找状态转移方程。首先来定义 dp 数组,这里使用最直接的定义方法,一维的 dp 数组,其中 dp[i] 表示子串 [0, i] 范围内的最小分割数,那么最终要返回的就是 dp[n-1] 了,这里先加个 corner case 的判断,若s串为空,直接返回0,OJ 的 test case 中并没有空串的检测,但博主认为还是加上比较好,毕竟空串也算是回文串的一种,所以最小分割数为0也说得过去。接下来就是大难点了,如何找出状态转移方程。

如何更新 dp[i] 呢,前面说过了其表示子串 [0, i] 范围内的最小分割数。那么这个区间的每个位置都可以尝试分割开来,所以就用一个变量j来从0遍历到i,这样就可以把区间 [0, i] 分为两部分,[0, j-1] 和 [j, i],那么 suppose 已经知道区间 [0, j-1] 的最小分割数 dp[j-1],因为是从前往后更新的,而 j 小于等于 i,所以 dp[j-1] 肯定在 dp[i] 之前就已经算出来了。这样就只需要判断区间 [j, i] 内的子串是否为回文串了,是的话,dp[i] 就可以用 1 + dp[j-1] 来更新了。判断子串的方法用的是之前那道 Palindromic Substrings 一样的方法,使用一个二维的 dp 数组p,其中 p[i][j] 表示区间 [i, j] 内的子串是否为回文串,其状态转移方程为 p[i][j] = (s[i] == s[j]) && p[i+1][j-1],其中 p[i][j] = true if [i, j]为回文。这样的话,这道题实际相当于同时用了两个 DP 的方法,确实难度不小呢。

第一个 for 循环遍历的是i,此时先将 dp[i] 初始化为 i,因为对于区间 [0, i],就算每个字母割一刀(怎么听起来像凌迟?!),最多能只用分割 i 次,不需要再多于这个数字。但是可能会变小,所以第二个 for 循环用 j 遍历区间 [0, j],根据上面的解释,需要验证的是区间 [j, i] 内的子串是否为回文串,那么只要 s[j] == s[i],并且 i-j < 2 或者 p[j+1][i-1] 为 true 的话,先更新 p[j][i] 为 true,然后在更新 dp[i],这里需要注意一下 corner case,当 j=0 时,直接给 dp[i] 赋值为0,因为此时能运行到这,说明 [j, i] 区间是回文串,而 j=0, 则说明 [0, i] 区间内是回文串,这样根本不用分割啊。若 j 大于0,则用 dp[j-1] + 1 来更新 dp[i],最终返回 dp[n-1] 即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int minCut(string s) {
if (s.empty()) return 0;
int n = s.size();
vector<vector<bool>> p(n, vector<bool>(n));
vector<int> dp(n);
for (int i = 0; i < n; ++i) {
dp[i] = i;
for (int j = 0; j <= i; ++j) {
if (s[i] == s[j] && (i - j < 2 || p[j + 1][i - 1])) {
p[j][i] = true;
dp[i] = (j == 0) ? 0 : min(dp[i], dp[j - 1] + 1);
}
}
}
return dp[n - 1];
}
};

我们也可以反向推,这里的dp数组的定义就刚好跟前面反过来了,dp[i] 表示区间 [i, n-1] 内的最小分割数,所以最终只需要返回 dp[0] 就是区间 [0, n-1] 内的最喜哦啊分割数了,极为所求。然后每次初始化 dp[i] 为 n-1-i 即可,j 的更新范围是 [i, n),此时就只需要用 1 + dp[j+1] 来更新 dp[i] 了,为了防止越界,需要对 j == n-1 的情况单独处理一下,整个思想跟上面的解法一模一样,请参见之前的讲解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int minCut(string s) {
if (s.empty()) return 0;
int n = s.size();
vector<vector<bool>> p(n, vector<bool>(n));
vector<int> dp(n);
for (int i = n - 1; i >= 0; --i) {
dp[i] = n - i - 1;
for (int j = i; j < n; ++j) {
if (s[i] == s[j] && (j - i <= 1 || p[i + 1][j - 1])) {
p[i][j] = true;
dp[i] = (j == n - 1) ? 0 : min(dp[i], dp[j + 1] + 1);
}
}
}
return dp[0];
}
};

下面这种解法是论坛上的高分解法,没用使用判断区间 [i, j] 内是否为回文串的二维dp数组,节省了空间。但写法上比之前的解法稍微有些凌乱,也算是个 trade-off 吧。这里还是用的一维 dp 数组,不过大小初始化为了 n+1,这样其定义就稍稍发生了些变化,dp[i] 表示由s串中前 i 个字母组成的子串的最小分割数,这样 dp[n] 极为最终所求。接下来就要找状态转移方程了。这道题的更新方式比较特别,跟之前的都不一样,之前遍历 i 的时候,都是更新的 dp[i],这道题更新的却是 dp[i+len+1] 和 dp[i+len+2],其中 len 是以i为中心,总长度为 2len + 1 的回文串,比如 bob,此时 i=1,len=1,或者是i为中心之一,总长度为 2len + 2 的回文串,比如 noon,此时 i=1,len=1。中间两个for循环就是分别更新以 i 为中心且长度为 2len + 1 的奇数回文串,和以 i 为中心之一且长度为 2len + 2 的偶数回文串的。i-len 正好是奇数或者偶数回文串的起始位置,由于我们定义的 dp[i] 是区间 [0, i-1] 的最小分割数,所以 dp[i-len] 就是区间 [0, i-len-1] 范围内的最小分割数,那么加上奇数回文串长度2*len + 1,此时整个区间为 [0, i+len],即需要更新 dp[i+len+1]。如果是加上偶数回文串的长度2*len + 2,那么整个区间为 [0, i+len+1],即需要更新 dp[i+len+2]。这就是分奇偶的状态转移方程,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int minCut(string s) {
if (s.empty()) return 0;
int n = s.size();
vector<int> dp(n + 1, INT_MAX);
dp[0] = -1;
for (int i = 0; i < n; ++i) {
for (int len = 0; i - len >= 0 && i + len < n && s[i - len] == s[i + len]; ++len) {
dp[i + len + 1] = min(dp[i + len + 1], 1 + dp[i - len]);
}
for (int len = 0; i - len >= 0 && i + len + 1 < n && s[i - len] == s[i + len + 1]; ++len) {
dp[i + len + 2] = min(dp[i + len + 2], 1 + dp[i - len]);
}
}
return dp[n];
}
};

Leetcode133. Clone Graph

Given a reference of a node in a connected undirected graph. Return a deep copy (clone) of the graph. Each node in the graph contains a val (int) and a list (List[Node]) of its neighbors.

1
2
3
4
class Node {
public int val;
public List<Node> neighbors;
}

Test case format:

  • For simplicity sake, each node’s value is the same as the node’s index (1-indexed). For example, the first node with val = 1, the second node with val = 2, and so on. The graph is represented in the test case using an adjacency list.

Adjacency list is a collection of unordered lists used to represent a finite graph. Each list describes the set of neighbors of a node in the graph.

The given node will always be the first node with val = 1. You must return the copy of the given node as a reference to the cloned graph.

Example 1:

1
2
3
4
5
6
7
Input: adjList = [[2,4],[1,3],[2,4],[1,3]]
Output: [[2,4],[1,3],[2,4],[1,3]]
Explanation: There are 4 nodes in the graph.
1st node (val = 1)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).
2nd node (val = 2)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).
3rd node (val = 3)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).
4th node (val = 4)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).

这道题目的难点在于如何处理每个结点的 neighbors,由于在深度拷贝每一个结点后,还要将其所有 neighbors 放到一个 vector 中,而如何避免重复拷贝呢?这道题好就好在所有结点值不同,所以我们可以使用 HashMap 来对应原图中的结点和新生成的克隆图中的结点。对于图的遍历的两大基本方法是深度优先搜索 DFS 和广度优先搜索 BFS,这里我们先使用深度优先搜索DFS来解答此题,在递归函数中,首先判空,然后再看当前的结点是否已经被克隆过了,若在 HashMap 中存在,则直接返回其映射结点。否则就克隆当前结点,并在 HashMap 中建立映射,然后遍历当前结点的所有 neihbor 结点,调用递归函数并且加到克隆结点的 neighbors 数组中即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:

Node* dfs(Node* node, unordered_map<Node*, Node*>& ma) {
if(!node)
return NULL;
if(ma.count(node))
return ma[node];
Node* new_node = new Node(node->val);
ma[node] = new_node;
for(Node* t : node->neighbors)
new_node->neighbors.push_back(dfs(t, ma));
return new_node;
}

Node* cloneGraph(Node* node) {
unordered_map<Node*, Node*> ma;
return dfs(node, ma);
}
};

Leetcode134. Gas Station

There are N gas stations along a circular route, where the amount of gas at station i is gas[i].

You have a car with an unlimited gas tank and it costs cost[i] of gas to travel from station i to its next station (i+1). You begin the journey with an empty tank at one of the gas stations.

Return the starting gas station’s index if you can travel around the circuit once in the clockwise direction, otherwise return -1.

Note:

  • If there exists a solution, it is guaranteed to be unique.
  • Both input arrays are non-empty and have the same length.
  • Each element in the input arrays is a non-negative integer.

Example 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Input: 
gas = [1,2,3,4,5]
cost = [3,4,5,1,2]

Output: 3

Explanation:
Start at station 3 (index 3) and fill up with 4 unit of gas. Your tank = 0 + 4 = 4
Travel to station 4. Your tank = 4 - 1 + 5 = 8
Travel to station 0. Your tank = 8 - 2 + 1 = 7
Travel to station 1. Your tank = 7 - 3 + 2 = 6
Travel to station 2. Your tank = 6 - 4 + 3 = 5
Travel to station 3. The cost is 5. Your gas is just enough to travel back to station 3.
Therefore, return 3 as the starting index.

思路: 累加在每个位置的left += gas[i] - cost[i], 就是在每个位置剩余的油量, 如果left一直大于0, 就可以一直走下取. 如果left小于0了, 那么就从下一个位置重新开始计数, 并且将之前欠下的多少记录下来, 如果最终遍历完数组剩下的燃料足以弥补之前不够的, 那么就可以到达, 并返回最后一次开始的位置.否则就返回-1.

证明这种方法的正确性:

  1. 如果从头开始, 每次累计剩下的油量都为整数, 那么没有问题, 他可以从头开到结束.
  2. 如果到中间的某个位置, 剩余的油量为负了, 那么说明之前累积剩下的油量不够从这一站到下一站了. 那么就从下一站从新开始计数. 为什么是下一站, 而不是之前的某站呢? 因为第一站剩余的油量肯定是大于等于0的, 然而到当前一站油量变负了, 说明从第一站之后开始的话到当前油量只会更少而不会增加. 也就是说从第一站之后, 当前站之前的某站出发到当前站剩余的油量是不可能大于0的. 所以只能从下一站重新出发开始计算从下一站开始剩余的油量, 并且把之前欠下的油量也累加起来, 看到最后剩余的油量是不是大于欠下的油量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int start = 0, lack = 0, left = 0;
for(int i = 0; i < gas.size(); i++)
{
left += gas[i] - cost[i];
if(left < 0)
{
start = i+1;
lack += left;
left = 0;
}
}
return left+lack>=0?start:-1;
}
};

Leetcode135. Candy

There are N children standing in a line. Each child is assigned a rating value.

You are giving candies to these children subjected to the following requirements:

  • Each child must have at least one candy.
  • Children with a higher rating get more candies than their neighbors.
  • What is the minimum candies you must give?

这道题看起来很难,其实解法并没有那么复杂,当然我也是看了别人的解法才做出来的,先来看看两遍遍历的解法,首先初始化每个人一个糖果,然后这个算法需要遍历两遍,第一遍从左向右遍历,如果右边的小盆友的等级高,等加一个糖果,这样保证了一个方向上高等级的糖果多。然后再从右向左遍历一遍,如果相邻两个左边的等级高,而左边的糖果又少的话,则左边糖果数为右边糖果数加一。最后再把所有小盆友的糖果数都加起来返回即可。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int candy(vector<int>& ratings) {
int res = 0, n = ratings.size();
vector<int> nums(n, 1);
for (int i = 0; i < n - 1; ++i) {
if (ratings[i + 1] > ratings[i]) nums[i + 1] = nums[i] + 1;
}
for (int i = n - 1; i > 0; --i) {
if (ratings[i - 1] > ratings[i]) nums[i - 1] = max(nums[i - 1], nums[i] + 1);
}
for (int num : nums) res += num;
return res;
}
};

下面来看一次遍历的方法,相比于遍历两次的思路简单明了,这种只遍历一次的解法就稍有些复杂了。首先我们给第一个同学一个糖果,那么对于接下来的一个同学就有三种情况:

  1. 接下来的同学的rating等于前一个同学,那么给接下来的同学一个糖果就行。
  2. 接下来的同学的rating大于前一个同学,那么给接下来的同学的糖果数要比前一个同学糖果数加1。
  3. 接下来的同学的rating小于前一个同学,那么我们此时不知道应该给这个同学多少个糖果,需要看后面的情况。

对于第三种情况,我们不确定要给几个,因为要是只给1个的话,那么有可能接下来还有rating更小的同学,总不能一个都不给吧。也不能直接给前一个同学的糖果数减1,有可能给多了,因为如果后面再没人了的话,其实只要给一个就行了。还有就是,如果后面好几个rating越来越小的同学,那么前一个同学的糖果数可能还得追加,以保证最后面的同学至少能有1个糖果。来一个例子吧,四个同学,他们的rating如下:

1
1 3 2 1

先给第一个rating为1的同学一个糖果,然后从第二个同学开始遍历,第二个同学rating为3,比1大,所以多给一个糖果,第二个同学得到两个糖果。下面第三个同学,他的rating为2,比前一个同学的rating小,如果我们此时给1个糖果的话,那么rating更小的第四个同学就得不到糖果了,所以我们要给第四个同学1个糖果,而给第三个同学2个糖果,此时要给第二个同学追加1个糖果,使其能够比第三个同学的糖果数多至少一个。那么我们就需要统计出多有个连着的同学的rating变小,用变量cnt来记录,找出了最后一个减小的同学,那么就可以往前推,每往前一个加一个糖果,这就是个等差数列,我们可以直接利用求和公式算出这些rating减小的同学的糖果之和。然后我们还要看第一个开始减小的同学的前一个同学需不需要追加糖果,只要比较cnt和pre的大小,pre是之前同学得到的最大糖果数,二者做差加1就是需要追加的糖果数,加到结果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
class Solution {
public:
int candy(vector<int>& ratings) {
if (ratings.empty()) return 0;
int res = 1, pre = 1, cnt = 0;
for (int i = 1; i < ratings.size(); ++i) {
if (ratings[i] >= ratings[i - 1]) {
if (cnt > 0) {
res += cnt * (cnt + 1) / 2;
if (cnt >= pre) res += cnt - pre + 1;
cnt = 0;
pre = 1;
}
pre = (ratings[i] == ratings[i - 1]) ? 1 : pre + 1;
res += pre;
} else {
++cnt;
}
}
if (cnt > 0) {
res += cnt * (cnt + 1) / 2;
if (cnt >= pre) res += cnt - pre + 1;
}
return res;
}
};

Leetcode136. Single Number

Given a non-empty array of integers, every element appears twice except for one. Find that single one.

Note:

  • Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?
1
2
3
4
Example 1:

Input: [2,2,1]
Output: 1
1
2
3
4
Example 2:

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

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。算法应该具有线性时间复杂度。且可以不使用额外空间来实现。

我的:

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

复杂些的:
有一个 n 个元素的数组,除了两个数只出现一次外,其余元素都出现两次,让你找出这两个只出现一次的数分别是几,要求时间复杂度为 O(n) 且再开辟的内存空间固定(与 n 无关)。

示例 :
输入: [1,2,2,1,3,4]
输出: [3,4]

根据前面找一个不同数的思路算法,在这里把所有元素都异或,那么得到的结果就是那两个只出现一次的元素异或的结果。然后,因为这两个只出现一次的元素一定是不相同的,所以这两个元素的二进制形式肯定至少有某一位是不同的,即一个为 0 ,另一个为 1 ,现在需要找到这一位。根据异或的性质 任何一个数字异或它自己都等于 0,得到这个数字二进制形式中任意一个为 1 的位都是我们要找的那一位。再然后,以这一位是 1 还是 0 为标准,将数组的 n 个元素分成两部分。将这一位为 0 的所有元素做异或,得出的数就是只出现一次的数中的一个,将这一位为 1 的所有元素做异或,得出的数就是只出现一次的数中的另一个。这样就解出题目。忽略寻找不同位的过程,总共遍历数组两次,时间复杂度为O(n)。

Leetcode137. Single Number II

Given a non-empty array of integers, every element appears three times except for one, which appears exactly once. Find that single one.

Note: Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?

Example 1:

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

这道题就是除了一个单独的数字之外,数组中其他的数字都出现了三次,还是要利用位操作 Bit Manipulation 来解。可以建立一个 32 位的数字,来统计每一位上1出现的个数,如果某一位上为1的话,那么如果该整数出现了三次,对3取余为0,这样把每个数的对应位都加起来对3取余,最终剩下来的那个数就是单独的数字。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int singleNumber(vector<int>& nums) {
int sum = 0;
int res = 0;
for(int i = 0; i < 32; i ++) {
sum = 0;
for(int j = 0; j < nums.size(); j ++) {
sum += (nums[j] >> i) & 1;
}
res |= ((sum % 3) << i);
}
return res;
}
};

Leetcode138. Copy List with Random Pointer

A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null. Return a deep copy of the list.

The Linked List is represented in the input/output as a list of n nodes. Each node is represented as a pair of [val, random_index] where:

  • val: an integer representing Node.val
  • random_index: the index of the node (range from 0 to n-1) where random pointer points to, or null if it does not point to any node.

Example 1:

1
2
Input: head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
Output: [[7,null],[13,0],[11,4],[10,2],[1,0]]

Example 2:

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

这道链表的深度拷贝题的难点就在于如何处理随机指针的问题,由于每一个节点都有一个随机指针,这个指针可以为空,也可以指向链表的任意一个节点,如果在每生成一个新节点给其随机指针赋值时,都要去遍历原链表的话,OJ 上肯定会超时,所以可以考虑用 HashMap 来缩短查找时间,第一遍遍历生成所有新节点时同时建立一个原节点和新节点的 HashMap,第二遍给随机指针赋值时,查找时间是常数级。

教训就是别想着省空间用unordered_map<int, 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
class Solution {
public:
Node* copyRandomList(Node* head) {
if(!head)
return NULL;
Node* cur = head, *res = new Node(-1), *res_head = res;
unordered_map<Node*, Node*> ma;
while(cur != NULL) {
Node *new_cur = new Node(cur->val);
res->next = new_cur;
res = res->next;
ma[cur] = new_cur;
cur = cur->next;
}

cur = head;
res = res_head->next;
while(cur != NULL) {
if(cur->random != NULL)
res->random = ma[cur->random];
res = res->next;
cur = cur->next;
}
return res_head->next;

}
};

Leetcode139. Word Break

Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, determine if s can be segmented into a space-separated sequence of one or more dictionary words.

Note:

  • The same word in the dictionary may be reused multiple times in the segmentation.
  • You may assume the dictionary does not contain duplicate words.

Example 1:

1
2
3
Input: s = "leetcode", wordDict = ["leet", "code"]
Output: true
Explanation: Return true because "leetcode" can be segmented as "leet code".

Example 2:
1
2
3
4
Input: s = "applepenapple", wordDict = ["apple", "pen"]
Output: true
Explanation: Return true because "applepenapple" can be segmented as "apple pen apple".
Note that you are allowed to reuse a dictionary word.

这道题其实还是一道经典的 DP 题目,也就是动态规划 Dynamic Programming。博主曾经说玩子数组或者子字符串且求极值的题,基本就是 DP 没差了,虽然这道题没有求极值,但是玩子字符串也符合 DP 的状态转移的特点。把一个人的温暖转移到另一个人的胸膛… 咳咳,跑错片场了,那是爱情转移~ 强行拉回,DP 解法的两大难点,定义 dp 数组跟找出状态转移方程,先来看 dp 数组的定义,这里我们就用一个一维的 dp 数组,其中 dp[i] 表示范围 [0, i) 内的子串是否可以拆分,注意这里 dp 数组的长度比s串的长度大1,是因为我们要 handle 空串的情况,我们初始化 dp[0] 为 true,然后开始遍历。注意这里我们需要两个 for 循环来遍历,因为此时已经没有递归函数了,所以我们必须要遍历所有的子串,我们用j把 [0, i) 范围内的子串分为了两部分,[0, j) 和 [j, i),其中范围 [0, j) 就是 dp[j],范围 [j, i) 就是 s.substr(j, i-j),其中 dp[j] 是之前的状态,我们已经算出来了,可以直接取,只需要在字典中查找 s.substr(j, i-j) 是否存在了,如果二者均为 true,将 dp[i] 赋为 true,并且 break 掉,此时就不需要再用j去分 [0, i) 范围了,因为 [0, i) 范围已经可以拆分了。最终我们返回 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
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
vector<int> dp(s.length() + 1, false);
unordered_set<string> se(wordDict.begin(), wordDict.end());

dp[0] = true;
for(int i = 1; i <= s.length(); i ++) {
bool temp = false;
int j;
for(j = 0; j < i; j ++) {
temp = se.count(s.substr(j, i - j)) != 0;
if(temp && dp[j])
break;
}
if(j == i)
dp[i] = false;
else
dp[i] = true;
}

return dp[s.length()];
}
};

Leetcode140. Word Break II

Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, add spaces in s to construct a sentence where each word is a valid dictionary word. Return all such possible sentences.

Note:

  • The same word in the dictionary may be reused multiple times in the segmentation.
  • You may assume the dictionary does not contain duplicate words.

Example 1:

1
2
3
4
5
6
7
8
Input:
s = "catsanddog"
wordDict = ["cat", "cats", "and", "sand", "dog"]
Output:
[
"cats and dog",
"cat sand dog"
]

Example 2:

1
2
3
4
5
6
7
8
9
10
Input:
s = "pineapplepenapple"
wordDict = ["apple", "pen", "applepen", "pine", "pineapple"]
Output:
[
"pine apple pen apple",
"pineapple pen apple",
"pine applepen apple"
]
Explanation: Note that you are allowed to reuse a dictionary word.

Example 3:

1
2
3
4
5
Input:
s = "catsandog"
wordDict = ["cats", "dog", "sand", "and", "cat"]
Output:
[]

这道题是之前那道Word Break 拆分词句的拓展,那道题只让我们判断给定的字符串能否被拆分成字典中的词,而这道题加大了难度,让我们求出所有可以拆分成的情况,就像题目中给的例子所示。

如果就给你一个s和wordDict,不看Output的内容,你会怎么找出结果。比如对于例子1,博主可能会先扫一遍wordDict数组,看有没有单词可以当s的开头,那么我们可以发现cat和cats都可以,比如我们先选了cat,那么此时s就变成了 “sanddog”,我们再在数组里找单词,发现了sand可以,最后剩一个dog,也在数组中,于是一个结果就出来了。然后回到开头选cats的话,那么此时s就变成了 “anddog”,我们再在数组里找单词,发现了and可以,最后剩一个dog,也在数组中,于是另一个结果也就出来了。那么这个查询的方法很适合用递归来实现,因为s改变后,查询的机制并不变,很适合调用递归函数。再者,我们要明确的是,如果不用记忆数组做减少重复计算的优化,那么递归方法跟brute force没什么区别,大概率无法通过OJ。所以我们要避免重复计算,如何避免呢,还是看上面的分析,如果当s变成 “sanddog”的时候,那么此时我们知道其可以拆分成sand和dog,当某个时候如果我们又遇到了这个 “sanddog”的时候,我们难道还需要再调用递归算一遍吗,当然不希望啦,所以我们要将这个中间结果保存起来,由于我们必须要同时保存s和其所有的拆分的字符串,那么可以使用一个HashMap,来建立二者之间的映射,那么在递归函数中,我们首先检测当前s是否已经有映射,有的话直接返回即可,如果s为空了,我们如何处理呢,题目中说了给定的s不会为空,但是我们递归函数处理时s是会变空的,这时候我们是直接返回空集吗,这里有个小trick,我们其实放一个空字符串返回,为啥要这么做呢?我们观察题目中的Output,发现单词之间是有空格,而最后一个单词后面没有空格,所以这个空字符串就起到了标记当前单词是最后一个,那么我们就不要再加空格了。接着往下看,我们遍历wordDict数组,如果某个单词是s字符串中的开头单词的话,我们对后面部分调用递归函数,将结果保存到rem中,然后遍历里面的所有字符串,和当前的单词拼接起来,这里就用到了我们前面说的trick。for循环结束后,记得返回结果res之前建立其和s之间的映射,方便下次使用,参见代码如下:

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
vector<string> wordBreak(string s, vector<string>& wordDict) {
unordered_map<string, vector<string>> m;
return helper(s, wordDict, m);
}
vector<string> helper(string s, vector<string>& wordDict, unordered_map<string, vector<string>>& m) {
if (m.count(s)) return m[s];
if (s.empty()) return {""};
vector<string> res;
for (string word : wordDict) {
if (s.substr(0, word.size()) != word) continue;
vector<string> rem = helper(s.substr(word.size()), wordDict, m);
for (string str : rem) {
res.push_back(word + (str.empty() ? "" : " ") + str);
}
}
return m[s] = res;
}
};

Leetcode141. Linked List Cycle

Given a linked list, determine if it has a cycle in it.

To represent a cycle in the given linked list, we use an integer pos which represents the position (0-indexed) in the linked list where tail connects to. If pos is -1, then there is no cycle in the linked list.

Example 1:

1
2
3
Input: head = [3,2,0,-4], pos = 1
Output: true
Explanation: There is a cycle in the linked list, where tail connects to the second node.


Example 2:
1
2
3
Input: head = [1,2], pos = 0
Output: true
Explanation: There is a cycle in the linked list, where tail connects to the first node.


Example 3:
1
2
3
Input: head = [1], pos = -1
Output: false
Explanation: There is no cycle in the linked list.

快慢指针判断链表是否有环。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode *fast = head, *slow = head;
while(slow && fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
return true;
}
return false;
}
};

Leetcode142. Linked List Cycle II

Given a linked list, return the node where the cycle begins. If there is no cycle, return null.

To represent a cycle in the given linked list, we use an integer pos which represents the position (0-indexed) in the linked list where tail connects to. If pos is -1, then there is no cycle in the linked list.

Note: Do not modify the linked list.

Example 1:

1
2
3
Input: head = [3,2,0,-4], pos = 1
Output: tail connects to node index 1
Explanation: There is a cycle in the linked list, where tail connects to the second node.

Example 2:

1
2
3
Input: head = [1,2], pos = 0
Output: tail connects to node index 0
Explanation: There is a cycle in the linked list, where tail connects to the first node.

这个求单链表中的环的起始点是之前那个判断单链表中是否有环的延伸,可参之前那道 Linked List Cycle。这里还是要设快慢指针,不过这次要记录两个指针相遇的位置,当两个指针相遇了后,让其中一个指针从链表头开始,此时再相遇的位置就是链表中环的起始位置,为啥是这样呢,因为快指针每次走2,慢指针每次走1,快指针走的距离是慢指针的两倍。而快指针又比慢指针多走了一圈。所以 head 到环的起点+环的起点到他们相遇的点的距离 与 环一圈的距离相等。现在重新开始,head 运行到环起点 和 相遇点到环起点 的距离也是相等的,相当于他们同时减掉了 环的起点到他们相遇的点的距离。

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 *detectCycle(ListNode *head) {
ListNode *fast = head, *slow = head;
int count = 0;
while(fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
break;
}

if(!fast || !fast->next)
return NULL;

slow = head;
while(slow != fast) {
slow = slow->next;
fast = fast->next;
}

return fast;
}
};

分析

复杂度O(n^2)的方法,使用两个指针a, b。a从表头开始一步一步往前走,遇到null则说明没有环,返回false;a每走一步,b从头开始走,如果遇到b==a.next,则说明有环true,如果遇到b==a,则说明暂时没有环,继续循环。

后来找到了复杂度O(n)的方法,使用两个指针slow,fast。两个指针都从表头开始走,slow每次走一步,fast每次走两步,如果fast遇到null,则说明没有环,返回false;如果slow==fast,说明有环,并且此时fast超了slow一圈,返回true。

为什么有环的情况下二者一定会相遇呢?因为fast先进入环,在slow进入之后,如果把slow看作在前面,fast在后面每次循环都向slow靠近1,所以一定会相遇,而不会出现fast直接跳过slow的情况。

扩展问题

在网上搜集了一下这个问题相关的一些问题,思路开阔了不少,总结如下:

  1. 环的长度是多少?
  2. 如何找到环中第一个节点(即Linked List Cycle II)?
  3. 如何将有环的链表变成单链表(解除环)?
  4. 如何判断两个单链表是否有交点?如何找到第一个相交的节点?

首先我们看下面这张图:

设:链表头是X,环的第一个节点是Y,slow和fast第一次的交点是Z。各段的长度分别是a,b,c,如图所示。环的长度是L。slow和fast的速度分别是qs,qf。下面我们来挨个问题分析。

      • 方法一(网上都是这个答案):第一次相遇后,让slow,fast继续走,记录到下次相遇时循环了几次。因为当fast第二次到达Z点时,fast走了一圈,slow走了半圈,而当fast第三次到达Z点时,fast走了两圈,slow走了一圈,正好还在Z点相遇。
      • 方法二:第一次相遇后,让fast停着不走了,slow继续走,记录到下次相遇时循环了几次。
      • 方法三(最简单): 第一次相遇时slow走过的距离:a+b,fast走过的距离:a+b+c+b。因为fast的速度是slow的两倍,所以fast走的距离是slow的两倍,有 2(a+b) = a+b+c+b,可以得到a=c(这个结论很重要!)。我们发现L=b+c=a+b,也就是说,从一开始到二者第一次相遇,循环的次数就等于环的长度。
    1. 我们已经得到了结论a=c,那么让两个指针分别从X和Z开始走,每次走一步,那么正好会在Y相遇!也就是环的第一个节点。
    1. 在上一个问题的最后,将c段中Y点之前的那个节点与Y的链接切断即可。
    1. 如何判断两个单链表是否有交点?先判断两个链表是否有环,如果一个有环一个没环,肯定不相交;如果两个都没有环,判断两个列表的尾部是否相等;如果两个都有环,判断一个链表上的Z点是否在另一个链表上。
      • 如何找到第一个相交的节点?求出两个链表的长度L1,L2(如果有环,则将Y点当做尾节点来算),假设L1<L2,用两个指针分别从两个链表的头部开始走,长度为L2的链表先走(L2-L1)步,然后两个一起走,直到二者相遇。

Leetcode 143. Reorder List

Given a singly linked list L: L0→L1→…→Ln-1→Ln, reorder it to: L0→Ln→L1→Ln-1→L2→Ln-2→…. You may not modify the values in the list’s nodes, only nodes itself may be changed.

Example 1:

1
Given 1->2->3->4, reorder it to 1->4->2->3.

Example 2:
1
Given 1->2->3->4->5, reorder it to 1->5->2->4->3.

分为三步:

  1. 找中点 (快慢指针)
  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
25
26
27
28
29
30
31
32
33
34
35
class Solution {
public:

ListNode* reverse(ListNode *head){
ListNode *prev = NULL, *cur = head;
while(cur != NULL) {
ListNode *tmp = cur->next;
cur->next = prev;
prev = cur;
cur = tmp;
}
return prev;
}

void reorderList(ListNode* head) {
if(head == NULL)
return;
ListNode *slow = head, *fast = head;
while(fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
}
// fast is tail, slow is 1/2 of list
ListNode *head2 = reverse(slow->next);
slow->next = NULL;
while(head != NULL && head2 != NULL) {
ListNode *tmp1 = head->next;
ListNode *tmp2 = head2->next;
head2->next = head->next;
head->next = head2;
head = tmp1;
head2 = tmp2;
}
}
};

Leetcode144. Binary Tree Preorder Traversal

Given a binary tree, return the preorder traversal of its nodes’ values.

Example:

1
2
3
4
5
6
7
8
Input: [1,null,2,3]
1
\
2
/
3

Output: [1,2,3]

题目的要求是不能使用递归求解,于是只能考虑到用非递归的方法,这就要用到stack来辅助运算。由于先序遍历的顺序是”根-左-右”, 算法为:

  1. 把根节点 push 到栈中
  2. 循环检测栈是否为空,若不空,则取出栈顶元素,保存其值,然后看其右子节点是否存在,若存在则 push 到栈中。再看其左子节点,若存在,则 push 到栈中。

这道题不配为medium,

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> preorderTraversal(TreeNode* root) {
if(root == NULL)
return {};
stack<TreeNode*> s;
s.push(root);
vector<int> res;
while(!s.empty()) {
TreeNode* tmp = s.top();
s.pop();
res.push_back(tmp->val);
if(tmp->right)
s.push(tmp->right);
if(tmp->left)
s.push(tmp->left);
}
return res;
}
};

Leetcode145. Binary Tree Postorder Traversal

Given the root of a binary tree, return the postorder traversal of its nodes’ values.

后序遍历

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

void add(vector<int>& res, TreeNode* root) {
if (root == NULL)
return ;
if (root->left)
add(res, root->left);
if (root->right)
add(res, root->right);
res.push_back(root->val);
}

vector<int> postorderTraversal(TreeNode* root) {
if (root == NULL)
return {};
vector<int> res;
add(res, root);
return res;
}
};

Leetcode146. LRU Cache

Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and put.

  • get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
  • put(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.

The cache is initialized with a positive capacity.

Follow up:
Could you do both operations in O(1) time complexity?

Example:

1
2
3
4
5
6
7
8
9
10
11
LRUCache cache = new LRUCache( 2 /* capacity */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // returns 1
cache.put(3, 3); // evicts key 2
cache.get(2); // returns -1 (not found)
cache.put(4, 4); // evicts key 1
cache.get(1); // returns -1 (not found)
cache.get(3); // returns 3
cache.get(4); // returns 4

这道题让我们实现一个 LRU 缓存器,LRU 是 Least Recently Used 的简写,就是最近最少使用的意思。那么这个缓存器主要有两个成员函数,get 和 put,其中 get 函数是通过输入 key 来获得 value,如果成功获得后,这对 (key, value) 升至缓存器中最常用的位置(顶部),如果 key 不存在,则返回 -1。而 put 函数是插入一对新的 (key, value),如果原缓存器中有该 key,则需要先删除掉原有的,将新的插入到缓存器的顶部。如果不存在,则直接插入到顶部。若加入新的值后缓存器超过了容量,则需要删掉一个最不常用的值,也就是底部的值。具体实现时我们需要三个私有变量,cap, l和m,其中 cap 是缓存器的容量大小,l是保存缓存器内容的列表,m是 HashMap,保存关键值 key 和缓存器各项的迭代器之间映射,方便我们以 O(1) 的时间内找到目标项。

然后我们再来看 get 和 put 如何实现,get 相对简单些,我们在 HashMap 中查找给定的 key,若不存在直接返回 -1。如果存在则将此项移到顶部,这里我们使用 C++ STL 中的函数 splice,专门移动链表中的一个或若干个结点到某个特定的位置,这里我们就只移动 key 对应的迭代器到列表的开头,然后返回 value。这里再解释一下为啥 HashMap 不用更新,因为 HashMap 的建立的是关键值 key 和缓存列表中的迭代器之间的映射,get 函数是查询函数,如果关键值 key 不在 HashMap,那么不需要更新。如果在,我们需要更新的是该 key-value 键值对儿对在缓存列表中的位置,而 HashMap 中还是这个 key 跟键值对儿的迭代器之间的映射,并不需要更新什么。

对于 put,我们也是现在 HashMap 中查找给定的 key,如果存在就删掉原有项,并在顶部插入新来项,然后判断是否溢出,若溢出则删掉底部项(最不常用项)。

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 LRUCache{
public:
LRUCache(int capacity) {
cap = capacity;
}

int get(int key) {
auto it = m.find(key);
if (it == m.end()) return -1;
l.splice(l.begin(), l, it->second);
return it->second->second;
}

void put(int key, int value) {
auto it = m.find(key);
if (it != m.end()) l.erase(it->second);
l.push_front(make_pair(key, value));
m[key] = l.begin();
if (m.size() > cap) {
int k = l.rbegin()->first;
l.pop_back();
m.erase(k);
}
}

private:
int cap;
list<pair<int, int>> l;
unordered_map<int, list<pair<int, int>>::iterator> m;
};

Leetcode147. Insertion Sort List

Sort a linked list using insertion sort.

A graphical example of insertion sort. The partial sorted list (black) initially contains only the first element in the list.
With each iteration one element (red) is removed from the input data and inserted in-place into the sorted list

Algorithm of Insertion Sort:

  • Insertion sort iterates, consuming one input element each repetition, and growing a sorted output list.
  • At each iteration, insertion sort removes one element from the input data, finds the location it belongs within the sorted list, and inserts it there.
  • It repeats until no input elements remain.

Example 1:

1
2
Input: 4->2->1->3
Output: 1->2->3->4

Example 2:
1
2
Input: -1->5->3->4->0
Output: -1->0->3->4->5

使用插入排序来排序链表。可以按照数组中的思路来做,先将创建一个新的链表,然后每次将原来的链表中的第一个节点拿出来与新的链表做对比,需要比较head的值和新建链表中每个值,直到找到合适的位置插入即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
ListNode* insertionSortList(ListNode* head) {
if(!head || !head->next)
return head;
ListNode* ress = new ListNode(-1);
while(head != NULL) {
ListNode *res = ress;
ListNode *tmp = head->next;
while(res->next && res->next->val <= head->val) {
res = res->next;
}
head->next = res->next;
res->next = head;
head = tmp;
}
return ress->next;
}
};

Leetcode148. Sort List

Sort a linked list in O(n log n) time using constant space complexity.

Example 1:

1
2
Input: 4->2->1->3
Output: 1->2->3->4

Example 2:
1
2
Input: -1->5->3->4->0
Output: -1->0->3->4->5

常见排序方法有很多,插入排序,选择排序,堆排序,快速排序,冒泡排序,归并排序,桶排序等等。。它们的时间复杂度不尽相同,而这里题目限定了时间必须为O(nlgn),符合要求只有快速排序,归并排序,堆排序,而根据单链表的特点,最适于用归并排序。为啥呢?这是由于链表自身的特点决定的,由于不能通过坐标来直接访问元素,所以快排什么的可能不太容易实现(但是被评论区的大神们打脸,还是可以实现的),堆排序的话,如果让新建结点的话,还是可以考虑的,若只能交换结点,最好还是不要用。而归并排序(又称混合排序)因其可以利用递归来交换数字,天然适合链表这种结构。归并排序的核心是一个 merge() 函数,其主要是合并两个有序链表,这个在 LeetCode 中也有单独的题目 Merge Two Sorted Lists。由于两个链表是要有序的才能比较容易 merge,那么对于一个无序的链表,如何才能拆分成有序的两个链表呢?我们从简单来想,什么时候两个链表一定都是有序的?就是当两个链表各只有一个结点的时候,一定是有序的。而归并排序的核心其实是分治法 Divide and Conquer,就是将链表从中间断开,分成两部分,左右两边再分别调用排序的递归函数 sortList(),得到各自有序的链表后,再进行 merge(),这样整体就是有序的了。因为子链表的递归函数中还是会再次拆成两半,当拆到链表只有一个结点时,无法继续拆分了,而这正好满足了前面所说的“一个结点的时候一定是有序的”,这样就可以进行 merge 了。然后再回溯回去,每次得到的都是有序的链表,然后进行 merge,直到还原整个长度。这里将链表从中间断开的方法,采用的就是快慢指针,大家可能对快慢指针找链表中的环比较熟悉,其实找链表中的中点同样好使,因为快指针每次走两步,慢指针每次走一步,当快指针到达链表末尾时,慢指针正好走到中间位置。
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:
ListNode* sortList(ListNode* head) {
if (!head || !head->next)
return head;
ListNode *slow = head, *fast = head, *prev = head;
while(fast && fast->next) {
prev = slow;
slow = slow->next;
fast = fast->next->next;
}
prev->next = NULL;
return merge(sortList(head), sortList(slow));
}

ListNode* merge(ListNode *first, ListNode *second) {
ListNode *prev = new ListNode(-1);
ListNode *cur = prev;
while(first && second) {
if(first->val < second->val) {
cur->next = first;
first = first->next;
}
else {
cur->next = second;
second = second->next;
}
cur = cur->next;
}
if(first)
cur->next = first;
if(second)
cur->next = second;
return prev->next;
}

};

Leetcode 150. Evaluate Reverse Polish Notation

Evaluate the value of an arithmetic expression in Reverse Polish Notation. Valid operators are +, -, *, /. Each operand may be an integer or another expression.

Note:

  • Division between two integers should truncate toward zero.
  • The given RPN expression is always valid. That means the expression would always evaluate to a result and there won’t be any divide by zero operation.

Example 1:

1
2
3
Input: ["2", "1", "+", "3", "*"]
Output: 9
Explanation: ((2 + 1) * 3) = 9

Example 2:
1
2
3
Input: ["4", "13", "5", "/", "+"]
Output: 6
Explanation: (4 + (13 / 5)) = 6

Example 3:
1
2
3
4
5
6
7
8
9
10
Input: ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"]
Output: 22
Explanation:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22

逆波兰表达式就是把操作数放前面,把操作符后置的一种写法,从前往后遍历数组,遇到数字则压入栈中,遇到符号,则把栈顶的两个数字拿出来运算,把结果再压入栈中,直到遍历完整个数组,栈顶数字即为最终答案。
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 evalRPN(vector<string>& tokens) {
if(tokens.size() == 1)
return stoi(tokens[0]);
stack<int> s;
for(int i = 0; i < tokens.size(); i ++) {
if (tokens[i] != "+" && tokens[i] != "-" && tokens[i] != "*" && tokens[i] != "/") {
s.push(stoi(tokens[i]));
}
else {
int a1 = s.top(); s.pop();
int a2 = s.top(); s.pop();
if (tokens[i] == "+") s.push(a2 + a1);
if (tokens[i] == "-") s.push(a2 - a1);
if (tokens[i] == "*") s.push(a2 * a1);
if (tokens[i] == "/") s.push(a2 / a1);
}
}
return s.top();
}
};

Leetcode151. Reverse Words in a String

Given an input string, reverse the string word by word.

Example 1:

1
2
Input: "the sky is blue"
Output: "blue is sky the"

Example 2:
1
2
3
Input: "  hello world!  "
Output: "world! hello"
Explanation: Your reversed string should not contain leading or trailing spaces.

Example 3:
1
2
3
Input: "a good   example"
Output: "example good a"
Explanation: You need to reduce multiple spaces between two words to a single space in the reversed string.

我自己的复杂度超级高:

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 reverseWords(string s) {
if(s == "")
return "";
string ans = "";
int p2 = s.length()-1;
while(p2 >= 0) {
string temp;
while(p2 >= 0 && s[p2] == ' ') {
p2 --;
}
while(p2 >= 0 && s[p2] != ' ')
temp = s[p2--] + temp;
ans = ans + temp + " ";
p2 --;
}
int i, j;
for(i = ans.length()-1, j = 0; i >= 0; i --)
if(ans[i] == ' ')
j ++;
else
break;
ans = ans.substr(0, ans.length()-j);
return ans;
}
};

先反转,然后逐个单词反转。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Solution {
public String reverseWords(String s) {
StringBuilder sb = new StringBuilder(" " + s + " ");
sb.reverse();
StringBuilder result = new StringBuilder();
int begin = 0;
for (int i = 0; i < sb.length() - 1; i++) {
char c1 = sb.charAt(i);
char c2 = sb.charAt(i + 1);
if (c1 == ' ' && c2 != ' ') {
begin = i;
} else if (c1 != ' ' && c2 == ' ') {
for (int j = i; j >= begin; j--) {
result.append(sb.charAt(j));
}
}
}

return result.toString().trim();
}
}

Leetcode152. Maximum Product Subarray

Given an integer array nums, find the contiguous subarray within an array (containing at least one number) which has the largest product.

Example 1:

1
2
3
Input: [2,3,-2,4]
Output: 6
Explanation: [2,3] has the largest product 6.

Example 2:
1
2
3
Input: [-2,0,-1]
Output: 0
Explanation: The result cannot be 2, because [-2,-1] is not a subarray.

这道题属于动态规划的题型,之前常见的是Maximum SubArray,现在是Product Subarray,不过思想是一致的。
当然不用动态规划,常规方法也是可以做的,但是时间复杂度过高(TimeOut),像下面这种形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 思路:用两个指针来指向字数组的头尾
int maxProduct(int A[], int n)
{
assert(n > 0);
int subArrayProduct = -32768;

for (int i = 0; i != n; ++ i) {
int nTempProduct = 1;
for (int j = i; j != n; ++ j) {
if (j == i)
nTempProduct = A[i];
else
nTempProduct *= A[j];
if (nTempProduct >= subArrayProduct)
subArrayProduct = nTempProduct;
}
}
return subArrayProduct;
}

用动态规划的方法,就是要找到其转移方程式,也叫动态规划的递推式,动态规划的解法无非是维护两个变量,局部最优和全局最优,我们先来看Maximum SubArray的情况,如果遇到负数,相加之后的值肯定比原值小,但可能比当前值大,也可能小,所以,对于相加的情况,只要能够处理局部最大和全局最大之间的关系即可,对此,写出转移方程式如下:
1
2
local[i + 1] = Max(local[i] + A[i], A[i]);
global[i + 1] = Max(local[i + 1], global[i]);

对应代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int maxSubArray(int A[], int n)
{
assert(n > 0);
if (n <= 0)
return 0;
int global = A[0];
int local = A[0];

for(int i = 1; i != n; ++ i) {
local = MAX(A[i], local + A[i]);
global = MAX(local, global);
}
return global;
}

而对于Product Subarray,要考虑到一种特殊情况,即负数和负数相乘:如果前面得到一个较小的负数,和后面一个较大的负数相乘,得到的反而是一个较大的数,如{2,-3,-7},所以,我们在处理乘法的时候,除了需要维护一个局部最大值,同时还要维护一个局部最小值,由此,可以写出如下的转移方程式:
1
2
3
max_copy[i] = max_local[i]
max_local[i + 1] = Max(Max(max_local[i] * A[i], A[i]), min_local * A[i])
min_local[i + 1] = Min(Min(max_copy[i] * A[i], A[i]), min_local * A[i])

对应代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#define MAX(x,y) ((x)>(y)?(x):(y))
#define MIN(x,y) ((x)<(y)?(x):(y))

int maxProduct1(int A[], int n)
{
assert(n > 0);
if (n <= 0)
return 0;

if (n == 1)
return A[0];
int max_local = A[0];
int min_local = A[0];

int global = A[0];
for (int i = 1; i != n; ++ i) {
int max_copy = max_local;
max_local = MAX(MAX(A[i] * max_local, A[i]), A[i] * min_local);
min_local = MIN(MIN(A[i] * max_copy, A[i]), A[i] * min_local);
global = MAX(global, max_local);
}
return global;
}

总结:动态规划题最核心的步骤就是要写出其状态转移方程,但是如何写出正确的方程式,需要我们不断的实践并总结才能达到。

Leetcode153. Find Minimum 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]).

Find the minimum element.

You may assume no duplicate exists in the array.

Example 1:

1
2
Input: [3,4,5,1,2] 
Output: 1

Example 2:
1
2
Input: [4,5,6,7,0,1,2]
Output: 0

没有固定的 target 值比较,而是要跟数组中某个特定位置上的数字比较,决定接下来去哪一边继续搜索。这里用中间的值 nums[mid] 和右边界值 nums[right] 进行比较,若数组没有旋转或者旋转点在左半段的时候,中间值是一定小于右边界值的,所以要去左半边继续搜索,反之则去右半段查找,最终返回 nums[right] 即可,参见代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
int findMin(vector<int>& nums) {
int m = 0, n = nums.size()-1;
while(m < n) {
int mid = m + (n - m) / 2;
if(nums[mid] > nums[n])
m = mid + 1;
else
n = mid;
}
return nums[n];
}
};

Leetcode154. Find Minimum in Rotated Sorted Array II

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]).

Find the minimum element.

The array may contain duplicates.

Example 1:

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

Example 2:

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

当数组中存在大量的重复数字时,就会破坏二分查找法的机制,将无法取得 O(lgn) 的时间复杂度,又将会回到简单粗暴的 O(n),比如这两种情况:{2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 1, 2} 和 {2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2},可以发现,当第一个数字和最后一个数字,还有中间那个数字全部相等的时候,二分查找法就崩溃了,因为它无法判断到底该去左半边还是右半边。这种情况下,将右指针左移一位(或者将左指针右移一位),略过一个相同数字,这对结果不会产生影响,因为只是去掉了一个相同的,然后对剩余的部分继续用二分查找法,在最坏的情况下,比如数组所有元素都相同,时间复杂度会升到 O(n),参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int findMin(vector<int>& nums) {
int left = 0, right = (int)nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] > nums[right]) left = mid + 1;
else if (nums[mid] < nums[right]) right = mid;
else --right;
}
return nums[right];
}
};

还是可以用分治法 Divide and Conquer 来解,只有在 nums[start] < nums[end] 的时候,才能返回 nums[start],等于的时候不能返回,比如 [3, 1, 3] 这个数组,或者当 start 等于 end 成立的时候,也可以直接返回 nums[start],后面的操作跟之前那道题相同,每次将区间 [start, end] 从中间 mid 位置分为两段,分别调用递归函数,并比较返回值,每次取返回值较小的那个即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int findMin(vector<int>& nums) {
return helper(nums, 0, (int)nums.size() - 1);
}
int helper(vector<int>& nums, int start, int end) {
if (start == end) return nums[start];
if (nums[start] < nums[end]) return nums[start];
int mid = (start + end) / 2;
return min(helper(nums, start, mid), helper(nums, mid + 1, end));
}
};

Leecode155. Min Stack

Design a stack that supports push, pop, top, and retrieving the minimum element in constant time.

  • push(x) — Push element x onto stack.
  • pop() — Removes the element on top of the stack.
  • top() — Get the top element.
  • getMin() — Retrieve the minimum element in the stack.

Example 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Input
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]

Output
[null,null,null,null,-3,null,0,-2]

Explanation
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); // return -3
minStack.pop();
minStack.top(); // return 0
minStack.getMin(); // return -2

Constraints: Methods pop, top and getMin operations will always be called on non-empty stacks.

这道最小栈跟原来的栈相比就是多了一个功能,可以返回该栈的最小值。使用两个栈来实现,一个栈来按顺序存储 push 进来的数据,另一个用来存出现过的最小值。代码如下:

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 MinStack {
public:
stack<int> s;
stack<int> s_min;

MinStack() { }

void push(int x) {
s.push(x);
if(s_min.empty() || s_min.top() >= x)
s_min.push(x);
}

void pop() {
int temp = s.top();
s.pop();
if(!s_min.empty() && temp == s_min.top())
s_min.pop();
}

int top() {
return s.top();
}

int getMin() {
return s_min.top();
}
};

Leetcode157. read N Characters Given Read4

因为是付费的题目,所以只贴一下题解就算了。

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
/********************************************************************************** 
*
* The API: int read4(char *buf) reads 4 characters at a time from a file.
*
* The return value is the actual number of characters read.
* For example, it returns 3 if there is only 3 characters left in the file.
*
* By using the read4 API, implement the function int read(char *buf, int n)
* that reads n characters from the file.
*
* Note:
* The read function will only be called once for each test case.
*
**********************************************************************************/

// Forward declaration of the read4 API.
int read4(char *buf);

class Solution {
public:
/**
* @param buf Destination buffer
* @param n Maximum number of characters to read
* @return The number of characters read
*/
int read(char *buf, int n) {
srand(time(0));
if (rand()%2){
return read1(buf, n);
}
return read2(buf, n);
}

//read the data in-place. potential out-of-boundary issue
int read1(char *buf, int n) {
int len = 0;
int size = 0;

// `buf` could be accessed out-of-boundary
while(len <=n && (size = read4(buf))>0){
size = len + size > n ? n - len : size;
len += size;
buf += size;
}
return len;
}

//using a temp-buffer to avoid peotential out-of-boundary issue
int read2(char *buf, int n) {
char _buf[4]; // the buffer for read4()
int _n = 0; // the return for read4()
int len = 0; // total buffer read from read4()
int size = 0; // how many bytes need be copied from `_buf` to `buf`
while((_n = read4(_buf)) > 0){
//check the space of `buf` whether full or not
size = len + _n > n ? n-len : _n;
strncpy(buf+len, _buf, size);
len += size;
//buffer is full
if (len>=n){
break;
}
}
return len;
}
};

Leetcode159. Longest Substring with At Most Two Distinct Characters

Given a string s , find the length of the longest substring t that contains at most 2 distinct characters.

Example 1:

1
2
3
Input: "eceba"
Output: 3
Explanation: _t_ is "ece" which its length is 3.

Example 2:

1
2
3
Input: "ccaabbb"
Output: 5
Explanation: _t_ is "aabbb" which its length is 5.

这道题给我们一个字符串,让求最多有两个不同字符的最长子串。那么首先想到的是用 HashMap 来做,HashMap 记录每个字符的出现次数,然后如果 HashMap 中的映射数量超过两个的时候,这里需要删掉一个映射,比如此时 HashMap 中e有2个,c有1个,此时把b也存入了 HashMap,那么就有三对映射了,这时 left 是0,先从e开始,映射值减1,此时e还有1个,不删除,left 自增1。这时 HashMap 里还有三对映射,此时 left 是1,那么到c了,映射值减1,此时e映射为0,将e从 HashMap 中删除,left 自增1,然后更新结果为 i - left + 1,以此类推直至遍历完整个字符串,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int lengthOfLongestSubstringTwoDistinct(string s) {
int res = 0, left = 0;
unordered_map<char, int> m;
for (int i = 0; i < s.size(); ++i) {
++m[s[i]];
while (m.size() > 2) {
if (--m[s[left]] == 0) m.erase(s[left]);
++left;
}
res = max(res, i - left + 1);
}
return res;
}
};

我们除了用 HashMap 来映射字符出现的个数,还可以映射每个字符最新的坐标,比如题目中的例子 “eceba”,遇到第一个e,映射其坐标0,遇到c,映射其坐标1,遇到第二个e时,映射其坐标2,当遇到b时,映射其坐标3,每次都判断当前 HashMap 中的映射数,如果大于2的时候,那么需要删掉一个映射,还是从 left=0 时开始向右找,看每个字符在 HashMap 中的映射值是否等于当前坐标 left,比如第一个e,HashMap 此时映射值为2,不等于 left 的0,那么 left 自增1,遇到c的时候,HashMap 中c的映射值是1,和此时的 left 相同,那么我们把c删掉,left 自增1,再更新结果,以此类推直至遍历完整个字符串,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int lengthOfLongestSubstringTwoDistinct(string s) {
int res = 0, left = 0;
unordered_map<char, int> m;
for (int i = 0; i < s.size(); ++i) {
m[s[i]] = i;
while (m.size() > 2) {
if (m[s[left]] == left) m.erase(s[left]);
++left;
}
res = max(res, i - left + 1);
}
return res;
}
};

后来又在网上看到了一种解法,这种解法是维护一个 sliding window,指针 left 指向起始位置,right 指向 window 的最后一个位置,用于定位 left 的下一个跳转位置,思路如下:

  • 若当前字符和前一个字符相同,继续循环。
  • 若不同,看当前字符和 right 指的字符是否相同
    • 若相同,left 不变,右边跳到 i - 1
    • 若不同,更新结果,left 变为 right+1,right 变为 i - 1

最后需要注意在循环结束后,还要比较结果 res 和 s.size() - left 的大小,返回大的,这是由于如果字符串是 “ecebaaa”,那么当 left=3 时,i=5,6 的时候,都是继续循环,当i加到7时,跳出了循环,而此时正确答案应为 “baaa” 这4个字符,而我们的结果 res 只更新到了 “ece” 这3个字符,所以最后要判断 s.size() - left 和结果 res 的大小。

另外需要说明的是这种解法仅适用于于不同字符数为2个的情况,如果为k个的话,还是需要用上面两种解法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int lengthOfLongestSubstringTwoDistinct(string s) {
int left = 0, right = -1, res = 0;
for (int i = 1; i < s.size(); ++i) {
if (s[i] == s[i - 1]) continue;
if (right >= 0 && s[right] != s[i]) {
res = max(res, i - left);
left = right + 1;
}
right = i - 1;
}
return max(s.size() - left, res);
}
};

Leetcode160. Intersection of Two Linked Lists

Write a program to find the node at which the intersection of two singly linked lists begins.

Example 1:

1
2
3
Input: intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
Output: Reference of the node with value = 8
Input Explanation: The intersected node's value is 8 (note that this must not be 0 if the two lists intersect). From the head of A, it reads as [4,1,8,4,5]. From the head of B, it reads as [5,0,1,8,4,5]. There are 2 nodes before the intersected node in A; There are 3 nodes before the intersected node in B.


Example 2:
1
2
3
Input: intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
Output: Reference of the node with value = 2
Input Explanation: The intersected node's value is 2 (note that this must not be 0 if the two lists intersect). From the head of A, it reads as [0,9,1,2,4]. From the head of B, it reads as [3,2,4]. There are 3 nodes before the intersected node in A; There are 1 node before the intersected node in B.


Example 3:
1
2
3
4
Input: intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
Output: null
Input Explanation: From the head of A, it reads as [2,6,4]. From the head of B, it reads as [1,5]. Since the two lists do not intersect, intersectVal must be 0, while skipA and skipB can be arbitrary values.
Explanation: The two lists do not intersect, so return null.

简单做了做,没明白题意,就是求两个链表的交。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *pa = headA, *pb = headB;
while(pa != pb) {
if(pa != NULL)
pa = pa->next;
else
pa = headB;
if(pb != NULL)
pb = pb->next;
else
pb = headA;
}
return pa;
}
};

解题思路:

方法一

先统计两个链表的长度,再遍历较长的链表,使两个链表等长,之后同时遍历两个链表,如果遍历某个结点时相等,则返回结点值,全部结点遍历完成之后如果未找到交叉结点,则返回NULL

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
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
if(headA==NULL||headB==NULL)return NULL;
struct ListNode *p=headA,*q=headB;
int nA=0,nB=0;//统计两链表长度。
int i=0;
while(p!=NULL){
nA++;
p=p->next;
}
while(q!=NULL){
nB++;
q=q->next;
}
p=headA;q=headB;
if(nA>nB){
while(i<(nA-nB)){
i++;
p=p->next;
}
}
else if(nB>nA){
while(i<(nB-nA)){
i++;
q=q->next;
}
}

while(p!=NULL&&q!=NULL){
if(p==q)return p;
p=p->next;
q=q->next;
}
return NULL;
}

方法二

将B链表表头链接到A链表表尾,如果A和B有交叉点则形成环形链表,否则是单链表。此时题目就变为环形链表找第一个进入环形的结点(思想方法见142.环形链表)

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
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {

if(headA == NULL || headB == NULL){
return NULL;
}
struct ListNode* aListTail = headA;//指向A链表尾
struct ListNode* p = headA->next;//工作指针

while(p != NULL){
aListTail = aListTail->next;
p = p->next;
}
aListTail->next = headB;
//使用"142.环形链表II"的思想和方法
struct ListNode* slow = headA,*fast = headA;
p = headA;
while(fast != NULL && fast->next != NULL){
fast = fast->next->next;
slow = slow->next;
if(slow == fast){
while(p != slow){
p = p->next;
slow = slow->next;
}
aListTail->next = NULL;
return p;
}
}
aListTail->next = NULL;
return NULL;
}

方法三

此方法可以想象成是把A链表链接到B链表之后,并且B链表连接到A链表之后,此时A与B变成两个长度相等的大链表,同时遍历两个大链表,第一个相同结点就是交叉结点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
if(headA == NULL || headB == NULL){
return NULL;
}
//工作指针
struct ListNode* p = headA;
struct ListNode* q = headB;

while(p != q){
p = p == NULL ? headB : p->next;
q = q == NULL ? headA : q->next;
}

return p;
}

Leetcode162. Find Peak Element

A peak element is an element that is greater than its neighbors. Given an input array nums, where nums[i] ≠ nums[i+1], find a peak element and return its index. The array may contain multiple peaks, in that case return the index to any one of the peaks is fine.

You may imagine that nums[-1] = nums[n] = -∞.

Example 1:

1
2
3
Input: nums = [1,2,3,1]
Output: 2
Explanation: 3 is a peak element and your function should return the index number 2.

Example 2:
1
2
3
4
Input: nums = [1,2,1,3,5,6,4]
Output: 1 or 5
Explanation: Your function can return either index number 1 where the peak element is 2,
or index number 5 where the peak element is 6.

由于题目中提示了要用对数级的时间复杂度,那么我们就要考虑使用类似于二分查找法来缩短时间,由于只是需要找到任意一个峰值,那么我们在确定二分查找折半后中间那个元素后,和紧跟的那个元素比较下大小,如果大于,则说明峰值在前面,如果小于则在后面。这样就可以找到一个峰值了,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int left = 0, right = nums.size()-1;
while(left < right) {
int mid = left + (right - left) / 2;
if(nums[mid] >= nums[mid+1])
right = mid;
else
left = mid + 1;
}
return right;
}
};

leetcode165. Compare Version Numbers

Given two version numbers, version1 and version2, compare them.

Version numbers consist of one or more revisions joined by a dot ‘.’. Each revision consists of digits and may contain leading zeros. Every revision contains at least one character. Revisions are 0-indexed from left to right, with the leftmost revision being revision 0, the next revision being revision 1, and so on. For example 2.5.33 and 0.1 are valid version numbers.

To compare version numbers, compare their revisions in left-to-right order. Revisions are compared using their integer value ignoring any leading zeros. This means that revisions 1 and 001 are considered equal. If a version number does not specify a revision at an index, then treat the revision as 0. For example, version 1.0 is less than version 1.1 because their revision 0s are the same, but their revision 1s are 0 and 1 respectively, and 0 < 1.

Return the following:

  • If version1 < version2, return -1.
  • If version1 > version2, return 1.
  • Otherwise, return 0.

Example 1:

1
2
3
Input: version1 = "1.01", version2 = "1.001"
Output: 0
Explanation: Ignoring leading zeroes, both "01" and "001" represent the same integer "1".

Example 2:
1
2
3
Input: version1 = "1.0", version2 = "1.0.0"
Output: 0
Explanation: version1 does not specify revision 2, which means it is treated as "0".

Example 3:
1
2
3
Input: version1 = "0.1", version2 = "1.1"
Output: -1
Explanation: version1's revision 0 is "0", while version2's revision 0 is "1". 0 < 1, so version1 < version2.

Example 4:
1
2
Input: version1 = "1.0.1", version2 = "1"
Output: 1

Example 5:
1
2
Input: version1 = "7.5.2.4", version2 = "7.5.3"
Output: -1

由于两个版本号所含的小数点个数不同,有可能是1和1.1.1比较,还有可能开头有无效0,比如01和1就是相同版本,还有可能末尾无效0,比如1.0和1也是同一版本。对于没有小数点的数字,可以默认为最后一位是小数点,而版本号比较的核心思想是相同位置的数字比较,比如题目给的例子,1.2和13.37比较,我们都知道应该显示1和13比较,13比1大,所以后面的不用再比了,再比如1.1和1.2比较,前面都是1,则比较小数点后面的数字。那么算法就是每次对应取出相同位置的小数点之前所有的字符,把他们转为数字比较,若不同则可直接得到答案,若相同,再对应往下取。如果一个数字已经没有小数点了,则默认取出为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
class Solution {
public:
int compareVersion(string version1, string version2) {
int len1 = version1.length(), len2 = version2.length();
int i1 = 0, i2 = 0;
int temp1, temp2;
while(i1 < len1 || i2 < len2) {
temp1 = 0;
while(i1 < len1 && version1[i1] != '.') {
temp1 = temp1 * 10 + (version1[i1] - '0');
i1 ++;
}
temp2 = 0;
while(i2 < len2 && version2[i2] != '.') {
temp2 = temp2 * 10 + (version2[i2] - '0');
i2 ++;
}
printf("%d %d\n", temp1, temp2);
if(temp1 > temp2)
return 1;
else if(temp1 < temp2)
return -1;
i1 ++;
i2 ++;
}
if(temp1 > temp2)
return 1;
else if(temp1 < temp2)
return -1;
else
return 0;
}
};

由于这道题我们需要将版本号以’.’分开,那么我们可以借用强大的字符串流stringstream的功能来实现分段和转为整数,使用这种方法写的代码很简洁,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int compareVersion(string version1, string version2) {
istringstream v1(version1 + "."), v2(version2 + ".");
int d1 = 0, d2 = 0;
char dot = '.';
while (v1.good() || v2.good()) {
if (v1.good()) v1 >> d1 >> dot;
if (v2.good()) v2 >> d2 >> dot;
if (d1 > d2) return 1;
else if (d1 < d2) return -1;
d1 = d2 = 0;
}
return 0;
}
};

最后我们来看一种用C语言的字符串指针来实现的方法,这个方法的关键是用到将字符串转为长整型的strtol函数,关于此函数的用法可以参见我的另一篇博客strtol 函数用法。参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int compareVersion(string version1, string version2) {
int res = 0;
char *v1 = (char*)version1.c_str(), *v2 = (char*)version2.c_str();
while (res == 0 && (*v1 != '\0' || *v2 != '\0')) {
long d1 = *v1 == '\0' ? 0 : strtol(v1, &v1, 10);
long d2 = *v2 == '\0' ? 0 : strtol(v2, &v2, 10);
if (d1 > d2) return 1;
else if (d1 < d2) return -1;
else {
if (*v1 != '\0') ++v1;
if (*v2 != '\0') ++v2;
}
}
return res;
}
};

Leetcode166. Fraction to Recurring Decimal

Given two integers representing the numerator and denominator of a fraction, return the fraction in string format.

If the fractional part is repeating, enclose the repeating part in parentheses.

If multiple answers are possible, return any of them.

Example 1:

1
2
Input: numerator = 1, denominator = 2
Output: "0.5"

Example 2:
1
2
Input: numerator = 2, denominator = 1
Output: "2"

Example 3:
1
2
Input: numerator = 2, denominator = 3
Output: "0.(6)"

Example 4:
1
2
Input: numerator = 4, denominator = 333
Output: "0.(012)"

Example 5:
1
2
Input: numerator = 1, denominator = 5
Output: "0.2"

由于还存在正负情况,处理方式是按正数处理,符号最后在判断,那么我们需要把除数和被除数取绝对值,那么问题就来了:由于整型数INT的取值范围是-2147483648~2147483647,而对-2147483648取绝对值就会超出范围,所以我们需要先转为long long型再取绝对值。那么怎么样找循环呢,肯定是再得到一个数字后要看看之前有没有出现这个数。为了节省搜索时间,我们采用哈希表来存数每个小数位上的数字。还有一个小技巧,由于我们要算出小数每一位,采取的方法是每次把余数乘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
class Solution {
public:
string fractionToDecimal(int numerator, int denominator) {
string res = "";
int l1 = numerator >= 0 ? 1 : -1;
int l2 = denominator >= 0 ? 1 : -1;
long long num = abs((long long)numerator);
long long den = abs((long long)denominator);
long long out = num / den;
long long remain = num % den;
if(numerator == 0)
return "0";
if(l1 * l2 == -1)
res = "-";
res = res + to_string(out);
if(remain == 0)
return res;
res = res + ".";
string s = "";
unordered_map<int, int> m;
int pos = 0;
while(remain != 0) {
if(m.find(remain) != m.end()) {
s.insert(m[remain], "(");
s = s + ")";
return res + s;
}
m[remain] = pos;
out = (remain * 10) / den;
remain = (remain * 10) % den;
s = s + to_string(out);

pos ++;
}
return res + s;
}
};

Leetcode167. Two Sum II - Input array is sorted

Given an array of integers that is already sorted in ascending order, find two numbers such that they add up to a specific target number.

The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2.

Note:

Your returned answers (both index1 and index2) are not zero-based.
You may assume that each input would have exactly one solution and you may not use the same element twice.

Example:

1
2
3
Input: numbers = [2,7,11,15], target = 9
Output: [1,2]
Explanation: The sum of 2 and 7 is 9. Therefore index1 = 1, index2 = 2.

乍一看来,Two Sum II 这道题和 Two Sum 问题一样平平无奇。然而,这道题实际上内藏玄机,加上了数组有序的变化之后,它就换了一套解法。

如果你直接翻答案的话,会发现这就是一道普通的双指针解法。两个指针,O(n)的时间。但是,如果你只看答案,没有理解背后的道理,就会陷入一看就会,一做就跪的困境。

实际上,在这个双指针解法背后蕴含的是缩减搜索空间的通用思想。那么,这篇文章将会为你细细讲述这个解法背后的道理,让你能真正地理解这道经典题目。同时,要做到下次遇到同类题目时,可以快速想到这种解法。

看到有序这个条件,你可能首先想到的是二分查找。但是仔细一想,需要固定一个数,对另一个数进行二分查找,这样时间复杂度是O(nlogn) ,显然不行。在不排序的情况下都做得到O(n)时间、O(n)空间。那么我们的目标只可能是:O(n)的时间、O(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:
vector<int> twoSum(vector<int>& numbers, int target) {
int left=0,right=numbers.size()-1;
vector<int> result;
while(left != right) {
if(numbers[left] + numbers[right] > target) {
right --;
}
else if(numbers[left] + numbers[right] < target) {
left ++;
}
else {
result.push_back(left+1);
result.push_back(right+1);
break;
}
}
return result;
}
};

双指针解法的正确性解释
我们考虑两个指针指向的数字,A[i]A[j]。由于数组是有序的,在一开始,A[i] 是数组中最小的数字,A[j] 是最大的数字。我们将 A[i] + A[j] 与目标和 t`Arget 进行比较,则可能有两种情况:

A[i] + A[j] 大了。这时候,我们应该去找更小的两个数。由于 A[i] 已经是最小的元素了,将任何 A[i] 以外的数跟 A[j] 相加的话,和只会更大。因此 A[j] 一定不能构成正确的解,于是将 j 向左移动一格,排除 A[j]
A[i] + A[j] 小了。这时候,我们应该去找更大的两个数。由于 A[j] 已经是最大的元素了,将任何 A[j] 以外的数跟 A[i] 相加的话,和只会更小。因此 A[i] 一定不能构成正确的解,于是将 i 向右移动一格,排除 A[i]
而第一步排除掉最左或最后的一个数后,我们再看子数组 A[i..j] ,其中 A[i] 又是最小的数字,A[j] 又是最大的数字。我们可以继续进行这样的排除。以此类推,进行 步,就可以排除掉所有可能的情况。

可以看到,无论 A[i] + A[j] 的结果是大了还是小了,我们都可以排除掉其中一个数。这样的排除法和二分搜索很相似。二分搜索通过每次排除一半的元素来减少比较的次数;而这道题的方法通过每次排除一个元素来减少比较的次数。两者又恰好都是利用了数组有序这个性质。

说到这里,这个解法的原理已经揭开一半了。接下来,我们再用更直观的方式,从搜索空间的角度真正地理解这道题。

在这道题中,我们要寻找的是符合条件的一对下标(i, j),它们需要满足的约束条件是:

  • i、j都是合法的下标
  • i < j(题目要求)

而我们希望从中找到满足 A[i] + A[j] == target 的下标。以n=8为例,这时候全部的搜索空间是:

由于i、j的约束条件的限制,搜索空间是白色的倒三角部分。可以看到,搜索空间的大小是O(n^2)数量级的。如果用暴力解法求解,一次只检查一个单元格,那么时间复杂度一定是O(n^2)。而更优的算法,则可以在一次操作内排除掉多个不合格的单元格,从而快速削减搜索空间,定位问题的解。

那么我们来看看,本题的双指针解法是如何削减搜索空间的。一开始,我们检查右上方单元格 ,即计算 A[0] + A[7]

假设A[0] + A[7]小于目标和,由于 A[7] 已经是最大的数,我们可以推出 A[0] + A[6]A[0] + A[5]、……、A[0] + A[1] 也都小于目标和,这些都是不合要求的解,可以一次排除,如下图所示。这相当于排除i=0的全部解,削减了一行的搜索空间,对应双指针解法中的 i++。

以此类推,每次会削减一行或一列的搜索空间,经过n步以后,一定能检查完所有的可能性。

这种方法的运行还是较慢, 只比10.34%的C++要快。

Leetcode168. Excel Sheet Column Title

Given a positive integer, return its corresponding column title as appear in an Excel sheet.

For example:

1
2
3
4
5
6
7
8
1 -> A
2 -> B
3 -> C
...
26 -> Z
27 -> AA
28 -> AB
...

Example 1:

1
2
Input: 1
Output: "A"

Example 2:
1
2
Input: 28
Output: "AB"

Example 3:
1
2
Input: 701
Output: "ZY"

Excel序是这样的:A~Z, AA~ZZ, AAA~ZZZ, ……本质上就是将一个10进制数转换为一个26进制的数。注意:由于下标从1开始而不是从0开始,因此要减一操作。

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
string convertToTitle(int n) {
string result = "";
while(n>0) {
result = (char)('A'+ (n-1)%26) + result;
n = (n-1) / 26;
}
return result;
}
};

Leetcode169. Majority Element

Given an array of size n, find the majority element. The majority element is the element that appears more than ⌊ n/2 ⌋ times.

You may assume that the array is non-empty and the majority element always exist in the array.

Example 1:

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

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

最简单的做法就是先排序,然后返回(n / 2)个元素,但是这样太慢了。
1
2
3
4
5
6
7
class Solution {
public:
int majorityElement(vector<int>& nums) {
sort(nums.begin(), nums.end());
return nums[nums.size()/2];
}
};

有一种比较巧妙的做法,用的是Boyer-Moore’s Algorithm,用count记录当前的m的个数,如果m为0了说明这个数不是出现最多的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int majorityElement(vector<int>& nums) {
int m = nums[0], i, count = 1;
int numsSize = nums.size();
for (i = 1; i < numsSize; i ++)
{
if (count == 0){
m = nums[i];
count ++;
}
else if (m == nums[i])
count++;
else
count--;
}
return m;
}
};

Leetcode170. Two Sum III - Data structure design

Design and implement a TwoSum class. It should support the following operations: add and find.

add - Add the number to an internal data structure.
find - Find if there exists any pair of numbers which sum is equal to the value.

Example 1:

1
2
3
add(1); add(3); add(5);
find(4) -> true
find(7) -> false

Example 2:
1
2
3
add(3); add(1); add(2);
find(3) -> true
find(6) -> false

让我们设计一个 Two Sum 的数据结构。先来看用 HashMap 的解法,把每个数字和其出现的次数建立映射,然后遍历 HashMap,对于每个值,先求出此值和目标值之间的差值t,然后需要分两种情况来看,如果当前值不等于差值t,那么只要 HashMap 中有差值t就返回 True,或者是当差值t等于当前值时,如果此时 HashMap 的映射次数大于1,则表示 HashMap 中还有另一个和当前值相等的数字,二者相加就是目标值,参见代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class TwoSum {
public:
void add(int number) {
++m[number];
}
bool find(int value) {
for (auto a : m) {
int t = value - a.first;
if ((t != a.first && m.count(t)) || (t == a.first && a.second > 1)) {
return true;
}
}
return false;
}
private:
unordered_map<int, int> m;
};

Leetcode171. Excel Sheet Column Number

Given a column title as appear in an Excel sheet, return its corresponding column number.

Example 1:

1
2
Input: "A"
Output: 1

Example 2:
1
2
Input: "AB"
Output: 28

Example 3:
1
2
Input: "ZY"
Output: 701

给一个excel表中的列标,把它转换成十进制的数,简单,但是要用long才能过。
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int titleToNumber(string s) {
int length = s.length();
long result = 0;
long temp = 1;
for(int i = length - 1; i >= 0; i--) {
result += (s[i] - 'A' + 1) * temp;
temp *= 26;
}
return result;
}
};

简单做法:
1
2
3
4
5
6
7
8
9
public int titleToNumber(String s) {
if (s.length() == 0 || s == null)
return 0;
int sum = 0;
for (int i = 0; i < s.length(); i++) {
sum = sum * 26 + (s.charAt(i) - 'A' + 1);
}
return sum;
}

Leetcode172. Factorial Trailing Zeroes

Given an integer n, return the number of trailing zeroes in n!.

Example 1:

1
2
3
Input: 3
Output: 0
Explanation: 3! = 6, no trailing zero.

Example 2:
1
2
3
Input: 5
Output: 1
Explanation: 5! = 120, one trailing zero.

让求一个数的阶乘末尾0的个数,也就是要找乘数中 10 的个数,而 10 可分解为2和5,而2的数量又远大于5的数量(比如1到 10 中有2个5,5个2),那么此题即便为找出5的个数。仍需注意的一点就是,像 25,125,这样的不只含有一个5的数字需要考虑进去
1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
int trailingZeroes(int n) {
int res = 0;
while(n) {
res += n/5;
n /= 5;
}
return res;
}
};

Leetcode173. Binary Search Tree Iterator

Implement an iterator over a binary search tree (BST). Your iterator will be initialized with the root node of a BST.

Calling next() will return the next smallest number in the BST.

Example:

1
2
3
4
5
6
7
8
9
10
BSTIterator iterator = new BSTIterator(root);
iterator.next(); // return 3
iterator.next(); // return 7
iterator.hasNext(); // return true
iterator.next(); // return 9
iterator.hasNext(); // return true
iterator.next(); // return 15
iterator.hasNext(); // return true
iterator.next(); // return 20
iterator.hasNext(); // return false

关键在于理解inorder的stack代表了什么含义,为什么要用到这个stack:stack的存在是为了我们找回来时的路。但是不一定立刻就去找到successor,如果call到了,再沿着栈顶元素去找。而做法就是首先将最左边的路径一路推进栈,每次从栈中pop来提取next。提取之后还要保证,如果栈顶有right,那么一路将top -> right 的left推入栈;如果没有right,栈顶本身就是successor。这样做完这一系列检查后,栈顶元素就可以保证是successor。

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 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 BSTIterator {
public:
stack<TreeNode*> s;

BSTIterator(TreeNode* root) {
//s = new stack<TreeNode*>();
while(root != NULL) {
s.push(root);
root = root->left;
}
}

/** @return the next smallest number */
int next() {
TreeNode* cur = s.top();
s.pop();
TreeNode *temp = cur->right;
while(temp) {
s.push(temp);
temp = temp->left;
}
return cur->val;
}

/** @return whether we have a next smallest number */
bool hasNext() {
return !s.empty();
}
};

Leetcode175. Combine Two Tables

Table: Person
+——————-+————-+
| Column Name | Type |
+——————-+————-+
| PersonId | int |
| FirstName | varchar |
| LastName | varchar |
+——————-+————-+
PersonId is the primary key column for this table.

Table: Address
+——————-+————-+
| Column Name | Type |
+——————-+————-+
| AddressId | int |
| PersonId | int |
| City | varchar |
| State | varchar |
+——————-+————-+
AddressId is the primary key column for this table.

Write a SQL query for a report that provides the following information for each person in the Person table, regardless if there is an address for each of those people:

1
FirstName, LastName, City, State

Person表左连接 Address表查询,不能用条件查询,因为不管该人是否有地址
1
2
3
# Write your MySQL query statement below
select FirstName, LastName, City, State
from Person left join Address on Person.PersonId=Address.PersonId

Leetcode176. Second Highest Salary

Write a SQL query to get the second highest salary from the Employee table.

1
2
3
4
5
6
7
+----+--------+
| Id | Salary |
+----+--------+
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
+----+--------+

For example, given the above Employee table, the query should return 200 as the second highest salary. If there is no second highest salary, then the query should return null.
1
2
3
4
5
+---------------------+
| SecondHighestSalary |
+---------------------+
| 200 |
+---------------------+

刚开始的想法是使用ORDER BY指令排序,然后通过LIMIT输出第二行数据就可以了,但是这样做会有两个bug,一个是如果存在两个Id有相同的Salary并且都为最高,则我们的输出还是最高Salary;另一个就是这样无法满足题目要求的If there is no second highest salary, then the query should return null。这是因为SELECT指令会在没有满足条件的输出时,会输出空。

所以我们应该使用MAX()函数,其满足没有记录时输出null的条件。

题解:

1
2
3
# Write your MySQL query statement below
SELECT MAX(Salary) AS SecondHighestSalary FROM Employee
WHERE Salary < (SELECT MAX(Salary) FROM Employee);

Leetcode177. Nth Highest Salary

Write a SQL query to get the nth highest salary from the Employee table.

1
2
3
4
5
6
7
+----+--------+
| Id | Salary |
+----+--------+
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
+----+--------+

For example, given the above Employee table, the nth highest salary where n = 2 is 200. If there is no nth highest salary, then the query should return null.

1
2
3
4
5
+------------------------+
| getNthHighestSalary(2) |
+------------------------+
| 200 |
+------------------------+

找出表中工资第n高的

order by,limit 及变量的使用

1
2
3
4
5
6
7
8
9
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
declare m int;
set m = n - 1;
RETURN (
# Write your MySQL query statement below.
select distinct salary from Employee order by salary desc limit m, 1
);
END

Leetcode178. Rank Scores

Write a SQL query to rank scores. If there is a tie between two scores, both should have the same ranking. Note that after a tie, the next ranking number should be the next consecutive integer value. In other words, there should be no “holes” between ranks.

1
2
3
4
5
6
7
8
9
10
+----+-------+
| Id | Score |
+----+-------+
| 1 | 3.50 |
| 2 | 3.65 |
| 3 | 4.00 |
| 4 | 3.85 |
| 5 | 4.00 |
| 6 | 3.65 |
+----+-------+

For example, given the above Scores table, your query should generate the following report (order by highest score):

1
2
3
4
5
6
7
8
9
10
+-------+------+
| Score | Rank |
+-------+------+
| 4.00 | 1 |
| 4.00 | 1 |
| 3.85 | 2 |
| 3.65 | 3 |
| 3.65 | 3 |
| 3.50 | 4 |
+-------+------+

这道题给了我们一个分数表,让我们给分数排序,要求是相同的分数在相同的名次,下一个分数在相连的下一个名次,中间不能有空缺数字,解题的思路是对于每一个分数,找出表中有多少个大于或等于该分数的不同的分数,然后按降序排列即可,参见代码如下:

1
2
3
SELECT Score, 
(SELECT COUNT(DISTINCT Score) FROM Scores WHERE Score >= s.Score) Rank
FROM Scores s ORDER BY Score DESC;

Leetcode179. Largest Number

Given a list of non negative integers, arrange them such that they form the largest number.

Example 1:

1
2
Input: [10,2]
Output: "210"

Example 2:

1
2
Input: [3,30,34,5,9]
Output: "9534330"

Note: The result may be very large, so you need to return a string instead of an integer.

这道题给了我们一个数组,让将其拼接成最大的数,那么根据题目中给的例子来看,主要就是要给数组进行排序,但是排序方法不是普通的升序或者降序,因为9要排在最前面,而9既不是数组中最大的也不是最小的,所以要自定义排序方法。对于两个数字a和b来说,如果将其都转为字符串,如果 ab > ba,则a排在前面,比如9和34,由于 934>349,所以9排在前面,再比如说 30 和3,由于 303<330,所以3排在 30 的前面。按照这种规则对原数组进行排序后,将每个数字转化为字符串再连接起来就是最终结果。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
string largestNumber(vector<int>& nums) {
string res;
sort(nums.begin(), nums.end(), [](int a, int b) {
return to_string(a) + to_string(b) > to_string(b) + to_string(a);
});
for (int i = 0; i < nums.size(); ++i) {
res += to_string(nums[i]);
}
return res[0] == '0' ? "0" : res;
}
};

Leetcode180. Consecutive Numbers

Table: Logs

1
2
3
4
5
6
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| id | int |
| num | varchar |
+-------------+---------+

id is the primary key for this table.

Write an SQL query to find all numbers that appear at least three times consecutively.

Return the result table in any order.

The query result format is in the following example:

Logs table:

1
2
3
4
5
6
7
8
9
10
11
+----+-----+
| Id | Num |
+----+-----+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
| 5 | 1 |
| 6 | 2 |
| 7 | 2 |
+----+-----+

Result table:

1
2
3
4
5
+-----------------+
| ConsecutiveNums |
+-----------------+
| 1 |
+-----------------+

1 is the only number that appears consecutively for at least three times.

1
2
3
4
5
6
7
8
# Write your MySQL query statement below

select distinct l1.num as ConsecutiveNums
from logs l1, logs l2, logs l3
where l1.id = l2.id-1
and l2.id = l3.id-1
and l1.num = l2.num
and l2.num = l3.num;

Leetcode181. Employees Earning More Than Their Managers

The Employee table holds all employees including their managers. Every employee has an Id, and there is also a column for the manager Id.

1
2
3
4
5
6
7
8
+----+-------+--------+-----------+
| Id | Name | Salary | ManagerId |
+----+-------+--------+-----------+
| 1 | Joe | 70000 | 3 |
| 2 | Henry | 80000 | 4 |
| 3 | Sam | 60000 | NULL |
| 4 | Max | 90000 | NULL |
+----+-------+--------+-----------+

Given the Employee table, write a SQL query that finds out employees who earn more than their managers. For the above table, Joe is the only employee who earns more than his manager.
1
2
3
4
5
+----------+
| Employee |
+----------+
| Joe |
+----------+

自连接,找出比其上司挣得更多的员工。
1
2
3
# Write your MySQL query statement below
select r1.Name as Employee from Employee as r1, Employee as r2
where r1.ManagerId = r2.Id and r1.Salary > r2.Salary

Leetcode182. Duplicate Emails

Write a SQL query to find all duplicate emails in a table named Person.

1
2
3
4
5
6
7
+----+---------+
| Id | Email |
+----+---------+
| 1 | a@b.com |
| 2 | c@d.com |
| 3 | a@b.com |
+----+---------+

For example, your query should return the following for the above table:
1
2
3
4
5
+---------+
| Email |
+---------+
| a@b.com |
+---------+

在表中,可能会包含重复值。这并不成问题,不过,有时您也许希望仅仅列出不同(distinct)的值。关键词 DISTINCT 用于返回唯一不同的值。
1
2
3
# Write your MySQL query statement below
select distinct r1.Email as Email from Person as r1, Person as r2
where r1.Email = r2.Email and r1.Id != r2.Id

Leetcode183. Customers Who Never Order

Suppose that a website contains two tables, the Customers table and the Orders table. Write a SQL query to find all customers who never order anything.

Table: Customers.

1
2
3
4
5
6
7
8
+----+-------+
| Id | Name |
+----+-------+
| 1 | Joe |
| 2 | Henry |
| 3 | Sam |
| 4 | Max |
+----+-------+

Table: Orders.
1
2
3
4
5
6
+----+------------+
| Id | CustomerId |
+----+------------+
| 1 | 3 |
| 2 | 1 |
+----+------------+

Using the above tables as example, return the following:
1
2
3
4
5
6
+-----------+
| Customers |
+-----------+
| Henry |
| Max |
+-----------+

使用NOT IN
1
2
3
select Name as Customers
from Customers
where Id not in (select CustomerId from Orders)

使用左外连接 然后使用条件语句
1
2
3
4
5
SELECT C.Name Customers
FROM Customers C
LEFT JOIN Orders O
ON C.Id=O.CustomerId
WHERE O.CustomerId IS NULL;

Leetcode184. Department Highest Salary

The Employee table holds all employees. Every employee has an Id, a salary, and there is also a column for the department Id.

1
2
3
4
5
6
7
8
9
+----+-------+--------+--------------+
| Id | Name | Salary | DepartmentId |
+----+-------+--------+--------------+
| 1 | Joe | 70000 | 1 |
| 2 | Jim | 90000 | 1 |
| 3 | Henry | 80000 | 2 |
| 4 | Sam | 60000 | 2 |
| 5 | Max | 90000 | 1 |
+----+-------+--------+--------------+

The Department table holds all departments of the company.
1
2
3
4
5
6
+----+----------+
| Id | Name |
+----+----------+
| 1 | IT |
| 2 | Sales |
+----+----------+

Write a SQL query to find employees who have the highest salary in each of the departments. For the above tables, your SQL query should return the following rows (order of rows does not matter).
1
2
3
4
5
6
7
+------------+----------+--------+
| Department | Employee | Salary |
+------------+----------+--------+
| IT | Max | 90000 |
| IT | Jim | 90000 |
| Sales | Henry | 80000 |
+------------+----------+--------+

可以将Employee表先按DepartmentId分组然后SELECT出部门id和最大工资作为子查询,然后再两个表JOIN起来查询出需要的信息。代码如下:

1
2
3
4
5
6
SELECT Department.name `Department`, Employee.name `Employee`, Salary
FROM Employee JOIN Department
ON Employee.DepartmentId = Department.Id
WHERE (Employee.DepartmentId, Salary) IN
(SELECT DepartmentId, MAX(Salary)
FROM Employee GROUP BY DepartmentId)

Leetcode185. Department Top Three Salaries

Table: Employee

1
2
3
4
5
6
7
8
+--------------+---------+
| Column Name | Type |
+--------------+---------+
| Id | int |
| Name | varchar |
| Salary | int |
| DepartmentId | int |
+--------------+---------+

Id is the primary key for this table.Each row contains the ID, name, salary, and department of one employee.

Table: Department

1
2
3
4
5
6
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| Id | int |
| Name | varchar |
+-------------+---------+

Id is the primary key for this table. Each row contains the ID and the name of one department.

A company’s executives are interested in seeing who earns the most money in each of the company’s departments. A high earner in a department is an employee who has a salary in the top three unique salaries for that department.

Write an SQL query to find the employees who are high earners in each of the departments.

Return the result table in any order.

The query result format is in the following example:

Employee table:

1
2
3
4
5
6
7
8
9
10
11
+----+-------+--------+--------------+
| Id | Name | Salary | DepartmentId |
+----+-------+--------+--------------+
| 1 | Joe | 85000 | 1 |
| 2 | Henry | 80000 | 2 |
| 3 | Sam | 60000 | 2 |
| 4 | Max | 90000 | 1 |
| 5 | Janet | 69000 | 1 |
| 6 | Randy | 85000 | 1 |
| 7 | Will | 70000 | 1 |
+----+-------+--------+--------------+

Department table:
1
2
3
4
5
6
+----+-------+
| Id | Name |
+----+-------+
| 1 | IT |
| 2 | Sales |
+----+-------+

Result table:
1
2
3
4
5
6
7
8
9
10
+------------+----------+--------+
| Department | Employee | Salary |
+------------+----------+--------+
| IT | Max | 90000 |
| IT | Joe | 85000 |
| IT | Randy | 85000 |
| IT | Will | 70000 |
| Sales | Henry | 80000 |
| Sales | Sam | 60000 |
+------------+----------+--------+

In the IT department:

  • Max earns the highest unique salary
  • Both Randy and Joe earn the second-highest unique salary
  • Will earns the third-highest unique salary

In the Sales department:

  • Henry earns the highest salary
  • Sam earns the second-highest salary
  • There is no third-highest salary as there are only two employees
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Write your MySQL query statement below
select t2.name as Department, t1.Employee , t1.salary from
(
select e1.id, e1.name as employee, e1.salary, e1.departmentid from Employee e1
inner join Employee e2
on e1.departmentid = e2.departmentid
and e1.salary<=e2.salary
group by e1.id
having count(distinct(e2.salary))<=3
)
t1
inner join
(select * from Department )t2
on t1.DepartmentId = t2.id

Leetcode187. Repeated DNA Sequences

The DNA sequence is composed of a series of nucleotides abbreviated as ‘A’, ‘C’, ‘G’, and ‘T’.

For example, “ACGAATTCCG” is a DNA sequence.
When studying DNA, it is useful to identify repeated sequences within the DNA.

Given a string s that represents a DNA sequence, return all the 10-letter-long sequences (substrings) that occur more than once in a DNA molecule. You may return the answer in any order.

Example 1:

1
2
Input: s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"
Output: ["AAAAACCCCC","CCCCCAAAAA"]

Example 2:
1
2
Input: s = "AAAAAAAAAAAAA"
Output: ["AAAAAAAAAA"]

哈希表。直接把每个长度为10的字符串的count记下来,然后看计数大于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:
vector<string> findRepeatedDnaSequences(string s) {
map<string, int> m;
if (s.length() < 10)
return {};
for(int i = 0; i <= s.length()-10;i ++) {
string tmp = s.substr(i, 10);
if (!m.count(tmp))
m[tmp] = 1;
else
m[tmp] ++;
}
vector<string> v;
for(pair<string, int> p : m) {
if (p.second > 1)
v.push_back(p.first);
}
return v;
}
};

此题由于构成输入字符串的字符只有四种,分别是 A, C, G, T,下面来看下它们的 ASCII 码用二进制来表示:

A: 0100 0001  C: 0100 0011  G: 0100 0111  T: 0101 0100

由于目的是利用位来区分字符,当然是越少位越好,通过观察发现,每个字符的后三位都不相同,故而可以用末尾三位来区分这四个字符。而题目要求是 10 个字符长度的串,每个字符用三位来区分,10 个字符需要30位,在 32 位机上也 OK。为了提取出后 30 位,还需要用个 mask,取值为 0x7ffffff,用此 mask 可取出后27位,再向左平移三位即可。算法的思想是,当取出第十个字符时,将其存在 HashMap 里,和该字符串出现频率映射,之后每向左移三位替换一个字符,查找新字符串在 HashMap 里出现次数,如果之前刚好出现过一次,则将当前字符串存入返回值的数组并将其出现次数加一,如果从未出现过,则将其映射到1。为了能更清楚的阐述整个过程,就用题目中给的例子来分析整个过程:

首先取出前九个字符 AAAAACCCC,根据上面的分析,用三位来表示一个字符,所以这九个字符可以用二进制表示为 001001001001001011011011011,然后继续遍历字符串,下一个进来的是C,则当前字符为 AAAAACCCCC,二进制表示为 001001001001001011011011011011,然后将其存入 HashMap 中,用二进制的好处是可以用一个 int 变量来表示任意十个字符序列,比起直接存入字符串大大的节省了内存空间,然后再读入下一个字符C,则此时字符串为 AAAACCCCCA,还是存入其二进制的表示形式,以此类推,当某个序列之前已经出现过了,将其存入结果 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<string> findRepeatedDnaSequences(string s) {
if (s.length() <= 10)
return {};
vector<string> v;
int mask = 0x7ffffff, cur = 0;
unordered_map<int, int> m;
for (int i = 0; i < 9; i ++)
cur = (cur << 3) | (s[i] & 7);
for (int i = 9; i < s.length(); i ++) {
cur = ((cur & mask)<<3) | (s[i] & 7);
if (m.count(cur)) {
if (m[cur] == 1)
v.push_back(s.substr(i-9, 10));
m[cur] ++;
}
else
m[cur] = 1;
}
return v;
}
};

Leetcode189. Rotate Array

Given an array, rotate the array to the right by k steps, where k is non-negative.

Follow up: Try to come up as many solutions as you can, there are at least 3 different ways to solve this problem.
Could you do it in-place with O(1) extra space?

Example 1:

1
2
3
4
5
6
Input: nums = [1,2,3,4,5,6,7], k = 3
Output: [5,6,7,1,2,3,4]
Explanation:
rotate 1 steps to the right: [7,1,2,3,4,5,6]
rotate 2 steps to the right: [6,7,1,2,3,4,5]
rotate 3 steps to the right: [5,6,7,1,2,3,4]

Example 2:
1
2
3
4
5
Input: nums = [-1,-100,3,99], k = 2
Output: [3,99,-1,-100]
Explanation:
rotate 1 steps to the right: [99,-1,-100,3]
rotate 2 steps to the right: [3,99,-1,-100]

由于提示中要求我们空间复杂度为 O(1),所以我们不能用辅助数组,上面的思想还是可以使用的,但是写法就复杂的多,而且需要用到很多的辅助变量,我们还是要将 nums[idx] 上的数字移动到 nums[(idx+k) % n] 上去,为了防止数据覆盖丢失,我们需要用额外的变量来保存,这里用了 pre 和 cur,其中 cur 初始化为了数组的第一个数字,然后当然需要变量 idx 标明当前在交换的位置,还需要一个变量 start,这个是为了防止陷入死循环的,初始化为0,一旦当 idx 变到了 strat 的位置,则 start 自增1,再赋值给 idx,这样 idx 的位置也改变了,可以继续进行交换了。举个例子,假如 [1, 2, 3, 4], K=2 的话,那么 idx=0,下一次变为 idx = (idx+k) % n = 2,再下一次又变成了 idx = (idx+k) % n = 0,此时明显 1 和 3 的位置还没有处理过,所以当我们发现 idx 和 start 相等,则二者均自增1,那么此时 idx=1,下一次变为 idx = (idx+k) % n = 3,就可以交换完所有的数字了。

因为长度为n的数组只需要更新n次,所以我们用一个 for 循环来处理n次。首先 pre 更新为 cur,然后计算新的 idx 的位置,然后将 nums[idx] 上的值先存到 cur 上,然后把 pre 赋值给 nums[idx],这相当于把上一轮的 nums[idx] 赋给了新的一轮,完成了数字的交换,然后 if 语句判断是否会变到处理过的数字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
void rotate(vector<int>& nums, int k) {
if (nums.empty() || (k %= nums.size()) == 0) return;
int temp;
int length = nums.size();
int idx = 0, start = 0, cur = nums[0], pre = -1;
for(int i = 0; i < length; i ++) {
pre = cur;
idx = (idx + k) % length;
cur = nums[idx];
nums[idx] = pre;
if (idx == start) {
idx = ++start;
cur = nums[idx];
}
}
}
};

这道题其实还有种类似翻转字符的方法,思路是先把前 n-k 个数字翻转一下,再把后k个数字翻转一下,最后再把整个数组翻转一下:

1
2
3
4
5
6
7
8
9
10
class Solution {
public:
void rotate(vector<int>& nums, int k) {
if (nums.empty() || (k %= nums.size()) == 0) return;
int n = nums.size();
reverse(nums.begin(), nums.begin() + n - k);
reverse(nums.begin() + n - k, nums.end());
reverse(nums.begin(), nums.end());
}
};

Leetcode190. Reverse Bits

Reverse bits of a given 32 bits unsigned integer.

Example 1:

1
2
3
Input: 00000010100101000001111010011100
Output: 00111001011110000010100101000000
Explanation: The input binary string 00000010100101000001111010011100 represents the unsigned integer 43261596, so return 964176192 which its binary representation is 00111001011110000010100101000000.

Example 2:
1
2
3
Input: 11111111111111111111111111111101
Output: 10111111111111111111111111111111
Explanation: The input binary string 11111111111111111111111111111101 represents the unsigned integer 4294967293, so return 3221225471 which its binary representation is 10111111111111111111111111111111.

把要翻转的数从右向左一位位的取出来,如果取出来的是1,将结果 res 左移一位并且加上1;如果取出来的是0,将结果 res 左移一位,然后将n右移一位即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
int res = 0;
for(int i = 0; i < 32; i ++) {
if(n & 1)
res = (res << 1) + 1;
else
res = res << 1;
n = n >> 1;
}
return res;
}
};

Leetcode191. Number of 1 Bits

Write a function that takes an unsigned integer and return the number of ‘1’ bits it has (also known as the Hamming weight).

Example 1:

1
2
3
Input: 00000000000000000000000000001011
Output: 3
Explanation: The input binary string 00000000000000000000000000001011 has a total of three '1' bits.

Example 2:
1
2
3
Input: 00000000000000000000000010000000
Output: 1
Explanation: The input binary string 00000000000000000000000010000000 has a total of one '1' bit.

Example 3:
1
2
3
Input: 11111111111111111111111111111101
Output: 31
Explanation: The input binary string 11111111111111111111111111111101 has a total of thirty one '1' bits.

同样的位操作
1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
int hammingWeight(uint32_t n) {
int res = 0;
for(int i = 0; i < 32; i ++) {
if(n & 1)
res ++;
n = n >> 1;
}
return res;
}
};

Leetcode192. Word Frequency

Write a bash script to calculate the frequency of each word in a text file words.txt.

For simplicity sake, you may assume:

  • words.txt contains only lowercase characters and space ‘ ‘ characters.
  • Each word must consist of lowercase characters only.
  • Words are separated by one or more whitespace characters.

Example:

Assume that words.txt has the following content:

1
2
the day is sunny the the
the sunny is is

Your script should output the following, sorted by descending frequency:

1
2
3
4
the 4
is 3
sunny 2
day 1

1
cat words.txt | tr -s ' ' '\n' | sort | uniq -c | sort -r | awk '{ print $2, $1 }'
  • tr -s: 通过目标字符串截取字符串,只保留一个实例(多个空格只算和一个空格等效)
  • sort: 排序,将相同的字符串连续在一起,以便统计相同字符串
  • uniq -c: 过滤重复行,并统计相同的字符串
  • sort -r:逆序排序
  • awk '{print $2,$1}':格式化输出

Leetcode193. Valid Phone Numbers

Given a text file file.txt that contains list of phone numbers (one per line), write a one liner bash script to print all valid phone numbers.

You may assume that a valid phone number must appear in one of the following two formats: (xxx) xxx-xxxx or xxx-xxx-xxxx. (x means a digit)

You may also assume each line in the text file must not contain leading or trailing white spaces.

Example: Assume that file.txt has the following content:

1
2
3
987-123-4567
123 456 7890
(123) 456-7890

Your script should output the following valid phone numbers:
1
2
987-123-4567
(123) 456-7890

写一个bash命令,用grep加上正则表达式:
1
2
# Read from the file file.txt and output all valid phone numbers to stdout.
grep -e '^[0-9]\{3\}-[0-9]\{3\}-[0-9]\{4\}$' -e '^([0-9]\{3\}) [0-9]\{3\}-[0-9]\{4\}$' file.txt

Leetcode194. Transpose File

Given a text file file.txt, transpose its content.

You may assume that each row has the same number of columns, and each field is separated by the ‘ ‘ character.

Example:

If file.txt has the following content:

1
2
3
name age
alice 21
ryan 30

Output the following:

1
2
name alice ryan
age 21 30

这道题让我们转置一个文件,其实感觉就是把文本内容当做了一个矩阵,每个单词空格隔开看做是矩阵中的一个元素,然后将转置后的内容打印出来。那么我们先来看使用awk关键字的做法,关于awk的介绍可以参见这个帖子。其中NF表示当前记录中的字段个数,就是有多少列,NR表示已经读出的记录数,就是行号,从1开始。那么在这里NF是2,因为文本只有两列,这里面这个for循环还跟我们通常所熟悉for循环不太一样,通常我们以为i只能是1和2,然后循环就结束了,而这里的i实际上遍历的数字为1,2,1,2,1,2,我们可能看到实际上循环了3遍1和2,而行数正好是3,可能人家就是这个机制吧。知道了上面这些,那么下面的代码就不难理解了,遍历过程如下:

i = 1, s = [name]

i = 2, s = [name; age]

i = 1, s = [name alice; age]

i = 2, s = [name alice; age 21]

i = 1, s = [name alice ryan; age 21]

i = 2, s = [name alice ryan; age 21 30]

然后我们再将s中的各行打印出来即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
awk '{
for (i = 1; i <= NF; ++i) {
if (NR == 1) s[i] = $i;
else s[i] = s[i] " " $i;
}
} END {
for (i = 1; s[i] != ""; ++i) {
print s[i];
}
}' file.txt

Leetcode195. Tenth Line

Given a text file file.txt, print just the 10th line of the file.

Example: Assume that file.txt has the following content:

1
2
3
4
5
6
7
8
9
10
Line 1
Line 2
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9
Line 10

Your script should output the tenth line, which is:
1
Line 10

Shell分类的题全部都可以使用awk或者sed等linux命令来完成的,并且使用起来较为简单

awk:

1
awk 'NR == 10' file.txt

NR是awk的一个内置变量,可以理解为第几行。显然这个是非常简单的,输出第10行的内容。

sed:

1
sed -n '10p' file.txt

至于sed和awk其实都一样,不过是换了一种表达方式而已。

也可以使用bash风格来解决。

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
lines=0
while read -r line
do
let lines=lines+1
echo "$line"
if(($lines == 10));then
echo "$line"
fi
done<file.txt

由于本身这道题就比较简单,所以也容易理解这段代码。定义一个变量lines用来计算行数,然后使用while read line的一种方法,该方法可以参考while read line 介绍。然后就是每次读取一行内容对lines进行+1,判断是否等于10,等于的话就直接输出。

Leetcode196. Delete Duplicate Emails

Write a SQL query to delete all duplicate email entries in a table named Person, keeping only unique emails based on its smallest Id.

1
2
3
4
5
6
7
+----+------------------+
| Id | Email |
+----+------------------+
| 1 | john@example.com |
| 2 | bob@example.com |
| 3 | john@example.com |
+----+------------------+

Id is the primary key column for this table.
For example, after running your query, the above Person table should have the following rows:
1
2
3
4
5
6
+----+------------------+
| Id | Email |
+----+------------------+
| 1 | john@example.com |
| 2 | bob@example.com |
+----+------------------+

Note: Your output is the whole Person table after executing your sql. Use delete statement.
1
2
# Write your MySQL query statement below
delete p1 from Person as p1, Person as p2 where p1.Email = p2.Email and p1.Id > p2.Id

Leetcode197. Rising Temperature

Given a Weather table, write a SQL query to find all dates’ Ids with higher temperature compared to its previous (yesterday’s) dates.

1
2
3
4
5
6
7
8
+---------+------------------+------------------+
| Id(INT) | RecordDate(DATE) | Temperature(INT) |
+---------+------------------+------------------+
| 1 | 2015-01-01 | 10 |
| 2 | 2015-01-02 | 25 |
| 3 | 2015-01-03 | 20 |
| 4 | 2015-01-04 | 30 |
+---------+------------------+------------------+

For example, return the following Ids for the above Weather table:
1
2
3
4
5
6
+----+
| Id |
+----+
| 2 |
| 4 |
+----+

1
2
3
4
select a.Id 
from Weather a inner join Weather b
on b.Date+interval 1 day =a.Date
where a.Temperature>b.Temperature;

1
2
3
4
select a.Id 
from Weather a inner join Weather b
on datediff(a.RecordDate, b.RecordDate) = 1
where a.Temperature>b.Temperature;

Leetcode198. House Robber

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.

Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.

Example 1:

Input: [1,2,3,1]
Output: 4
Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3).
Total amount you can rob = 1 + 3 = 4.
Example 2:

Input: [2,7,9,3,1]
Output: 12
Explanation: Rob house 1 (money = 2), rob house 3 (money = 9) and rob house 5 (money = 1).
Total amount you can rob = 2 + 9 + 1 = 12.

根据这道题的条件特点:
如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警(即相邻的数字不能同时作为最终求和的有效数字)。

分析
这个条件如果精简掉其他内容,很容易让人联想到奇偶数。这个解法就是从这点出发。
设置两个变量,sumOdd 和 sumEven 分别对数组的奇数和偶数元素求和。

最后比较这两个和谁更大,谁就是最优解。

接下来要解决的就是最优解不是纯奇数和或者偶数和的情况。
这种情况下,最优解可能前半段出现在这边,后半段出现在另一边。
那么只要找到一个时机,当这一段的最优解没有另一边好时,就复制对面的最优解过来。

其实就是比较每个步骤内的奇数偶数和谁大

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(vector<int>& nums) {
int sumEven=0;
int sumOdd=0;
for (int i = 0; i < nums.size(); i++)
{
if (i % 2 == 0)
{
sumEven += nums[i];
sumEven = max(sumEven,sumOdd);
}
else
{
sumOdd += nums[i];
sumOdd = max(sumEven,sumOdd);
}
}
return max(sumOdd, sumEven);
}
};

Leetcode199. Binary Tree Right Side View

Given the root of a binary tree, imagine yourself standing on the right side of it, return the values of the nodes you can see ordered from top to bottom.

Example 1:

1
2
Input: root = [1,2,3,null,5,null,4]
Output: [1,3,4]

Example 2:

1
2
Input: root = [1,null,3]
Output: [1,3]

这道题要求我们打印出二叉树每一行最右边的一个数字,实际上是求二叉树层序遍历的一种变形,我们只需要保存每一层最右边的数字即可,可以参考我之前的博客 Binary Tree Level Order Traversal 二叉树层序遍历,这道题只要在之前那道题上稍加修改即可得到结果,还是需要用到数据结构队列queue,遍历每层的节点时,把下一层的节点都存入到queue中,每当开始新一层节点的遍历之前,先把新一层最后一个节点值存到结果中,代码如下:

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> rightSideView(TreeNode *root) {
vector<int> res;
if (!root) return res;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
res.push_back(q.back()->val);
int size = q.size();
for (int i = 0; i < size; ++i) {
TreeNode *node = q.front();
q.pop();
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
}
return res;
}
};

Leetcode200. Number of Islands

Given a 2d grid map of ‘1’s (land) and ‘0’s (water), count the number of islands. 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:

1
2
3
4
5
6
7
Input:
11110
11010
11000
00000

Output: 1

Example 2:
1
2
3
4
5
6
7
Input:
11000
11000
00100
00011

Output: 3

这道题的主要思路是深度优先搜索。每次走到一个是 1 的格子,就搜索整个岛屿。

网格可以看成是一个无向图的结构,每个格子和它上下左右的四个格子相邻。如果四个相邻的格子坐标合法,且是陆地,就可以继续搜索。

在深度优先搜索的时候要注意避免重复遍历。我们可以把已经遍历过的陆地改成 2,这样遇到 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
class Solution {
public:
void dfs(vector<vector<char>>& grid, int i, int j) {
if ( i < 0 || i > grid.size()-1 || j < 0 || j > grid[0].size()-1 || grid[i][j] != '1')
return;
grid[i][j] = '0';
dfs(grid, i+1, j);
dfs(grid, i-1, j);
dfs(grid, i, j+1);
dfs(grid, i, j-1);
}
int numIslands(vector<vector<char>>& grid) {
if (grid.size() == 0 || grid[0].size() == 0)
return 0;
int count = 0;
for( int i = 0; i < grid.size(); i ++)
for( int j = 0; j < grid[0].size(); j ++) {
if (grid[i][j] == '1') {
dfs(grid, i, j);
count ++;
}
}
return count;
}
};