Hao Yu's blog

The program monkey was eaten by the siege lion.

0%

Leetcode401. Binary Watch

A binary watch has 4 LEDs on the top which represent the hours (0-11), and the 6 LEDs on the bottom represent the minutes (0-59). Each LED represents a zero or one, with the least significant bit on the right.

Given a non-negative integer n which represents the number of LEDs that are currently on, return all possible times the watch could represent.

Example:

1
2
Input: n = 1
Return: ["1:00", "2:00", "4:00", "8:00", "0:01", "0:02", "0:04", "0:08", "0:16", "0:32"]

Note:

  • The order of output does not matter.
  • The hour must not contain a leading zero, for example “01:00” is not valid, it should be “1:00”.
  • The minute must be consist of two digits and may contain a leading zero, for example “10:2” is not valid, it should be “10:02”.

首先先明白二进制手表的含义,把1,2,4,8转化为四位的二进制就是0001, 0010, 0100,1000, 9点时亮1和8,是1001。分钟数也是同理。
其次表示小时的数值只有0-11,表示分钟的数值只有0-59。先分别对小时跟分钟的数值进行预处理,按照包含而二进制中包含1的个数分开保存小时数值的字符串跟分钟数值的字符串。

用bitset可以方便地记下来每个数字有几个二进制1,这样可以简单地做出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
vector<string> readBinaryWatch(int num) {
vector<string> res;
for(int i = 0; i < 12; i ++) {
bitset<4> h(i);
for(int j = 0; j < 60; j ++) {
bitset<6> m(j);
if(h.count() + m.count() == num)
res.push_back(to_string(i) + (j < 10? ":0": ":") + to_string(j));
}
}
return res;
}
};

基本还是一道DFS的题目,分别在小时和分钟上做DFS,给定几个灯亮,然后把这些亮的灯枚举分给小时和分钟.需要注意的是剪枝,即小时必须小于12,分钟小于60。然后将小时和分钟组合即可.还有一个需要注意的是如果分钟只有1位数,还要补0.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public:
void DFS(int len, int k, int curIndex, int val, vector<int>& vec)
{
if(k==0 && len==4 && val < 12) vec.push_back(val);
if(k==0 && len==6 && val < 60) vec.push_back(val);
if(curIndex == len || k == 0) return;
DFS(len, k, curIndex+1, val, vec);
val += pow(2, curIndex), k--, curIndex++;
DFS(len, k, curIndex, val, vec);
}

vector<string> readBinaryWatch(int num) {
vector<string> ans;
for(int i = max(0, num-6); i <= min(4, num); i++)
{
vector<int> vec1, vec2;
DFS(4, i, 0, 0, vec1), DFS(6, num-i, 0, 0, vec2);
for(auto val1: vec1)
for(auto val2: vec2)
{
string str = (to_string(val2).size()==1?"0":"") + to_string(val2);
ans.push_back(to_string(val1)+":"+ str);
}
}
return ans;
}
};

Leetcode402. Remove K Digits

Given string num representing a non-negative integer num, and an integer k, return the smallest possible integer after removing k digits from num.

Example 1:

1
2
3
Input: num = "1432219", k = 3
Output: "1219"
Explanation: Remove the three digits 4, 3, and 2 to form the new number 1219 which is the smallest.

Example 2:

1
2
3
Input: num = "10200", k = 1
Output: "200"
Explanation: Remove the leading 1 and the number is 200. Note that the output must not contain leading zeroes.

Example 3:

1
2
3
Input: num = "10", k = 2
Output: "0"
Explanation: Remove all the digits from the number and it is left with nothing which is 0.

这道题让我们将给定的数字去掉k位,要使得留下来的数字最小。

首先来考虑,若数字是递增的话,比如 1234,那么肯定是要从最后面移除最大的数字。若是乱序的时候,比如 1324,若只移除一个数字,移除谁呢?这个例子比较简单,我们一眼可以看出是移除3,变成 124 是最小。这里我们维护一个递增栈,只要发现当前的数字小于栈顶元素的话,就将栈顶元素移除,比如点那个遍历到2的时候,栈里面有1和3,此时2小于栈顶元素3,那么将3移除即可。为何一定要移除栈顶元素呢,后面说不定有更大的数字呢?这是因为此时栈顶元素在高位上,就算后面的数字再大,也是在低位上,我们只有将高位上的数字尽可能的变小,才能使整个剩下的数字尽可能的小。

我们开始遍历给定数字 num 的每一位,对于当前遍历到的数字c,进行如下 while 循环,如果 res 不为空,且k大于0,且 res 的最后一位大于c,那么应该将 res 的最后一位移去,且k自减1。当跳出 while 循环后,我们将c加入 res 中,最后将 res 的大小重设为 n-k。根据题目中的描述,可能会出现 “0200” 这样不符合要求的情况,所以我们用一个 while 循环来去掉前面的所有0,然后返回时判断是否为空,为空则返回 “0”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
string removeKdigits(string num, int k) {
string res = "";
int len = num.length();
for (int i = 0; i < len; i ++) {
while (k > 0 && res.length() > 0 && num[i] < res.back()) {
res.pop_back();
k --;
}
if (res.length() > 0 || num[i] != '0')
res += num[i];
}
while(res.size() > 0 && k--)
res.pop_back();
return res.empty() ? "0" : res;
}
};

Leetcode403. Frog Jump

A frog is crossing a river. The river is divided into x units and at each unit there may or may not exist a stone. The frog can jump on a stone, but it must not jump into the water.

Given a list of stones’ positions (in units) in sorted ascending order, determine if the frog is able to cross the river by landing on the last stone. Initially, the frog is on the first stone and assume the first jump must be 1 unit.

If the frog’s last jump was k units, then its next jump must be either k - 1, k , or k + 1 units. Note that the frog can only jump in the forward direction.

Note:

  • The number of stones is ≥ 2 and is < 1,100.
  • Each stone’s position will be a non-negative integer < 231.
  • The first stone’s position is always 0.

Example 1:

1
2
3
4
5
6
7
8
9
10
11
[0,1,3,5,6,8,12,17]

There are a total of 8 stones.
The first stone at the 0th unit, second stone at the 1st unit,
third stone at the 3rd unit, and so on...
The last stone at the 17th unit.

Return true. The frog can jump to the last stone by jumping
1 unit to the 2nd stone, then 2 units to the 3rd stone, then
2 units to the 4th stone, then 3 units to the 6th stone,
4 units to the 7th stone, and 5 units to the 8th stone.

Example 2:

1
2
3
4
[0,1,2,3,4,8,9,11]

Return false. There is no way to jump to the last stone as
the gap between the 5th and 6th stone is too large.

题目中说青蛙如果上一次跳了k距离,那么下一次只能跳 k-1, k, 或 k+1 的距离,那么青蛙跳到某个石头上可能有多种跳法,由于这道题只是让判断青蛙是否能跳到最后一个石头上,并没有让返回所有的路径,这样就降低了一些难度。我们可以用递归来做,这里维护一个 HashMap,建立青蛙在 pos 位置和拥有 jump 跳跃能力时是否能跳到对岸。为了能用一个变量同时表示 pos 和 jump,可以将jump左移很多位并或上 pos,由于题目中对于位置大小有限制,所以不会产生冲突。首先判断 pos 是否已经到最后一个石头了,是的话直接返回 true;然后看当前这种情况是否已经出现在 HashMap 中,是的话直接从 HashMap 中取结果。如果没有,就遍历余下的所有石头,对于遍历到的石头,计算到当前石头的距离dist,如果距离小于 jump-1,接着遍历下一块石头;如果 dist 大于 jump+1,说明无法跳到下一块石头,m[key] 赋值为 false,并返回 false;如果在青蛙能跳到的范围中,调用递归函数,以新位置i为 pos,距离 dist 为 jump,如果返回 true 了,给 m[key] 赋值为 true,并返回 true。如果结束遍历给 m[key] 赋值为 false,并返回 false,参加代码如下:

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
bool canCross(vector<int>& stones) {
unordered_map<int, bool> m;
return helper(stones, 0, 0, m);
}
bool helper(vector<int>& stones, int pos, int jump, unordered_map<int, bool>& m) {
int n = stones.size(), key = pos | jump << 11;
if (pos >= n - 1) return true;
if (m.count(key)) return m[key];
for (int i = pos + 1; i < n; ++i) {
int dist = stones[i] - stones[pos];
if (dist < jump - 1) continue;
if (dist > jump + 1) return m[key] = false;
if (helper(stones, i, dist, m)) return m[key] = true;
}
return m[key] = false;
}
};

我们也可以用迭代的方法来解,用一个 HashMap 来建立每个石头和在该位置上能跳的距离之间的映射,建立一个一维 dp 数组,其中 dp[i] 表示在位置为i的石头青蛙的弹跳力(只有青蛙能跳到该石头上,dp[i] 才大于0),由于题目中规定了第一个石头上青蛙跳的距离必须是1,为了跟后面的统一,对青蛙在第一块石头上的弹跳力初始化为0(虽然为0,但是由于题目上说青蛙最远能到其弹跳力+1的距离,所以仍然可以到达第二块石头)。这里用变量k表示当前石头,然后开始遍历剩余的石头,对于遍历到的石头i,来找到刚好能跳到i上的石头k,如果i和k的距离大于青蛙在k上的弹跳力+1,则说明青蛙在k上到不了i,则k自增1。从k遍历到i,如果青蛙能从中间某个石头上跳到i上,更新石头i上的弹跳力和最大弹跳力。这样当循环完成后,只要检查最后一个石头上青蛙的最大弹跳力是否大于0即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
bool canCross(vector<int>& stones) {
unordered_map<int, unordered_set<int>> m;
vector<int> dp(stones.size(), 0);
m[0].insert(0);
int k = 0;
for (int i = 1; i < stones.size(); ++i) {
while (dp[k] + 1 < stones[i] - stones[k]) ++k;
for (int j = k; j < i; ++j) {
int t = stones[i] - stones[j];
if (m[j].count(t - 1) || m[j].count(t) || m[j].count(t + 1)) {
m[i].insert(t);
dp[i] = max(dp[i], t);
}
}
}
return dp.back() > 0;
}
};

Leetcode404. Sum of Left Leaves

Find the sum of all left leaves in a given binary tree.

Example:

1
2
3
4
5
6
    3
/ \
9 20
/ \
15 7
There are two left leaves in the binary tree, with values 9 and 15 respectively. Return 24.

求二叉树的所有左叶子节点的和,判断是不是左叶子节点,加到对列中。
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 sumOfLeftLeaves(TreeNode* root) {
if(!root)
return 0;
if(!root->left && !root->right)
return 0;
queue<TreeNode*> q;
int res = 0;
q.push(root);
while(!q.empty()) {
TreeNode* temp = q.front();
q.pop();
if(temp->left)
q.push(temp->left);
if(temp->right && (temp->right->left || temp->right->right))
q.push(temp->right);
if(!temp->left && !temp->right)
res += temp->val;
}
return res;
}
};

Leetcode405. Convert a Number to Hexadecimal

Given an integer, write an algorithm to convert it to hexadecimal. For negative integer, two’s complement method is used.

Note:

  1. All letters in hexadecimal (a-f) must be in lowercase.
  2. The hexadecimal string must not contain extra leading 0s. If the number is zero, it is represented by a single zero character ‘0’; otherwise, the first character in the hexadecimal string will not be the zero character.
  3. The given number is guaranteed to fit within the range of a 32-bit signed integer.
  4. You must not use any method provided by the library which converts/formats the number to hex directly.

Example 1:

1
2
Input: 26
Output: "1a"

Example 2:
1
2
Input: -1
Output: "ffffffff"

十进制转十六进制,简单。
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 toHex(int num) {
string res = "";
if(num == 0)
return "0";
char digits[] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
if(num < 0) {
num = abs(num);
num = ~num + 1;
}
int count = 8;
while(count --) {
int temp = 15 & num;
num = num >> 4;
res = digits[temp] + res;
if(num == 0)
break;
cout << digits[temp] << " " << num << endl;
}
return res;
}
};

Leetcode406. Queue Reconstruction by Height

You are given an array of people, people, which are the attributes of some people in a queue (not necessarily in order). Each people[i] = [hi, ki] represents the ith person of height hi with exactly ki other people in front who have a height greater than or equal to hi.

Reconstruct and return the queue that is represented by the input array people. The returned queue should be formatted as an array queue, where queue[j] = [hj, kj] is the attributes of the jth person in the queue (queue[0] is the person at the front of the queue).

Example 1:

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

Explanation:

  • Person 0 has height 5 with no other people taller or the same height in front.
  • Person 1 has height 7 with no other people taller or the same height in front.
  • Person 2 has height 5 with two persons taller or the same height in front, which is person 0 and 1.
  • Person 3 has height 6 with one person taller or the same height in front, which is person 1.
  • Person 4 has height 4 with four people taller or the same height in front, which are people 0, 1, 2, and 3.
  • Person 5 has height 7 with one person taller or the same height in front, which is person 1.
  • Hence [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] is the reconstructed queue.

这道题给了我们一个队列,队列中的每个元素是一个 pair,分别为身高和前面身高不低于当前身高的人的个数,让我们重新排列队列,使得每个 pair 的第二个参数都满足题意。首先来看一种超级简洁的方法,给队列先排个序,按照身高高的排前面,如果身高相同,则第二个数小的排前面。然后新建一个空的数组,遍历之前排好序的数组,然后根据每个元素的第二个数字,将其插入到 res 数组中对应的位置,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
sort(people.begin(), people.end(), [](vector<int>& a, vector<int>& b) {
return a[0] > b[0] || (a[0] == b[0] && a[1] < b[1]);
});
vector<vector<int>> res;
for (auto a : people) {
res.insert(res.begin() + a[1], a);
}
return res;
}
};

Leetcode407. Trapping Rain Water II

Given an m x n integer matrix heightMap representing the height of each unit cell in a 2D elevation map, return the volume of water it can trap after raining.

Example 1:

1
2
3
4
5
Input: heightMap = [[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]]
Output: 4
Explanation: After the rain, water is trapped between the blocks.
We have two small pounds 1 and 3 units trapped.
The total volume of water trapped is 4.

Example 2:

1
2
Input: heightMap = [[3,3,3,3,3],[3,2,2,2,3],[3,2,1,2,3],[3,2,2,2,3],[3,3,3,3,3]]
Output: 10

这道三维的,我们需要用 BFS 来做,解法思路很巧妙,下面我们就以题目中的例子来进行分析讲解,多图预警,手机流量党慎入:

首先我们应该能分析出,能装水的底面肯定不能在边界上,因为边界上的点无法封闭,那么所有边界上的点都可以加入 queue,当作 BFS 的启动点,同时我们需要一个二维数组来标记访问过的点,访问过的点我们用红色来表示,那么如下图所示:

我们再想想,怎么样可以成功的装进去水呢,是不是周围的高度都应该比当前的高度高,形成一个凹槽才能装水,而且装水量取决于周围最小的那个高度,有点像木桶原理的感觉,那么为了模拟这种方法,我们采用模拟海平面上升的方法来做,我们维护一个海平面高度 mx,初始化为最小值,从1开始往上升,那么我们 BFS 遍历的时候就需要从高度最小的格子开始遍历,那么我们的 queue 就不能使用普通队列了,而是使用优先级队列,将高度小的放在队首,最先取出,这样我们就可以遍历高度为1的三个格子,用绿色标记出来了,如下图所示:

如上图所示,向周围 BFS 搜索的条件是不能越界,且周围格子未被访问,那么可以看出上面的第一个和最后一个绿格子无法进一步搜索,只有第一行中间那个绿格子可以搜索,其周围有一个灰格子未被访问过,将其加入优先队列 queue 中,然后标记为红色,如下图所示:

那么优先队列 queue 中高度为1的格子遍历完了,此时海平面上升1,变为2,此时我们遍历优先队列 queue 中高度为2的格子,有3个,如下图绿色标记所示:

我们发现这三个绿格子周围的格子均已被访问过了,所以不做任何操作,海平面继续上升,变为3,遍历所有高度为3的格子,如下图绿色标记所示:

由于我们没有特别声明高度相同的格子在优先队列 queue 中的顺序,所以应该是随机的,其实谁先遍历到都一样,对结果没啥影响,我们就假设第一行的两个绿格子先遍历到,那么那么周围各有一个灰格子可以遍历,这两个灰格子比海平面低了,可以存水了,把存水量算出来加入结果 res 中,如下图所示:

上图中这两个遍历到的蓝格子会被加入优先队列 queue 中,由于它们的高度小,所以下一次从优先队列 queue 中取格子时,它们会被优先遍历到,那么左边的那个蓝格子进行BFS搜索,就会遍历到其左边的那个灰格子,由于其高度小于海平面,也可以存水,将存水量算出来加入结果 res 中,如下图所示:

等两个绿格子遍历结束了,它们会被标记为红色,蓝格子遍历会先被标记红色,然后加入优先队列 queue 中,由于其周围格子全变成红色了,所有不会有任何操作,如下图所示:

此时所有的格子都标记为红色了,海平面继续上升,继续遍历完优先队列 queue 中的格子,不过已经不会对结果有任何影响了,因为所有的格子都已经访问过了,此时等循环结束后返回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:
int trapRainWater(vector<vector<int>>& heights) {
if (heights.size() == 0)
return 0;
int m = heights.size(), n = heights[0].size(), res = 0;
vector<vector<bool> > visited(m, vector<bool>(n, false));
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
vector<vector<int>> dir{{0,-1},{-1,0},{0,1},{1,0}};

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) {
q.push({heights[i][j], i*n+j});
visited[i][j] = true;
}
int max_height = 0;
while(!q.empty()) {
pair<int, int> t = q.top();
q.pop();
int h = t.first, r = t.second / n, c = t.second % n;
max_height = max(max_height, h);
for (int i = 0; i < 4; i ++) {
int x = r + dir[i][0];
int y = c + dir[i][1];
if (x < 0 || x >= m || y < 0 || y >= n || visited[x][y])
continue;
visited[x][y] = true;
if (heights[x][y] < max_height)
res = res + (max_height - heights[x][y]);
q.push({heights[x][y], x*n+y});
}
}
return res;
}
};

Leetcode409. Longest Palindrome

Given a string which consists of lowercase or uppercase letters, find the length of the longest palindromes that can be built with those letters.

This is case sensitive, for example “Aa” is not considered a palindrome here.

Note:
Assume the length of given string will not exceed 1,010.

Example:

1
2
3
4
Input: "abccccdd"
Output: 7
Explanation:
One longest palindrome that can be built is "dccaccd", whose length is 7.

先统计每个字母的个数,然后如果这个字母是偶数个的话,可以放到回文里,如果是奇数的话,先放进去个数减一个,然后如果现在回文长度是偶数,那还可以加一个。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int longestPalindrome(string s) {
int ch[128] = {0};
for(char c : s)
ch[c]++;
int ans = 0;
for(int i : ch) {
ans += (i / 2 * 2);
if(ans % 2== 0 && i % 2 == 1)
ans ++;
}
return ans;
}
};

Leetcode410. Split Array Largest Sum

Given an array which consists of non-negative integers and an integer m , you can split the array into m non-empty continuous subarrays. Write an algorithm to minimize the largest sum among these m subarrays.

Note: Given m satisfies the following constraint: 1 ≤ m ≤ length(nums) ≤ 14,000.

Examples:

1
2
3
4
5
6
Input:
nums = [7,2,5,10,8]
m = 2

Output:
18

Explanation:

  • There are four ways to split nums into two subarrays.
  • The best way is to split it into [7,2,5] and [10,8],
  • where the largest sum among the two subarrays is only 18.

这道题给了我们一个非负数的数组 nums 和一个整数m,让把数组分割成m个非空的连续子数组,让最小化m个子数组中的最大值。

首先来分析,如果m和数组 nums 的个数相等,那么每个数组都是一个子数组,所以返回 nums 中最大的数字即可,如果m为1,那么整个 nums 数组就是一个子数组,返回 nums 所有数字之和,所以对于其他有效的m值,返回的值必定在上面两个值之间,所以可以用二分搜索法来做。用一个例子来分析,nums = [1, 2, 3, 4, 5], m = 3,将 left 设为数组中的最大值5,right 设为数字之和 15,然后算出中间数为 10,接下来要做的是找出和最大且小于等于 10 的子数组的个数,[1, 2, 3, 4], [5],可以看到无法分为3组,说明 mid 偏大,所以让 right=mid,然后再次进行二分查找,算出 mid=7,再次找出和最大且小于等于7的子数组的个数,[1,2,3], [4], [5],成功的找出了三组,说明 mid 还可以进一步降低,让 right=mid,再次进行二分查找,算出 mid=6,再次找出和最大且小于等于6的子数组的个数,[1,2,3], [4], [5],成功的找出了三组,尝试着继续降低 mid,让 right=mid,再次进行二分查找,算出 mid=5,再次找出和最大且小于等于5的子数组的个数,[1,2], [3], [4], [5],发现有4组,此时的 mid 太小了,应该增大 mid,让 left=mid+1,此时 left=6,right=6,循环退出了,返回 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
class Solution {
public:
int splitArray(vector<int>& nums, int m) {
long left = 0, right = 0;
for (int i = 0; i < nums.size(); ++i) {
left = max(left, (long)nums[i]);
right += nums[i];
}
while (left < right) {
long long mid = left + (right - left) / 2;
if (can_split(nums, m, mid)) right = mid;
else left = mid + 1;
}
return right;
}
bool can_split(vector<int>& nums, long m, long sum) {
long cnt = 1, curSum = 0;
for (int i = 0; i < nums.size(); ++i) {
curSum += nums[i];
if (curSum > sum) {
curSum = nums[i];
++cnt;
if (cnt > m) return false;
}
}
return true;
}
};

上面的解法相对来说比较难想,在热心网友 perthblank 的提醒下,再来看一种 DP 的解法,相对来说,这种方法应该更容易理解一些。建立一个二维数组 dp,其中 dp[i][j] 表示将数组中前j个数字分成i组所能得到的最小的各个子数组中最大值,初始化为整型最大值,如果无法分为i组,那么还是保持为整型最大值。为了能快速的算出子数组之和,还是要建立累计和数组,难点就是在于推导状态转移方程了。

来分析一下,如果前j个数字要分成i组,那么i的范围是什么,由于只有j个数字,如果每个数字都是单独的一组,那么最多有j组;如果将整个数组看为一个整体,那么最少有1组,所以i的范围是[1, j],所以要遍历这中间所有的情况,假如中间任意一个位置k,dp[i-1][k] 表示数组中前k个数字分成 i-1 组所能得到的最小的各个子数组中最大值,而 sums[j]-sums[k] 就是后面的数字之和,取二者之间的较大值,然后和 dp[i][j] 原有值进行对比,更新 dp[i][j] 为二者之中的较小值,这样k在 [1, j] 的范围内扫过一遍,dp[i][j] 就能更新到最小值,最终返回 dp[m][n] 即可,博主认为这道题所用的思想应该是之前那道题 Reverse Pairs 中解法二中总结的分割重现关系 (Partition Recurrence Relation),由此看来很多问题的本质都是一样,但是披上华丽的外衣,难免会让人有些眼花缭乱了,参见代码如下:

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 splitArray(vector<int>& nums, int m) {
int n = nums.size();
vector<long> sums(n + 1);
vector<vector<long>> dp(m + 1, vector<long>(n + 1, LONG_MAX));
dp[0][0] = 0;
for (int i = 1; i <= n; ++i) {
sums[i] = sums[i - 1] + nums[i - 1];
}
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
for (int k = i - 1; k < j; ++k) {
long val = max(dp[i - 1][k], sums[j] - sums[k]);
dp[i][j] = min(dp[i][j], val);
}
}
}
return dp[m][n];
}
};

Leetcode412. Fizz Buzz

Write a program that outputs the string representation of numbers from 1 to n.

But for multiples of three it should output “Fizz” instead of the number and for the multiples of five output “Buzz”. For numbers which are multiples of both three and five output “FizzBuzz”.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
n = 15,
Return:
[
"1",
"2",
"Fizz",
"4",
"Buzz",
"Fizz",
"7",
"8",
"Fizz",
"Buzz",
"11",
"Fizz",
"13",
"14",
"FizzBuzz"
]

太简单了浪费时间。
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> fizzBuzz(int n) {
vector<string> res;
if(n == 0)
return res;
for(int i = 0; i < n; i ++)
{
if((i + 1) % 3 == 0 && (i + 1) % 5 == 0)
res.push_back("FizzBuzz");
else if((i + 1) % 3 == 0)
res.push_back("Fizz");
else if((i + 1) % 5 == 0)
res.push_back("Buzz");
else
res.push_back(to_string(i + 1));
}
return res;
}
};

Leetcode413. Arithmetic Slices

An integer array is called arithmetic if it consists of at least three elements and if the difference between any two consecutive elements is the same.

For example, [1,3,5,7,9], [7,7,7,7], and [3,-1,-5,-9] are arithmetic sequences. Given an integer array nums, return the number of arithmetic subarrays of nums.

A subarray is a contiguous subsequence of the array.

Example 1:

1
2
3
Input: nums = [1,2,3,4]
Output: 3
Explanation: We have 3 arithmetic slices in nums: [1, 2, 3], [2, 3, 4] and [1,2,3,4] itself.

Example 2:

1
2
Input: nums = [1]
Output: 0

这道题让我们算一种算数切片,说白了就是找等差数列,限定了等差数列的长度至少为3,那么[1,2,3,4]含有3个长度至少为3的算数切片,我们再来看[1,2,3,4,5]有多少个呢:
len = 3: [1,2,3], [2,3,4], [3,4,5]

len = 4: [1,2,3,4], [2,3,4,5]

len = 5: [1,2,3,4,5]

那么我们可以归纳出规律,长度为n的等差数列有1个,长度为n-1的等差数列有2个,… ,长度为3的等差数列有 n-2 个,那么总共就是 1 + 2 + 3 + … + n-2 ,此时就要祭出高斯求和公式了,长度为n的等差数列中含有长度至少为3的算数切片的个数为(n-1)(n-2)/2,那么题目就变成了找原数组中等差数列的长度,然后带入公式去算个数即可,参见代码如下:

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

Leetcode414. Third Maximum Number

Given a non-empty array of integers, return the third maximum number in this array. If it does not exist, return the maximum number. The time complexity must be in O(n).

Example 1:

1
2
3
Input: [3, 2, 1]
Output: 1
Explanation: The third maximum is 1.

Example 2:
1
2
3
Input: [1, 2]
Output: 2
Explanation: The third maximum does not exist, so the maximum (2) is returned instead.

Example 3:
1
2
3
4
Input: [2, 2, 3, 1]
Output: 1
Explanation: Note that the third maximum here means the third maximum distinct number.
Both numbers with value 2 are both considered as second maximum.

遍历数组,通过跟三个变量(max, mid, 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
class Solution {
public:
int thirdMax(vector<int>& nums) {
long min = LONG_MIN, mid = LONG_MIN, max = LONG_MIN;
int count = 0;
for(int i = 0; i < nums.size(); i ++) {
if(nums[i] == max || nums[i] == mid)
continue;
if(nums[i] > max) {
min = mid;
mid = max;
max = nums[i];
count ++;
}
else if(nums[i] > mid) {
min = mid;
mid = nums[i];
count ++;
}
else if(nums[i] >= min) {
min = nums[i];
count ++;
}
}

if(count >= 3)
return min;
else return max;
}
};

Leetcode415. Add Strings

Given two non-negative integers num1 and num2 represented as string, return the sum of num1 and num2.

Note:

  • The length of both num1 and num2 is < 5100.
  • Both num1 and num2 contains only digits 0-9.
  • Both num1 and num2 does not contain any leading zero.
  • You must not use any built-in BigInteger library or convert the inputs to integer directly.

简单模拟,做的及其纠结。

1
2
3
4
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 addStrings(string nums1, string nums2) {
if(nums1.length() < nums2.length()) {
string temp = nums1;
nums1 = nums2;
nums2 = temp;
}
int i, j;
for(i = nums1.length()-1, j = nums2.length()-1; i >= 0 && j >= 0; i --, j --) {
nums1[i] = nums1[i] + nums2[j] - 48;
if(nums1[i] > 57) {
if(i == 0)
break;
nums1[i] -= 10;
nums1[i - 1] = nums1[i - 1] + 1;
}
}
if(i < 0) {
i ++;
if(nums1[i] > 57) {
nums1[i] -= 10;
if(i == 0)
nums1 = "1" + nums1;
else
nums1[i - 1] ++;
}
}
else {
while(i >= 0 && nums1[i] > 57) {
nums1[i] -= 10;
if(i == 0)
nums1 = "1" + nums1;
else
nums1[i - 1] ++;
i --;
}
}
return nums1;
}
};

1
2
3
4
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:
string addStrings(string num1, string num2) {
// computing lengths of both strings
int num1length = num1.length() - 1;
int num2length = num2.length()- 1;

// solution string
string sol = "";

// remainder for when summing two numbers
int remainder = 0;

// while both strings haven't been consumed
while (num1length >= 0 || num2length >= 0) {

// get current characters of iteration
int current_num1 = (num1length >= 0) ? num1.at(num1length) - '0' : 0;
int current_num2 = (num2length >= 0) ? num2.at(num2length) - '0' : 0;

// appending sum and remainder from previous to solution string
int sum = current_num1 + current_num2 + remainder;
sol = std::to_string(sum % 10) + sol;

// determining whether there's a remainder for next sum
remainder = (sum > 9) ? 1 : 0;

// decrementing for next addition
num1length--;
num2length--;

}

// append final remainder if there is one
sol = (remainder == 1) ? std::to_string(1) + sol : sol;

return sol;
}
};

Leetcode416. Partition Equal Subset Sum

Given a non-empty array nums containing only positive integers, find if the array can be partitioned into two subsets such that the sum of elements in both subsets is equal.

Example 1:

1
2
3
Input: nums = [1,5,11,5]
Output: true
Explanation: The array can be partitioned as [1, 5, 5] and [11].

Example 2:

1
2
3
Input: nums = [1,2,3,5]
Output: false
Explanation: The array cannot be partitioned into equal sum subsets.

这道题给了我们一个数组,问这个数组能不能分成两个非空子集合,使得两个子集合的元素之和相同。那么想,原数组所有数字和一定是偶数,不然根本无法拆成两个和相同的子集合,只需要算出原数组的数字之和,然后除以2,就是 target,那么问题就转换为能不能找到一个非空子集合,使得其数字之和为 target。开始博主想的是遍历所有子集合,算和,但是这种方法无法通过 OJ 的大数据集合。于是乎,动态规划 Dynamic Programming 就是不二之选。定义一个一维的 dp 数组,其中 dp[i] 表示原数组是否可以取出若干个数字,其和为i。那么最后只需要返回 dp[target] 就行了。

初始化 dp[0] 为 true,由于题目中限制了所有数字为正数,就不用担心会出现和为0或者负数的情况。关键问题就是要找出状态转移方程了,需要遍历原数组中的数字,对于遍历到的每个数字 nums[i],需要更新 dp 数组,既然最终目标是想知道 dp[target] 的 boolean 值,就要想办法用数组中的数字去凑出 target,因为都是正数,所以只会越加越大,加上 nums[i] 就有可能会组成区间 [nums[i], target] 中的某个值,那么对于这个区间中的任意一个数字j,如果 dp[j - nums[i]] 为 true 的话,说明现在已经可以组成 j-nums[i] 这个数字了,再加上 nums[i],就可以组成数字j了,那么 dp[j] 就一定为 true。如果之前 dp[j] 已经为 true 了,当然还要保持 true,所以还要 ‘或’ 上自身,于是状态转移方程如下:

1
dp[j] = dp[j] || dp[j - nums[i]]         (nums[i] <= j <= target)

有了状态转移方程,就可以写出代码了,这里需要特别注意的是,第二个 for 循环一定要从 target 遍历到 nums[i],而不能反过来,想想为什么呢?因为如果从 nums[i] 遍历到 target 的话,假如 nums[i]=1 的话,那么 [1, target] 中所有的 dp 值都是 true,因为 dp[0] 是 true,dp[1] 会或上 dp[0],为 true,dp[2] 会或上 dp[1],为 true,依此类推,完全使的 dp 数组失效了,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = accumulate(nums.begin(), nums.end(), 0), target = sum >> 1;
if (sum & 1) return false;
vector<bool> dp(target + 1, false);
dp[0] = true;
for (int num : nums) {
for (int i = target; i >= num; --i) {
dp[i] = dp[i] || dp[i - num];
}
}
return dp[target];
}
};

Leetcode417. Pacific Atlantic Water Flow

There is an m x n rectangular island that borders both the Pacific Ocean and Atlantic Ocean. The Pacific Ocean touches the island’s left and top edges, and the Atlantic Ocean touches the island’s right and bottom edges.

The island is partitioned into a grid of square cells. You are given an m x n integer matrix heights where heights[r][c] represents the height above sea level of the cell at coordinate (r, c).

The island receives a lot of rain, and the rain water can flow to neighboring cells directly north, south, east, and west if the neighboring cell’s height is less than or equal to the current cell’s height. Water can flow from any cell adjacent to an ocean into the ocean.

Return a 2D list of grid coordinates result where result[i] = [ri, ci] denotes that rain water can flow from cell (ri, ci) to both the Pacific and Atlantic oceans.

Example 1:

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

Example 2:

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

上面一条边和左边一条边代表的是太平洋,右边一条边和下边一条边代表的是大西洋。现在告诉你水往低处流,问哪些位置的水能同时流进太平洋和大西洋?

直接DFS求解。一般来说DFS需要有固定的起点,但是对于这个题,四条边界的每个位置都算作起点。

使用两个二维数组,分别记录每个位置的点能不能到达太平洋和大西洋。然后对4条边界进行遍历,看这些以这些边为起点能不能所有的地方。注意了,因为是从边界向中间去寻找,所以,这个时候是新的点要比当前的点海拔高才行。

最坏情况下的时间复杂度是O((M+N)*MN),空间复杂度是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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class Solution {
public:
vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
int m = heights.size();
if (m == 0)
return {};
int n = heights[0].size();
vector<vector<int>> res;
vector<vector<bool>> p_flags(m, vector<bool>(n ,false));
vector<vector<bool>> a_flags(m, vector<bool>(n ,false));

for (int i = 0; i < m; i ++) {
dfs(heights, p_flags, i, 0);
dfs(heights, a_flags, i, n-1);
}
for (int i = 0; i < n; i ++) {
dfs(heights, p_flags, 0, i);
dfs(heights, a_flags, m-1, i);
}

for (int i = 0; i < m; i ++)
for (int j = 0; j < n; j ++)
if (p_flags[i][j] && a_flags[i][j]) {
vector<int> t;
t.push_back(i);
t.push_back(j);
res.push_back(t);
}
return res;
}

void dfs(vector<vector<int>>& heights, vector<vector<bool>>& flags, int i, int j) {
int m = heights.size(), n = heights[0].size();
vector<pair<int, int>> dirs = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
flags[i][j] = true;
for (int ii = 0; ii < 4; ii ++) {
int x = i + dirs[ii].first;
int y = j + dirs[ii].second;
if (x >= 0 && x < m && y >= 0 && y < n && !flags[x][y] && heights[x][y] >= heights[i][j]) {
flags[x][y] = true;
dfs(heights, flags, x, y);
}
}
}
};

Leetcode419. Battleships in a Board

Given an 2D board, count how many battleships are in it. The battleships are represented with ‘X’s, empty slots are represented with ‘.’s. You may assume the following rules:
You receive a valid board, made of only battleships or empty slots.
Battleships can only be placed horizontally or vertically. In other words, they can only be made of the shape 1xN (1 row, N columns) or Nx1 (N rows, 1 column), where N can be of any size.
At least one horizontal or vertical cell separates between two battleships - there are no adjacent battleships.
Example:

1
2
3
X..X
...X
...X

In the above board there are 2 battleships.
Invalid Example:
1
2
3
...X
XXXX
...X

This is an invalid board that you will not receive - as battleships will always have a cell separating between them.
Follow up:
Could you do it in one-pass, using only O(1) extra memory and without modifying the value of the board?

利用最简单的方法找有多少个X块,这里用的遍历很方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int countBattleships(vector<vector<char>>& board) {
int x = board.size();
int y = board[0].size();
int res=0;
for(int i=0;i<x;i++)
for(int j=0;j<y;j++)
if(board[i][j]=='X'){
if(j<y-1 && board[i][j+1]=='X') continue;
if(i<x-1 && board[i+1][j]=='X') continue;
res++;
}
return res;
}
};

Leetcode421. Maximum XOR of Two Numbers in an Array

Given an integer array nums, return the maximum result of nums[i] XOR nums[j], where 0 <= i <= j < n.

Example 1:

1
2
3
Input: nums = [3,10,5,25,2,8]
Output: 28
Explanation: The maximum result is 5 XOR 25 = 28.

Example 2:

1
2
Input: nums = [14,70,53,83,49,91,36,80,92,51,66,70]
Output: 127

Constraints:

  • 1 <= nums.length <= 2 * 105
  • 0 <= nums[i] <= 231 - 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

class Solution {
public:

struct Node {
int son[2];
Node() {
son[0] = son[1] = -1;
}
};

vector<Node> nodes;

int findMaximumXOR(vector<int>& a) {
// 让异或的结果最高位尽可能大
// 从高位到低位考虑,ai当前这个位为0,那希望aj当前位为1
// 用前缀树

if (a.size() == 0)
return 0;

nodes.push_back({});
insert(a.back());

int ret = 0;
for (int i = a.size()-2; i >= 0; i --) {
int ans = query(a[i]);
ret = max(ret, ans);
insert(a[i]);
}
return ret;
}

void insert(int a) {
int id = 0;
for (int i = 31; i >= 0; i --) {
int t = (a >> i) & 1;
if (nodes[id].son[t] == -1) {
nodes.push_back({});
nodes[id].son[t] = nodes.size()-1;
}
id = nodes[id].son[t];
}
}

int query(int x) {
int ret = 0;
int id = 0;
for (int i = 31; i >= 0; i --) {
int b = (x >> i) & 1;
int b2 = b ^ 1;
if (nodes[id].son[b2] != -1) {
id = nodes[id].son[b2];
ret |= (1 << i);
}
else {
id = nodes[id].son[b];
}
}
return ret;
}
};

Leetcode423. Reconstruct Original Digits from English

Given a non-empty string containing an out-of-order English representation of digits 0-9, output the digits in ascending order.

Note:

  • Input contains only lowercase English letters.
  • Input is guaranteed to be valid and can be transformed to its original digits. That means invalid inputs such as “abc” or “zerone” are not permitted.
  • Input length is less than 50,000.

Example 1:

1
2
Input: "owoztneoer"
Output: "012"

Example 2:

1
2
Input: "fviefuro"
Output: "45"

这道题给了我们一串英文字符串,是由表示数字的英文单词组成的,不过字符顺序是打乱的,让我们重建出数字。那么这道题的思路是先要统计出各个字符出现的次数,然后算出每个单词出现的次数,然后就可以重建了。由于题目中限定了输入的字符串一定是有效的,那么不会出现无法成功重建的情况,这里需要用个trick。

我们仔细观察这些表示数字的单词”zero”, “one”, “two”, “three”, “four”, “five”, “six”, “seven”, “eight”, “nine”,我们可以发现有些的单词的字符是独一无二的,比如z,只出现在zero中,还有w,u,x,g这四个单词,分别只出现在two,four,six,eight中,那么这五个数字的个数就可以被确定了,由于含有o的单词有zero,two,four,one,其中前三个都被确定了,那么one的个数也就知道了;由于含有h的单词有eight,three,其中eight个数已知,那么three的个数就知道了;由于含有f的单词有four,five,其中four个数已知,那么five的个数就知道了;由于含有s的单词有six,seven,其中six个数已知,那么seven的个数就知道了;由于含有i的单词有six,eight,five,nine,其中前三个都被确定了,那么nine的个数就知道了。

知道了这些问题就变的容易多了,我们按这个顺序”zero”, “two”, “four”, “six”, “eight”, “one”, “three”, “five”, “seven”, “nine”就能找出所有的个数了,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
string originalDigits(string s) {
string res = "";
vector<string> words{"zero", "two", "four", "six", "eight", "one", "three", "five", "seven", "nine"};
vector<int> nums{0, 2, 4, 6, 8, 1, 3, 5, 7, 9}, counts(26, 0);
vector<char> chars{'z', 'w', 'u', 'x', 'g', 'o', 'h', 'f', 's', 'i'};
for (char c : s) ++counts[c - 'a'];
for (int i = 0; i < 10; ++i) {
int cnt = counts[chars[i] - 'a'];
for (int j = 0; j < words[i].size(); ++j) {
counts[words[i][j] - 'a'] -= cnt;
}
while (cnt--) res += (nums[i] + '0');
}
sort(res.begin(), res.end());
return res;
}
};

Leetcode424. Longest Repeating Character Replacement

Given a string that consists of only uppercase English letters, you can replace any letter in the string with another letter at most k times. Find the length of a longest substring containing all repeating letters you can get after performing the above operations.

Note:
Both the string’s length and k will not exceed 10 4.

Example 1:

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

Output:
4

Explanation:
Replace the two 'A's with two 'B's or vice versa.

Example 2:

1
2
3
4
5
6
7
8
9
Input:
s = "AABABBA", k = 1

Output:
4

Explanation:
Replace the one 'A' in the middle with 'B' and form "AABBBBA".
The substring "BBBB" has the longest repeating letters, which is 4.

这道题给我们了一个字符串,说我们有k次随意置换任意字符的机会,让我们找出最长的重复字符的字符串。我们首先来想,如果没有k的限制,让我们求把字符串变成只有一个字符重复的字符串需要的最小置换次数,那么就是字符串的总长度减去出现次数最多的字符的个数。如果加上k的限制,我们其实就是求满足 (子字符串的长度减去出现次数最多的字符个数)<=k 的最大子字符串长度即可,搞清了这一点,我们也就应该知道怎么用滑动窗口来解了吧。我们用一个变量 start 记录滑动窗口左边界,初始化为0,然后遍历字符串,每次累加出现字符的个数,然后更新出现最多字符的个数,然后我们判断当前滑动窗口是否满足之前说的那个条件,如果不满足,我们就把滑动窗口左边界向右移动一个,并注意去掉的字符要在 counts 里减一,直到满足条件,我们更新结果 res 即可。需要注意的是,当滑动窗口的左边界向右移动了后,窗口内的相同字母的最大个数貌似可能会改变啊,为啥这里不用更新 maxCnt 呢?这是个好问题,原因是此题让求的是最长的重复子串,maxCnt 相当于卡了一个窗口大小,我们并不希望窗口变小,虽然窗口在滑动,但是之前是出现过跟窗口大小相同的符合题意的子串,缩小窗口没有意义,并不会使结果 res 变大,所以我们才不更新 maxCnt 的,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int characterReplacement(string s, int k) {
int res = 0, maxCnt = 0, start = 0;
vector<int> counts(26, 0);
for (int i = 0; i < s.size(); ++i) {
maxCnt = max(maxCnt, ++counts[s[i] - 'A']);
while (i - start + 1 - maxCnt > k) {
--counts[s[start] - 'A'];
++start;
}
res = max(res, i - start + 1);
}
return res;
}
};

Leetcode427. Construct Quad Tree

Given a n * n matrix grid of 0’s and 1’s only. We want to represent the grid with a Quad-Tree.

Return the root of the Quad-Tree representing the grid.

Notice that you can assign the value of a node to True or False when isLeaf is False, and both are accepted in the answer.

A Quad-Tree is a tree data structure in which each internal node has exactly four children. Besides, each node has two attributes:

  • val: True if the node represents a grid of 1’s or False if the node represents a grid of 0’s.
  • isLeaf: True if the node is leaf node on the tree or False if the node has the four children.
1
2
3
4
5
6
7
8
class Node {
public boolean val;
public boolean isLeaf;
public Node topLeft;
public Node topRight;
public Node bottomLeft;
public Node bottomRight;
}

We can construct a Quad-Tree from a two-dimensional area using the following steps:

If the current grid has the same value (i.e all 1’s or all 0’s) set isLeaf True and set val to the value of the grid and set the four children to Null and stop.

Example 1:

1
2
3
4
5
6
Input: grid = [[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0],[1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1],[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0]]
Output: [[0,1],[1,1],[0,1],[1,1],[1,0],null,null,null,null,[1,0],[1,0],[1,1],[1,1]]
Explanation: All values in the grid are not the same. We divide the grid into four sub-grids.
The topLeft, bottomLeft and bottomRight each has the same value.
The topRight have different values so we divide it into 4 sub-grids where each has the same value.
Explanation is shown in the photo below:

这道题让我们根据一个二维数组来建立一棵四叉树,首先整个数组被分成了四等份,左上,左下,和右下部分内的值均相同,那么他们都是一个叶结点,而右上只有再四等分一下,才能使各自部分内的值相同,所以其就不是叶结点,而四等分后的每个区间才是叶结点。题目中限定了N的值一定是2的指数,就是说其如果可分的话,一定可以四等分,而之前说了,只有区间内的值不同时,才需要四等分,否则整体就当作一个叶结点。所以我们需要check四等分区间内的值是否相同,当然,我们可以将二维数组拆分为四个二维数组,但是那样可能不太高效,而且还占用额外空间,一个比较好的选择是用坐标变量来控制等分数组的范围,我们只需要一个起始点坐标,和区间的长度,就可以精确定位一个区间了。

比如说对于例子中的整个二维数组数组来说,知道起始点坐标 (0, 0),还有长度8,就知道表示的是哪个区间。我们可以遍历这个区间上的其他所有的点,跟起点对比,只要有任何点跟起点不相同,则说明该区间是可分的,因为我们前面说了,只有一个区间上所有的值均相同,才能当作一个叶结点。只要有不同,就表示可以四分,那么我们就新建一个结点,这里的左上,左下,右上,和右下四个子结点就需要用过调用递归函数来实现了,实现原理都一样。

对于非叶结点,结点值可以是true或者false都没问题。如果某个区间上所有值均相同,那么就生成一个叶结点,结点值就跟区间值相同,isLeaf是true,四个子结点均为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
class Solution {
public:
Node* construct(vector<vector<int>>& grid) {
return build(grid, 0, 0, grid.size()-1, grid.size()-1);
}

Node* build(vector<vector<int>>& grid, int r0, int c0, int r1, int c1) {
if (r0 > r1 || c0 > c1)
return NULL;
bool isleaf = true;
int val = grid[r0][c0];
for (int i = r0; i <= r1; i ++)
for (int j = c0; j <= c1; j ++)
if (grid[i][j] != val) {
isleaf = false;
break;
}
if (isleaf)
return new Node(val == 1, true, NULL, NULL, NULL, NULL);
int mid1 = (r0+r1) / 2, mid2 = (c0+c1) / 2;
return new Node(false, false,
build(grid, r0, c0, mid1, mid2),
build(grid, r0, mid2+1, mid1, c1),
build(grid, mid1+1, c0, r1, mid2),
build(grid, mid1+1, mid2+1, r1, c1));
}
};

Leetcode429. N-ary Tree Level Order Traversal

Given an n-ary tree, return the level order traversal of its nodes’ values. (ie, from left to right, level by level).

Note:

  • The depth of the tree is at most 1000.
  • The total number of nodes is at most 5000.

这道题给了我们一棵N叉树,让我们对其进行层序遍历。虽说现在每一个结点可能有很多个子结点,但其实处理的思路的都是一样的。子结点放到了一个children数组中,我们访问的时候只要遍历数组就行了。先来看迭代的写法,用到了队列queue来辅助,首先判断root是否为空,为空直接返回空数组,否则加入queue中。然后遍历queue,这里用的trick就是,要加个for循环,要将当前queue中的结点的个数统计下来,因为再加入下一层的结点时,queue的结点个数会增加,而在加入下一层结点之前,当前queue中的结点个数全都属于一层,所以我们要把层与层区分开来,将同一层的结点都放到一个数组out中,之后再放入结果res中,这种层序遍历的思想在迷宫遍历找最短路径的时候应用的也很多,是个必须要掌握的方法呢,参见代码如下:

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>> levelOrder(Node* root) {
if (!root) return {};
vector<vector<int>> res;
queue<Node*> q{{root}};
while (!q.empty()) {
vector<int> out;
for (int i = q.size(); i > 0; --i) {
auto t = q.front(); q.pop();
out.push_back(t->val);
if (!t->children.empty()) {
for (auto a : t->children) q.push(a);
}
}
res.push_back(out);
}
return res;
}
};

Leetcode430. Flatten a Multilevel Doubly Linked List

You are given a doubly linked list which in addition to the next and previous pointers, it could have a child pointer, which may or may not point to a separate doubly linked list. These child lists may have one or more children of their own, and so on, to produce a multilevel data structure, as shown in the example below.

Flatten the list so that all the nodes appear in a single-level, doubly linked list. You are given the head of the first level of the list.

Example 1:

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

Explanation:

The multilevel linked list in the input is as follows:

Example 2:

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

Explanation:

1
2
3
4
5
The input multilevel linked list is as follows:

1---2---NULL
|
3---NULL

这道题给了一个多层的双向链表,让我们压平成为一层的双向链表,题目中给了形象的图例,不难理解题意。根据题目中给的例子,我们可以看出如果某个结点有下一层双向链表,那么下一层双向链表中的结点就要先加入进去,如果下一层链表中某个结点还有下一层,那么还是优先加入下一层的结点,整个加入的机制是DFS的,就是有岔路先走岔路,走到没路了后再返回,这就是深度优先遍历的机制。好,那么既然是DFS,肯定优先考虑递归啦。方法有了,再来看具体怎么递归。由于给定的多层链表本身就是双向的,所以我们只需要把下一层的结点移到第一层即可,那么没有子结点的结点就保持原状,不作处理。只有对于那些有子结点的,我们需要做一些处理,由于子结点链接的双向链表要加到后面,所以当前结点之后要断开,再断开之前,我们用变量 next 指向下一个链表,然后对子结点调用递归函数,我们 suppose 返回的结点已经压平了,那么就只有一层,就相当于要把这一层的结点加到断开的地方,所以需要知道这层的最后一个结点的位置,我们用一个变量 last,来遍历到压平的这一层的末结点。现在就可以开始链接了,首先把子结点链到 cur 的 next,然后把反向指针 prev 也链上。此时 cur 的子结点 child 可以清空,然后压平的这一层的末节点 last 链上之前保存的 next 结点,如果 next 非空,那么链上反向结点 prev。这些操作完成后,我们就已经将压平的这一层完整的加入了之前层断开的地方,继续在之前层往下遍历即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
Node* flatten(Node* head) {
Node *cur = head;
while (cur) {
if (cur->child) {
Node *next = cur->next;
Node *last = cur->child;
while (last->next) last = last->next;
cur->next = cur->child;
cur->next->prev = cur;
cur->child = NULL;
last->next = next;
if (next) next->prev = last;
}
cur = cur->next;
}
return head;
}
};

Leetcode433. Minimum Genetic Mutation

A gene string can be represented by an 8-character long string, with choices from ‘A’, ‘C’, ‘G’, and ‘T’.

Suppose we need to investigate a mutation from a gene string start to a gene string end where one mutation is defined as one single character changed in the gene string.

For example, “AACCGGTT” —> “AACCGGTA” is one mutation. There is also a gene bank bank that records all the valid gene mutations. A gene must be in bank to make it a valid gene string.

Given the two gene strings start and end and the gene bank bank, return the minimum number of mutations needed to mutate from start to end. If there is no such a mutation, return -1.

Note that the starting point is assumed to be valid, so it might not be included in the bank.

Example 1:

1
2
Input: start = "AACCGGTT", end = "AACCGGTA", bank = ["AACCGGTA"]
Output: 1

Example 2:

1
2
Input: start = "AACCGGTT", end = "AAACGGTA", bank = ["AACCGGTA","AACCGCTA","AAACGGTA"]
Output: 2

Example 3:

1
2
Input: start = "AAAAACCC", end = "AACCCCCC", bank = ["AAAACCCC","AAACCCCC","AACCCCCC"]
Output: 3

先建立bank数组的距离场,这里距离就是两个字符串之间不同字符的个数。然后以start字符串为起点,向周围距离为1的点扩散,采用BFS搜索,每扩散一层,level自加1,当扩散到end字符串时,返回当前level即可。注意我们要把start字符串也加入bank中,而且此时我们也知道start的坐标位置,bank的最后一个位置,然后在建立距离场的时候,调用一个count子函数,用来统计输入的两个字符串之间不同字符的个数,注意dist[i][j]和dist[j][i]是相同,所以我们只用算一次就行了。然后我们进行BFS搜索,用一个visited集合来保存遍历过的字符串,注意检测距离的时候,dist[i][j]和dist[j][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
35
36
37
38
39
40
41
42
class Solution {
public:
int minMutation(string start, string end, vector<string>& bank) {
if (bank.empty())
return -1;
bank.push_back(start);
int res = 0, len = bank.size();
queue<int> q;
vector<vector<int> > dist(len, vector<int>(len, 0));
vector<int> visited(len, 0);
for (int i = 0; i < len; i ++)
for (int j = i+1; j < len; j ++)
dist[i][j] = cal_dist(bank[i], bank[j]);
q.push(len-1);

while(!q.empty()) {
res ++;
int size = q.size();
for (int i = 0; i < size; i ++) {
int t = q.front();
q.pop();
visited[t] = true;
for (int j = 0; j < len; j ++) {
if ((dist[t][j] != 1 && dist[j][t] != 1) || visited[j])
continue;
q.push(j);
if (bank[j] == end)
return res;
}
}
}
return -1;
}

int cal_dist(string a, string b) {
int cnt = 0, len = a.length();
for (int i = 0; i < len; i ++)
if (a[i] != b[i])
cnt ++;
return cnt;
}
};

Leetcode434. Number of Segments in a String

Count the number of segments in a string, where a segment is defined to be a contiguous sequence of non-space characters.

Please note that the string does not contain any non-printable characters.

Example:

1
2
Input: "Hello, my name is John"
Output: 5

判断一个句子中有几个段。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int countSegments(string s) {
int res = 0;
if(s == "" || s == " ")
return 0;
for(int i = 0; i < s.length(); i ++) {
if(s[i] != ' ')
res ++;
while(i < s.length() && s[i] != ' ')
i ++;
}
return res;
}
};

有一种简单做法:
1
2
3
4
5
6
7
8
int countSegments(string s) {
stringstream ss(s);
string word;
int count = 0;
while (ss >> word)
count++;
return count;
}

Leetcode435. Non-overlapping Intervals

Given an array of intervals intervals where intervals[i] = [starti, endi], return the minimum number of intervals you need to remove to make the rest of the intervals non-overlapping.

Example 1:

1
2
3
Input: intervals = [[1,2],[2,3],[3,4],[1,3]]
Output: 1
Explanation: [1,3] can be removed and the rest of the intervals are non-overlapping.

Example 2:

1
2
3
Input: intervals = [[1,2],[1,2],[1,2]]
Output: 2
Explanation: You need to remove two [1,2] to make the rest of the intervals non-overlapping.

这道题给了我们一堆区间,让求需要至少移除多少个区间才能使剩下的区间没有重叠,那么首先要给区间排序,根据每个区间的 start 来做升序排序,然后开始要查找重叠区间,判断方法是看如果前一个区间的 end 大于后一个区间的 start,那么一定是重复区间,此时结果 res 自增1,我们需要删除一个,那么此时究竟该删哪一个呢,为了保证总体去掉的区间数最小,我们去掉那个 end 值较大的区间,而在代码中,我们并没有真正的删掉某一个区间,而是用一个变量 last 指向上一个需要比较的区间,我们将 last 指向 end 值较小的那个区间;如果两个区间没有重叠,那么此时 last 指向当前区间,继续进行下一次遍历,参见代码如下:

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

Leetcode436. Find Right Interval

You are given an array of intervals, where intervals[i] = [starti, endi] and each starti is unique.

The right interval for an interval i is an interval j such that startj >= endi and startj is minimized.

Return an array of right interval indices for each interval i. If no right interval exists for interval i, then put -1 at index i.

Example 1:

1
2
3
Input: intervals = [[1,2]]
Output: [-1]
Explanation: There is only one interval in the collection, so it outputs -1.

Example 2:

1
2
3
4
5
Input: intervals = [[3,4],[2,3],[1,2]]
Output: [-1,0,1]
Explanation: There is no right interval for [3,4].
The right interval for [2,3] is [3,4] since start0 = 3 is the smallest start that is >= end1 = 3.
The right interval for [1,2] is [2,3] since start1 = 2 is the smallest start that is >= end2 = 2.

Example 3:

1
2
3
4
Input: intervals = [[1,4],[2,3],[3,4]]
Output: [-1,2,-1]
Explanation: There is no right interval for [1,4] and [3,4].
The right interval for [2,3] is [3,4] since start2 = 3 is the smallest start that is >= end1 = 3.

这道题给了我们一堆区间,让我们找每个区间的最近右区间,要保证右区间的 start 要大于等于当前区间的 end,由于区间的顺序不能变,所以我们不能给区间排序,我们需要建立区间的 start 和该区间位置之间的映射,由于题目中限定了每个区间的 start 都不同,所以不用担心一对多的情况出现。然后我们把所有的区间的 start 都放到一个数组中,并对这个数组进行降序排序,那么 start 值大的就在数组前面。然后我们遍历区间集合,对于每个区间,我们在数组中找第一个小于当前区间的 end 值的位置,如果数组中第一个数就小于当前区间的 end,那么说明该区间不存在右区间,结果 res 中加入-1;如果找到了第一个小于当前区间 end 的位置,那么往前推一个就是第一个大于等于当前区间 end 的 start,我们在 HashMap 中找到该区间的坐标加入结果 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
class Solution {
public:
vector<int> findRightInterval(vector<vector<int>>& intervals) {
unordered_map<int, int> map;
vector<int> start;
for (int i = 0; i < intervals.size(); i ++) {
map[intervals[i][0]] = i;
start.push_back(intervals[i][0]);
}
sort(start.begin(), start.end());
vector<int> ress;
int len = intervals.size();

for (int i = 0; i < len; i ++) {
int low = 0, high = len-1, mid;
int best = -1;
while(low <= high) {
mid = low + (high-low) / 2;
if (start[mid] < intervals[i][1])
low = mid+1;
else {
best = map[start[mid]];
high = mid-1;
}
}
ress.push_back(best);
}
return ress;
}
};

Leetcode437. Path Sum III

You are given a binary tree in which each node contains an integer value. Find the number of paths that sum to a given value. The path does not need to start or end at the root or a leaf, but it must go downwards (traveling only from parent nodes to child nodes).

The tree has no more than 1,000 nodes and the values are in the range -1,000,000 to 1,000,000.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8
10
/ \
5 -3
/ \ \
3 2 11
/ \ \
3 -2 1
Return 3. The paths that sum to 8 are:
1. 5 -> 3
2. 5 -> 2 -> 1
3. -3 -> 11

这道题让我们求二叉树的路径的和等于一个给定值,说明了这条路径不必要从根节点开始,可以是中间的任意一段,而且二叉树的节点值也是有正有负。那么可以用递归来做,相当于先序遍历二叉树,对于每一个节点都有记录了一条从根节点到当前节点到路径,同时用一个变量 curSum 记录路径节点总和,然后看 curSum 和 sum 是否相等,相等的话结果 res 加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
class Solution {
public:
int ans = 0;

void dfs(TreeNode* root, vector<TreeNode*>& out, int sum, int cur) {
if(root == NULL)
return;
cur += root->val;
if (cur == sum) ++ans;
out.push_back(root);
int t = cur;
for(int i = 0; i < out.size() - 1; i ++) {
t = t - out[i]->val;
if (t == sum)
++ans;
}
dfs(root->left, out, sum, cur);
dfs(root->right, out, sum, cur);
out.pop_back();
}

int pathSum(TreeNode* root, int sum) {
vector<TreeNode*> out;
dfs(root, out, sum, 0);
return ans;
}
};

Leetcode438. Find All Anagrams in a String

Given two strings s and p, return an array of all the start indices of p’s anagrams in s. You may return the answer in any order.

Example 1:

1
2
3
4
5
Input: s = "cbaebabacd", p = "abc"
Output: [0,6]
Explanation:
The substring with start index = 0 is "cba", which is an anagram of "abc".
The substring with start index = 6 is "bac", which is an anagram of "abc".

Example 2:

1
2
3
4
5
6
Input: s = "abab", p = "ab"
Output: [0,1,2]
Explanation:
The substring with start index = 0 is "ab", which is an anagram of "ab".
The substring with start index = 1 is "ba", which is an anagram of "ab".
The substring with start index = 2 is "ab", which is an anagram of "ab".

这道题给了我们两个字符串s和p,让在s中找字符串p的所有变位次的位置,所谓变位次就是字符种类个数均相同但是顺序可以不同的两个词,那么肯定首先就要统计字符串p中字符出现的次数,然后从s的开头开始,每次找p字符串长度个字符,来验证字符个数是否相同,如果不相同出现了直接 break,如果一直都相同了,则将起始位置加入结果 res 中,参见代码如下:(不用unordered_map而是用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
30
31
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
unordered_map<char, int> map, maps;
vector<int> res;
int lens = s.length(), lenp = p.length();
for (int i = 0; i < 26; i ++) {
map['a' + i] = 0;
maps['a' + i] = 0;
}
for (int i = 0; i < lenp; i ++) {
map[p[i]] ++;
}
if (lenp > lens)
return {};
for (int i = 0; i < lenp-1; i ++)
maps[s[i]] ++;
for (int i = lenp-1; i < lens; i ++) {
maps[s[i]] ++;

int j = 0;
for(j = 0; j < 26; j ++)
if (maps['a' + j] != map['a' + j])
break;
if (j == 26)
res.push_back(i-lenp+1);
maps[s[i-lenp+1]] --;
}
return res;
}
};

Leetcode441. Arranging Coins

You have a total of n coins that you want to form in a staircase shape, where every k-th row must have exactly k coins.

Given n, find the total number of full staircase rows that can be formed. n is a non-negative integer and fits within the range of a 32-bit signed integer.

Example 1:

1
2
3
4
5
6
7
n = 5
The coins can form the following rows:
¤
¤ ¤
¤ ¤

Because the 3rd row is incomplete, we return 2.

Example 2:
1
2
3
4
5
6
7
8
9
n = 8

The coins can form the following rows:
¤
¤ ¤
¤ ¤ ¤
¤ ¤

Because the 4th row is incomplete, we return 3.

直接遍历即可,从1开始,如果剩下是数不能构成一行则返回。注意要先判断剩下的数是否满足,而不是累加以后再判断,这样可能会导致溢出。
1
2
3
4
5
6
7
8
9
10
int arrangeCoins(int n) {
int i = 1, ans = n;
if(n == 1)
return 1;
while(ans >= i) {
ans -= i;
i ++;
}
return i-1;
}

前 i 行完整的硬币数量为i * (i + 1) / 2 ,前 i+1 行则为(i + 2) * (i + 1) / 2。所以(i + 1)*i / 2 ≤ n < (i + 2) * (i + 1) / 2,所以sqrt(2n + 0.25) - 1.5 < n ≤ sqrt(2n + 0.25) - 0.5
1
2
3
4
int arrangeCoins(int n)
{
return (int)(sqrt(2 * (double)n + 0.25) - 0.5);
}

Leetcode442. Find All Duplicates in an Array

Given an integer array nums of length n where all the integers of nums are in the range [1, n] and each integer appears once or twice, return an array of all the integers that appears twice.

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

Example 1:

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

Example 2:

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

Example 3:

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

这类问题的一个重要条件就是1 ≤ a[i] ≤ n (n = size of array),不然很难在O(1)空间和O(n)时间内完成。首先来看一种正负替换的方法,这类问题的核心是就是找nums[i]和nums[nums[i] - 1]的关系,我们的做法是,对于每个nums[i],我们将其对应的nums[nums[i] - 1]取相反数,如果其已经是负数了,说明之前存在过,我们将其加入结果res中即可,参见代码如下:

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

本题使用Set的数据结构对数组进行遍历,找到出现两次的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
vector<int> findDuplicates(vector<int>& nums) {
vector<int> res;
set<int> s;
unordered_map<int, bool> flags;
for (int n : nums) {
if (!s.count(n))
s.insert(n);
else
res.push_back(n);
}
return res;
}
};

Leetcode443. String Compression

Given an array of characters, compress it in-place. The length after compression must always be smaller than or equal to the original array. Every element of the array should be a character (not int) of length 1. After you are done modifying the input array in-place, return the new length of the array.

Follow up:
Could you solve it using only O(1) extra space?

Example 1:

1
2
3
4
5
6
7
8
Input:
["a","a","b","b","c","c","c"]

Output:
Return 6, and the first 6 characters of the input array should be: ["a","2","b","2","c","3"]

Explanation:
"aa" is replaced by "a2". "bb" is replaced by "b2". "ccc" is replaced by "c3".

Example 2:
1
2
3
4
5
6
7
8
Input:
["a"]

Output:
Return 1, and the first 1 characters of the input array should be: ["a"]

Explanation:
Nothing is replaced.

Example 3:
1
2
3
4
5
6
7
8
9
Input:
["a","b","b","b","b","b","b","b","b","b","b","b","b"]

Output:
Return 4, and the first 4 characters of the input array should be: ["a","b","1","2"].

Explanation:
Since the character "a" does not repeat, it is not compressed. "bbbbbbbbbbbb" is replaced by "b12".
Notice each digit has it's own entry in the array.

字符串压缩,坑很多,如果是只有一个字符的话就不用压缩,否则的话把字符和字符的数量都加到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
30
31
32
33
34
35
36
class Solution {
public:
int compress(vector<char>& chars) {
int ans = 0;
char c = chars[0], cc = c;
int cur = 1, pointer = 0;
string nums;
for(int i = 1; i < chars.size(); i ++) {
if(chars[i] != c) {
cout << c << " " << cur << endl;
chars[pointer++] = c;
nums = to_string(cur);
if(cur == 1) {
c = chars[i];
continue;
}
for(int i = 0; i < nums.length(); i ++) {
chars[pointer++] = nums[i];
}
cur = 1;
c = chars[i];
}
else
cur ++;
}
chars[pointer++] = c;
if(cur == 1) {
return pointer;
}
nums = to_string(cur);
for(int i = 0; i < nums.length(); i ++) {
chars[pointer++] = nums[i];
}
return pointer;
}
};

Leetcode445. Add Two Numbers II

You are given two non-empty linked lists representing two non-negative integers. The most significant digit comes first and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list.

You may assume the two numbers do not contain any leading zero, except the number 0 itself.

Example 1:

1
2
Input: l1 = [7,2,4,3], l2 = [5,6,4]
Output: [7,8,0,7]

Example 2:

1
2
Input: l1 = [2,4,3], l2 = [5,6,4]
Output: [8,0,7]

Example 3:

1
2
Input: l1 = [0], l2 = [0]
Output: [0]

由于加法需要从最低位开始运算,而最低位在链表末尾,链表只能从前往后遍历,没法取到前面的元素,那怎么办呢?我们可以利用栈来保存所有的元素,然后利用栈的后进先出的特点就可以从后往前取数字了,我们首先遍历两个链表,将所有数字分别压入两个栈s1和s2中,我们建立一个值为0的res节点,然后开始循环,如果栈不为空,则将栈顶数字加入sum中,然后将res节点值赋为sum%10,然后新建一个进位节点head,赋值为sum/10,如果没有进位,那么就是0,然后我们head后面连上res,将res指向head,这样循环退出后,我们只要看res的值是否为0,为0返回res->next,不为0则返回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
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
stack<int> s1, s2;
ListNode *head = l1;
while(head) {
s1.push(head->val);
head = head->next;
}
head = l2;
while(head) {
s2.push(head->val);
head = head->next;
}
head = NULL;
int sum = 0;
while(!s1.empty() || !s2.empty()) {
if (!s1.empty()) {
sum += s1.top();
s1.pop();
}
if (!s2.empty()) {
sum += s2.top();
s2.pop();
}
head = new ListNode(sum%10, head);
sum /= 10;
}
if (sum > 0)
head = new ListNode(sum, head);
return head;
}
};

Leetcode447. Number of Boomerangs

Given n points in the plane that are all pairwise distinct, a “boomerang” is a tuple of points (i, j, k) such that the distance between i and j equals the distance between i and k (the order of the tuple matters).

Find the number of boomerangs. You may assume that n will be at most 500 and coordinates of points are all in the range [-10000, 10000] (inclusive).

Example:

1
2
3
Input: [[0,0],[1,0],[2,0]]
Output: 2
Explanation: The two boomerangs are [[1,0],[0,0],[2,0]] and [[1,0],[2,0],[0,0]]

给定 n 个两两各不相同的平面上的点,一个 “回旋镖” 是一个元组(tuple)的点(i,j,k),并且 i 和 j 的距离等于 i 和 k之间的距离(考虑顺序)。找出回旋镖的个数。你可以假设 n 不大于500,点的坐标范围在[-10000, 10000](包括边界)。

抓住两组点 (x1,y1)、(x2,y2) 和 (x1,y1)、(x3,y3) 之间的距离相等这个信息:distance = sqrt{(x1-x2)^2+(y1-y2)^2} = sqrt{(x1-x3)^2+(y1-y3)^2}

按照这种相等的距离,我们可以给所有点进行分类,相同距离的这些点(假设n个)可以构成一个排列组合中的排列:n*(n-1)个回旋镖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int numberOfBoomerangs(vector<vector<int>>& points) {
int ans = 0;
unordered_map<int, int> hash;
for(int i=0;i<points.size();i++){
for(int j=0;j<points.size();j++){
hash[pow(points[i][0]-points[j][0],2)+pow(points[i][1]-points[j][1],2)] += 1;
}
for(auto d:hash){
ans += d.second*(d.second-1);
}
hash.clear();
}
return ans;
}
};

Leetcode448. Find All Numbers Disappeared in an Array

Given an array of integers where 1 ≤ a[i] ≤ n (n = size of array), some elements appear twice and others appear once. Find all the elements of [1, n] inclusive that do not appear in this array. Could you do it without extra space and in O(n) runtime? You may assume the returned list does not count as extra space.

Example:

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

Output:
[5,6]

这道题让我们找出数组中所有消失的数,将nums[i]置换到其对应的位置nums[nums[i]-1]上去,比如对于没有缺失项的正确的顺序应该是[1, 2, 3, 4, 5, 6, 7, 8],而我们现在却是[4,3,2,7,8,2,3,1],我们需要把数字移动到正确的位置上去,比如第一个4就应该和7先交换个位置,以此类推,最后得到的顺序应该是[1, 2, 3, 4, 3, 2, 7, 8],我们最后在对应位置检验,如果nums[i]和i+1不等,那么我们将i+1存入结果res中即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
vector<int> res;
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] != nums[nums[i] - 1]) {
swap(nums[i], nums[nums[i] - 1]);
--i;
}
}
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] != i + 1) {
res.push_back(i + 1);
}
}
return res;
}
};

Leetcode449. Serialize and Deserialize BST

Serialization is converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment.

Design an algorithm to serialize and deserialize a binary search tree. There is no restriction on how your serialization/deserialization algorithm should work. You need to ensure that a binary search tree can be serialized to a string, and this string can be deserialized to the original tree structure.

The encoded string should be as compact as possible.

Example 1:

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

Example 2:

1
2
Input: root = []
Output: []

Constraints:

  • The number of nodes in the tree is in the range [0, 104].
  • 0 <= Node.val <= 104
  • The input tree is guaranteed to be a binary search tree.

用队列来做,比较慢,但是很原生且具有通用性:

1
2
3
4
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
class Codec {
public:

// Encodes a tree to a single string.
string serialize(TreeNode* root) {
string res = "";
if (root == NULL)
return "";
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
TreeNode* temp = q.front();
q.pop();
if (temp) {
res += to_string(temp->val) + " ";
q.push(temp->left);
q.push(temp->right);
}
else
res += "# ";
}
return res;
}

// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
if (data == "")
return NULL;
int pos = 0;
TreeNode *root = new TreeNode(get_num(data, pos));
pos ++;
queue<TreeNode*> q;
q.push(root);
while(!q.empty()) {
TreeNode* temp = q.front();
q.pop();
if (data[pos] == '#') {
temp->left = NULL;
pos ++;
}
else {
temp->left = new TreeNode(get_num(data, pos));
q.push(temp->left);
}
pos ++;
if (data[pos] == '#') {
temp->right = NULL;
pos ++;
}
else {
temp->right = new TreeNode(get_num(data, pos));
q.push(temp->right);
}
pos ++;
}
return root;
}

int get_num(string data, int& pos) {
int res = 0;
while(data[pos] != ' ')
res = res * 10 + data[pos++] - '0';
return res;
}
};

层序遍历的非递归解法略微复杂一些,我们需要借助queue来做,本质是BFS算法,也不是很难理解,就是BFS算法的常规套路稍作修改即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class Codec {
public:

// Encodes a tree to a single string.
string serialize(TreeNode* root) {
if (!root) return "";
ostringstream os;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
TreeNode *t = q.front(); q.pop();
if (t) {
os << t->val << " ";
q.push(t->left);
q.push(t->right);
} else {
os << "# ";
}
}
return os.str();
}

// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
if (data.empty()) return NULL;
istringstream is(data);
queue<TreeNode*> q;
string val = "";
is >> val;
TreeNode *res = new TreeNode(stoi(val)), *cur = res;
q.push(cur);
while (!q.empty()) {
TreeNode *t = q.front(); q.pop();
if (!(is >> val)) break;
if (val != "#") {
cur = new TreeNode(stoi(val));
q.push(cur);
t->left = cur;
}
if (!(is >> val)) break;
if (val != "#") {
cur = new TreeNode(stoi(val));
q.push(cur);
t->right = cur;
}
}
return res;
}
};

Leetcode450. Delete Node in a BST

Given a root node reference of a BST and a key, delete the node with the given key in the BST. Return the root node reference (possibly updated) of the BST.

Basically, the deletion can be divided into two stages:

Search for a node to remove.
If the node is found, delete the node.
Follow up: Can you solve it with time complexity O(height of tree)?

Example 1:

1
2
3
4
5
Input: root = [5,3,6,2,4,null,7], key = 3
Output: [5,4,6,2,null,null,7]
Explanation: Given key to delete is 3. So we find the node with value 3 and delete it.
One valid answer is [5,4,6,2,null,null,7], shown in the above BST.
Please notice that another valid answer is [5,2,6,null,4,null,7] and it's also accepted.

这道题让我们删除二叉搜索树中的一个节点,难点在于删除完结点并补上那个结点的位置后还应该是一棵二叉搜索树。被删除掉的结点位置,不一定是由其的左右子结点补上,比如下面这棵树:

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

如果要删除结点4,那么应该将结点5补到4的位置,这样才能保证还是 BST,那么结果是如下这棵树:
1
2
3
4
5
6
7
     7
/ \
5 8
/ \
2 6
\
3

先来看一种递归的解法,首先判断根节点是否为空。由于 BST 的左<根<右的性质,使得可以快速定位到要删除的结点,对于当前结点值不等于 key 的情况,根据大小关系对其左右子结点分别调用递归函数。若当前结点就是要删除的结点,先判断若有一个子结点不存在,就将 root 指向另一个结点,如果左右子结点都不存在,那么 root 就赋值为空了,也正确。难点就在于处理左右子结点都存在的情况,需要在右子树找到最小值,即右子树中最左下方的结点,然后将该最小值赋值给 root,然后再在右子树中调用递归函数来删除这个值最小的结点,参见代码如下:
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:
TreeNode* deleteNode(TreeNode* root, int key) {
if (!root)
return NULL;
if (root->val > key)
root->left = deleteNode(root->left, key);
else if (root->val < key)
root->right = deleteNode(root->right, key);
else {
if (!root->left || !root->right)
root = root->left ? root->left : root->right;
else {
TreeNode* cur = root->right;
while(cur->left)
cur = cur->left;
root->val = cur->val;
root->right = deleteNode(root->right, cur->val);
}
}
return root;
}
};

Leetcode451. Sort Characters By Frequency

Given a string s, sort it in decreasing order based on the frequency of characters, and return the sorted string.

Example 1:

1
2
3
4
Input: s = "tree"
Output: "eert"
Explanation: 'e' appears twice while 'r' and 't' both appear once.
So 'e' must appear before both 'r' and 't'. Therefore "eetr" is also a valid answer.

Example 2:

1
2
3
4
Input: s = "cccaaa"
Output: "aaaccc"
Explanation: Both 'c' and 'a' appear three times, so "aaaccc" is also a valid answer.
Note that "cacaca" is incorrect, as the same characters must be together.

Example 3:

1
2
3
4
Input: s = "Aabb"
Output: "bbAa"
Explanation: "bbaA" is also a valid answer, but "Aabb" is incorrect.
Note that 'A' and 'a' are treated as two different characters.

竟然还要区分大小写,还要排序,那map等结构就不能用了,直接用数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
static bool comp(pair<char, int>& a, pair<char, int>& b) {
return a.second > b.second || a.second == b.second && a.first < b.first;
}
string frequencySort(string s) {
int count[256] = {0};
for (char c : s)
count[c] ++;
sort(s.begin(), s.end(), [&](char a, char b){
return count[a] > count[b] || count[a] == count[b] && a < b;
});
return s;
}
};

Leetcode452. Minimum Number of Arrows to Burst Balloons

There are a number of spherical balloons spread in two-dimensional space. For each balloon, provided input is the start and end coordinates of the horizontal diameter. Since it’s horizontal, y-coordinates don’t matter and hence the x-coordinates of start and end of the diameter suffice. Start is always smaller than end. There will be at most 104 balloons.

An arrow can be shot up exactly vertically from different points along the x-axis. A balloon with xstart and xend bursts by an arrow shot at x if xstart ≤ x ≤ xend. There is no limit to the number of arrows that can be shot. An arrow once shot keeps travelling up infinitely. The problem is to find the minimum number of arrows that must be shot to burst all balloons.

Example 1:

1
2
3
Input: points = [[10,16],[2,8],[1,6],[7,12]]
Output: 2
Explanation: One way is to shoot one arrow for example at x = 6 (bursting the balloons [2,8] and [1,6]) and another arrow at x = 11 (bursting the other two balloons).

Example 2:

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

Example 3:

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

这道题给了我们一堆大小不等的气球,用区间范围来表示气球的大小,可能会有重叠区间。然后我们用最少的箭数来将所有的气球打爆。那么这道题是典型的用贪婪算法来做的题,因为局部最优解就等于全局最优解,我们首先给区间排序,我们不用特意去写排序比较函数,因为默认的对于pair的排序,就是按第一个数字升序排列,如果第一个数字相同,那么按第二个数字升序排列,这个就是我们需要的顺序,所以直接用即可。然后我们将res初始化为1,因为气球数量不为0,所以怎么也得先来一发啊,然后这一箭能覆盖的最远位置就是第一个气球的结束点,用变量end来表示。然后我们开始遍历剩下的气球,如果当前气球的开始点小于等于end,说明跟之前的气球有重合,之前那一箭也可以照顾到当前的气球,此时我们要更新end的位置,end更新为两个气球结束点之间较小的那个,这也是当前气球和之前气球的重合点,然后继续看后面的气球;如果某个气球的起始点大于end了,说明前面的箭无法覆盖到当前的气球,那么就得再来一发,既然又来了一发,那么我们此时就要把end设为当前气球的结束点了,这样贪婪算法遍历结束后就能得到最少的箭数了,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
static bool comp(vector<int>& a, vector<int>& b) {
return a[0] < b[0];
}

int findMinArrowShots(vector<vector<int>>& points) {
sort(points.begin(), points.end(), comp);
int res = 1, end = points[0][1];
for (int i = 1; i < points.size(); i ++) {
if (end >= points[i][0])
end = min(end, points[i][1]);
else {
res ++;
end = points[i][1];
}
}
return res;
}
};

Leetcode453. Minimum Moves to Equal Array Elements

Given a non-empty integer array of size n, find the minimum number of moves required to make all array elements equal, where a move is incrementing n - 1 elements by 1.

Example:

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

Output:
3

Explanation:
Only three moves are needed (remember each move increments two elements):
[1,2,3] => [2,3,3] => [3,4,3] => [4,4,4]

这道题给了我们一个长度为n的数组,说是每次可以对 n-1 个数字同时加1,问最少需要多少次这样的操作才能让数组中所有的数字相等。那么想,为了快速的缩小差距,该选择哪些数字加1呢,不难看出每次需要给除了数组最大值的所有数字加1,这样能快速的到达平衡状态。但是这道题如果老老实实的每次找出最大值,然后给其他数字加1,再判断是否平衡,思路是正确,但是 OJ 不答应。正确的解法相当的巧妙,需要换一个角度来看问题,其实给 n-1 个数字加1,效果等同于给那个未被选中的数字减1,比如数组 [1,2,3],给除去最大值的其他数字加1,变为 [2,3,3],全体减1,并不影响数字间相对差异,变为 [1,2,2],这个结果其实就是原始数组的最大值3自减1,那么问题也可能转化为,将所有数字都减小到最小值,这样难度就大大降低了,只要先找到最小值,然后累加每个数跟最小值之间的差值即可。

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
int minMoves(vector<int>& nums) {
int minn = INT_MAX, res = 0;
for(int i : nums)
minn = min(minn, i);
for(int i : nums)
res += (i - minn);
return res;
}
};

Leetcode454. 4Sum II

Given four integer arrays nums1, nums2, nums3, and nums4 all of length n, return the number of tuples (i, j, k, l) such that:

  • 0 <= i, j, k, l < n
  • nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

Example 1:

1
2
Input: nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
Output: 2

Explanation: The two tuples are:

  1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
  2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

这道题是之前那道 4Sum 的延伸,让我们在四个数组中各取一个数字,使其和为0。如果把A和B的两两之和都求出来,在 HashMap 中建立两数之和跟其出现次数之间的映射,那么再遍历C和D中任意两个数之和,只要看哈希表存不存在这两数之和的相反数就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
int n = nums1.size();
unordered_map<int, int> mab, mcd;
for (int i = 0; i < n; i ++)
for (int j = 0; j < n; j ++) {
mab[nums1[i]+nums2[j]] ++;
}
int res = 0;
for (int i = 0; i < n; i ++)
for (int j = 0; j < n; j ++) {
res += mab[-nums3[i]-nums4[j]];
}
return res;
}
};

用两个 HashMap 分别记录 AB 和 CB 的两两之和出现次数,然后遍历其中一个 HashMap,并在另一个 HashMap 中找和的相反数出现的次数,更方便,但更慢。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
int n = nums1.size();
unordered_map<int, int> mab, mcd;
for (int i = 0; i < n; i ++)
for (int j = 0; j < n; j ++) {
mab[nums1[i]+nums2[j]] ++;
mcd[nums3[i]+nums4[j]] ++;
}
int res = 0;
for (auto i : mab)
res += (i.second * mcd[-i.first]);
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
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4)
{
map<int, int> index1,index2,index3;
unordered_map<int,int> index4;
for (size_t i = 0; i < nums4.size(); ++i)
{
index1[nums1[i]]++;
index2[nums2[i]]++;
index3[nums3[i]]++;
index4[nums4[i]]++;
}
unordered_map<int, int> sums;
for (auto && it3 : index3)
for (auto && it4 : index4)
sums[it3.first+it4.first] += it3.second*it4.second;

int count = 0;
for (auto it1 = index1.begin(); it1 != index1.end(); ++it1)
{
for (auto it2 = index2.begin(); it2 != index2.end(); ++it2)
{
long t2 = (long) it1->first + (long) it2->first;
int ct2 = it1->second * it2->second;
auto pos = sums.find(-t2);
if (pos == sums.end()) continue;
count += pos->second*ct2;
}
}
return count;
}
};

Leetcode455. Assign Cookies

Assume you are an awesome parent and want to give your children some cookies. But, you should give each child at most one cookie. Each child i has a greed factor gi, which is the minimum size of a cookie that the child will be content with; and each cookie j has a size sj. If sj >= gi, we can assign the cookie j to the child i, and the child i will be content. Your goal is to maximize the number of your content children and output the maximum number.

Note:
You may assume the greed factor is always positive.
You cannot assign more than one cookie to one child.

Example 1:

1
2
3
4
5
Input: [1,2,3], [1,1]
Output: 1
Explanation: You have 3 children and 2 cookies. The greed factors of 3 children are 1, 2, 3.
And even though you have 2 cookies, since their size is both 1, you could only make the child whose greed factor is 1 content.
You need to output 1.

Example 2:
1
2
3
4
5
Input: [1,2], [1,2,3]
Output: 2
Explanation: You have 2 children and 3 cookies. The greed factors of 2 children are 1, 2.
You have 3 cookies and their sizes are big enough to gratify all of the children,
You need to output 2.

有一堆饼干和一堆孩子,每个饼干大小为s[j],每个孩子想要的大小为g[i],求这堆饼干能满足至多多少个孩子?
很容易想到,每个孩子尽量拿到和他想要的大小差距最小的饼干,就能保证不会“浪费”大块饼干。因此把g和s排序后,把最相邻的饼干分给刚刚好满足的孩子,就能得到最大的满足数量了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
sort(g.begin(), g.end());
sort(s.begin(), s.end());
int count = 0;
for(int i = 0, j = 0; i < g.size() && j < s.size(); j ++){
if(s[j] >= g[i]) {
count ++;
i ++;
}
}
return count;
}
};

Leetcode456. 132 Pattern

Given an array of n integers nums, a 132 pattern is a subsequence of three integers nums[i], nums[j] and nums[k] such that i < j < k and nums[i] < nums[k] < nums[j].

Return true if there is a 132 pattern in nums, otherwise, return false.

Example 1:

1
2
3
Input: nums = [1,2,3,4]
Output: false
Explanation: There is no 132 pattern in the sequence.

Example 2:

1
2
3
Input: nums = [3,1,4,2]
Output: true
Explanation: There is a 132 pattern in the sequence: [1, 4, 2].

Example 3:

1
2
3
Input: nums = [-1,3,2,0]
Output: true
Explanation: There are three 132 patterns in the sequence: [-1, 3, 2], [-1, 3, 0] and [-1, 2, 0].

思路是维护一个栈和一个变量 third,其中 third 就是第三个数字,也是 pattern 132 中的2,初始化为整型最小值,栈里面按顺序放所有大于 third 的数字,也是 pattern 132 中的3,那么在遍历的时候,如果当前数字小于 third,即 pattern 132 中的1找到了,直接返回 true 即可,因为已经找到了,注意应该从后往前遍历数组。如果当前数字大于栈顶元素,那么将栈顶数字取出,赋值给 third,然后将该数字压入栈,这样保证了栈里的元素仍然都是大于 third 的,想要的顺序依旧存在,进一步来说,栈里存放的都是可以维持坐标 second > third 的 second 值,其中的任何一个值都是大于当前的 third 值,如果有更大的值进来,那就等于形成了一个更优的 second > third 的这样一个组合,并且这时弹出的 third 值比以前的 third 值更大,为什么要保证 third 值更大,因为这样才可以更容易的满足当前的值 first 比 third 值小这个条件,举个例子来说吧,比如 [2, 4, 2, 3, 5],由于是从后往前遍历,所以后三个数都不会进入 while 循环,那么栈中的数字为 5, 3, 2(其中2为栈顶元素),此时 third 还是整型最小,那么当遍历到4的时候,终于4大于栈顶元素2了,那么 third 赋值为2,且2出栈。此时继续 while 循环,因为4还是大于新栈顶元素3,此时 third 赋值为3,且3出栈。现在栈顶元素是5,那么 while 循环结束,将4压入栈。下一个数字2,小于 third,则找到符合要求的序列 [2, 4, 3],参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
bool find132pattern(vector<int>& nums) {
int n = nums.size(), third = INT_MIN;
stack<int> s;
for (int i = n-1; i >= 0; i --) {
if (nums[i] < third)
return true;
while(!s.empty() && nums[i] > s.top()) {
third = s.top(); s.pop();
}
s.push(nums[i]);
}
return false;
}
};

Leetcode459. Repeated Substring Pattern

Given a non-empty string check if it can be constructed by taking a substring of it and appending multiple copies of the substring together. You may assume the given string consists of lowercase English letters only and its length will not exceed 10000.

Example 1:

1
2
3
Input: "abab"
Output: True
Explanation: It's the substring "ab" twice.

Example 2:
1
2
Input: "aba"
Output: False

Example 3:
1
2
3
Input: "abcabcabcabc"
Output: True
Explanation: It's the substring "abc" four times. (And the substring "abcabc" twice.)

传统方法,挨个子字符串对比
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:
bool repeatedSubstringPattern(string s) {
string temp;
int length = s.length();
for(int i = 1; i <= length/2; i ++) {
if(length % i)
continue;
temp = s.substr(0, i);
temp = gen(temp, length/i);
if(temp == s)
return true;
}
return false;
}

string gen(string temp, int i) {
string ans = "";
while(i--)
ans += temp;
return ans;
}
};

另一种做法,用dp。维护的一位数组dp[i]表示,到位置i-1为止的重复字符串的字符个数,不包括被重复的那个字符串,什么意思呢,我们举个例子,比如”abcabc”的dp数组为[0 0 0 0 1 2 3],dp数组长度要比原字符串长度多一个。那么我们看最后一个位置数字为3,就表示重复的字符串的字符数有3个。如果是”abcabcabc”,那么dp数组为[0 0 0 0 1 2 3 4 5 6],我们发现最后一个数字为6,那么表示重复的字符串为“abcabc”,有6个字符。那么怎么通过最后一个数字来知道原字符串是否由重复的子字符串组成的呢,首先当然是最后一个数字不能为0,而且还要满足dp[n] % (n - dp[n]) == 0才行,因为n - dp[n]是一个子字符串的长度,那么重复字符串的长度和肯定是一个子字符串的整数倍。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
bool repeatedSubstringPattern(string s) {
int n = s.length();
vector<int> dp(n+1, 0);
int i = 1, j = 0;
while(i < n) {
if(s[i] == s[j])
dp[++i] = ++j;
else if(j == 0)
i ++;
else
j = dp[j];
}
return dp[n] && (dp[n] % (n - dp[n]) == 0);
}
};

Leetcode461. Hamming Distance

The Hamming distance between two integers is the number of positions at which the corresponding bits are different. Given two integers x and y, calculate the Hamming distance.

Note:
0 ≤ x, y < 231.

Example:

1
2
3
4
5
6
7
Input: x = 1, y = 4
Output: 2

Explanation:
1 (0 0 0 1)
4 (0 1 0 0)
↑ ↑

The above arrows point to positions where the corresponding bits are different.

求两个数的海明距离,就是判断其二进制有多少不一样的位

1
2
3
4
5
6
7
8
9
10
class Solution {
public:
int hammingDistance(int x, int y) {
int temp = x ^ y;
int res=0;
for(int i=temp;i>0;i=i>>1)
if(i&1) res++;
return res;
}
};

Leetcode462. Minimum Moves to Equal Array Elements II

Given an integer array nums of size n, return the minimum number of moves required to make all array elements equal.

In one move, you can increment or decrement an element of the array by 1.

Test cases are designed so that the answer will fit in a 32-bit integer.

Example 1:

1
2
3
4
5
Input: nums = [1,2,3]
Output: 2
Explanation:
Only two moves are needed (remember each move increments or decrements one element):
[1,2,3] => [2,2,3] => [2,2,2]

Example 2:

1
2
Input: nums = [1,10,2,9]
Output: 16

这道题每次对任意一个数字加1或者减1,让我们用最少的次数让数组所有值相等。首先给数组排序,最终需要变成的相等的数字就是中间的数,如果数组有奇数个,那么就是最中间的那个数字;如果是偶数个,那么就是中间两个数的区间中的任意一个数字。而两端的数字变成中间的一个数字需要的步数实际上就是两端数字的距离。参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
int minMoves2(vector<int>& nums) {
int res = 0, i = 0, j = (int)nums.size() - 1;
sort(nums.begin(), nums.end());
while (i < j) {
res += nums[j--] - nums[i++];
}
return res;
}
};

既然有了上面的分析,我们知道实际上最后相等的数字就是数组的最中间的那个数字,那么我们在给数组排序后,直接利用坐标定位到中间的数字,然后算数组中每个数组与其的差的绝对值累加即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
int minMoves2(vector<int>& nums) {
sort(nums.begin(), nums.end());
int res = 0, mid = nums[nums.size() / 2];
for (int num : nums) {
res += abs(num - mid);
}
return res;
}
};

上面的两种方法都给整个数组排序了,时间复杂度是O(nlgn),其实我们并不需要给所有的数字排序,我们只关系最中间的数字,那么这个stl中自带的函数nth_element就可以完美的发挥其作用了,我们只要给出我们想要数字的位置,它就能在O(n)的时间内返回正确的数字,然后算数组中每个数组与其的差的绝对值累加即可,参见代码如下:

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

Leetcode463. Island Perimeter

You are given a map in form of a two-dimensional integer grid where 1 represents land and 0 represents water.

Grid cells are connected horizontally/vertically (not diagonally). The grid is completely surrounded by water, and there is exactly one island (i.e., one or more connected land cells).

The island doesn’t have “lakes” (water inside that isn’t connected to the water around the island). One cell is a square with side length 1. The grid is rectangular, width and height don’t exceed 100. Determine the perimeter of the island.

Example:

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

Output: 16

Explanation: The perimeter is the 16 yellow stripes in the image below:

看一共有几条边,对每个格子进行遍历。

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 islandPerimeter(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
int ans = 0, temp;
for(int i = 0; i < m; i ++) {
for(int j = 0; j < n; j ++) {
if(!grid[i][j])
continue;
temp = 0;
if(j == 0 || (j > 0 && !grid[i][j-1])) temp ++;
if(j == n-1 || (j < n-1 && !grid[i][j+1])) temp ++;
if(i == 0 || (i > 0 && !grid[i-1][j])) temp ++;
if(i == m-1 || (i < m-1 && !grid[i+1][j])) temp ++;

ans += temp;
}
}
return ans;
}
};

Leetcode464. Can I Win

In the “100 game,” two players take turns adding, to a running total, any integer from 1..10. The player who first causes the running total to reach or exceed 100 wins.

What if we change the game so that players cannot re-use integers?

For example, two players might take turns drawing from a common pool of numbers of 1..15 without replacement until they reach a total >= 100.

Given an integer maxChoosableInteger and another integer desiredTotal, determine if the first player to move can force a win, assuming both players play optimally.

You can always assume that maxChoosableInteger will not be larger than 20 and desiredTotal will not be larger than 300.

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
Input:
maxChoosableInteger = 10
desiredTotal = 11

Output:
false

Explanation:
No matter which integer the first player choose, the first player will lose.
The first player can choose an integer from 1 up to 10.
If the first player choose 1, the second player can only choose integers from 2 up to 10.
The second player will win by choosing 10 and get a total = 11, which is >= desiredTotal.
Same with other integers chosen by the first player, the second player will always win.

这道题给了我们一堆数字,然后两个人,每人每次选一个数字,看数字总数谁先到给定值,有点像之前那道 Nim Game,但是比那题难度大。我刚开始想肯定说用递归啊,结果写完发现 TLE 了,后来发现我们必须要优化效率,使用 HashMap 来记录已经计算过的结果。我们首先来看如果给定的数字范围大于等于目标值的话,直接返回 true。如果给定的数字总和小于目标值的话,说明谁也没法赢,返回 false。然后我们进入递归函数,首先我们查找当前情况是否在 HashMap 中存在,有的话直接返回即可。我们使用一个整型数按位来记录数组中的某个数字是否使用过,我们遍历所有数字,将该数字对应的 mask 算出来,如果其和 used 相与为0的话,说明该数字没有使用过,我们看如果此时的目标值小于等于当前数字,说明已经赢了,或者调用递归函数,如果返回 false,说明也是第一个人赢了。为啥呢,因为当前已经选过数字了,此时就该对第二个人调用递归函数,只有返回的结果是 false,我们才能赢,所以此时我们 true,并返回 true。如果遍历完所有数字,标记 false,并返回 false,参见代码如下:

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:
bool canIWin(int maxChoosableInteger, int desiredTotal) {
if (maxChoosableInteger >= desiredTotal) return true;
if (maxChoosableInteger * (maxChoosableInteger + 1) / 2 < desiredTotal) return false;
unordered_map<int, bool> m;
return canWin(maxChoosableInteger, desiredTotal, 0, m);
}
bool canWin(int length, int total, int used, unordered_map<int, bool>& m) {
if (m.count(used)) return m[used];
for (int i = 0; i < length; ++i) {
int cur = (1 << i);
if ((cur & used) == 0) {
if (total <= i + 1 || !canWin(length, total - (i + 1), cur | used, m)) {
m[used] = true;
return true;
}
}
}
m[used] = false;
return false;
}
};

Leetcode467. Unique Substrings in Wraparound String

Consider the string s to be the infinite wraparound string of “abcdefghijklmnopqrstuvwxyz”, so s will look like this: “…zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd….”.

Now we have another string p. Your job is to find out how many unique non-empty substrings of p are present in s. In particular, your input is the string p and you need to output the number of different non-empty substrings of p in the string s.

Note: p consists of only lowercase English letters and the size of p might be over 10000.

Example 1:

1
2
3
4
Input: "a"
Output: 1

Explanation: Only the substring "a" of string "a" is in the string s.

Example 2:

1
2
3
Input: "cac"
Output: 2
Explanation: There are two substrings "a", "c" of string "cac" in the string s.

Example 3:

1
2
3
Input: "zab"
Output: 6
Explanation: There are six substrings "z", "a", "b", "za", "ab", "zab" of string "zab" in the string s.

这道题说有一个无限长的封装字符串,然后又给了我们另一个字符串p,问我们p有多少非空子字符串在封装字符串中。我们通过观察题目中的例子可以发现,由于封装字符串是26个字符按顺序无限循环组成的,那么满足题意的p的子字符串要么是单一的字符,要么是按字母顺序的子字符串。这道题遍历p的所有子字符串会TLE,因为如果p很大的话,子字符串很多,会有大量的满足题意的重复子字符串,必须要用到trick,而所谓技巧就是一般来说你想不到的方法。我们看abcd这个字符串,以d结尾的子字符串有abcd, bcd, cd, d,那么我们可以发现bcd或者cd这些以d结尾的字符串的子字符串都包含在abcd中,那么我们知道以某个字符结束的最大字符串包含其他以该字符结束的字符串的所有子字符串,说起来很拗口,但是理解了我上面举的例子就行。那么题目就可以转换为分别求出以每个字符(a-z)为结束字符的最长连续字符串就行了,我们用一个数组cnt记录下来,最后在求出数组cnt的所有数字之和就是我们要的结果啦,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int findSubstringInWraproundString(string p) {
vector<int> cnt(26, 0);
int len = 0;
for (int i = 0; i < p.size(); ++i) {
if (i > 0 && (p[i] == p[i - 1] + 1 || p[i - 1] - p[i] == 25)) {
++len;
} else {
len = 1;
}
cnt[p[i] - 'a'] = max(cnt[p[i] - 'a'], len);
}
return accumulate(cnt.begin(), cnt.end(), 0);
}
};

Leetcode468. Validate IP Address

Given a string IP, return “IPv4” if IP is a valid IPv4 address, “IPv6” if IP is a valid IPv6 address or “Neither” if IP is not a correct IP of any type.

A valid IPv4 address is an IP in the form “x1.x2.x3.x4” where 0 <= xi <= 255 and xi cannot contain leading zeros. For example, “192.168.1.1” and “192.168.1.0” are valid IPv4 addresses but “192.168.01.1”, while “192.168.1.00” and “192.168@1.1” are invalid IPv4 addresses.

A valid IPv6 address is an IP in the form “x1:x2:x3:x4:x5:x6:x7:x8” where:

  • 1 <= xi.length <= 4
  • xi is a hexadecimal string which may contain digits, lower-case English letter (‘a’ to ‘f’) and upper-case English letters (‘A’ to ‘F’).
  • Leading zeros are allowed in xi.

For example, “2001:0db8:85a3:0000:0000:8a2e:0370:7334” and “2001:db8:85a3:0:0:8A2E:0370:7334” are valid IPv6 addresses, while “2001:0db8:85a3::8A2E:037j:7334” and “02001:0db8:85a3:0000:0000:8a2e:0370:7334” are invalid IPv6 addresses.

Example 1:

1
2
3
Input: IP = "172.16.254.1"
Output: "IPv4"
Explanation: This is a valid IPv4 address, return "IPv4".

Example 2:

1
2
3
Input: IP = "2001:0db8:85a3:0:0:8A2E:0370:7334"
Output: "IPv6"
Explanation: This is a valid IPv6 address, return "IPv6".

Example 3:

1
2
3
Input: IP = "256.256.256.256"
Output: "Neither"
Explanation: This is neither a IPv4 address nor a IPv6 address.

Example 4:

1
2
Input: IP = "2001:0db8:85a3:0:0:8A2E:0370:7334:"
Output: "Neither"

巨难搞,就跟判断一个数是不是合法一样。

1
2
3
4
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
class Solution {
public:

bool is_number(char c) {
return ('0' <= c && c <= '9');
}
bool is_char(char c) {
return ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f');
}

string validIPAddress(string IP) {
if (isv4(IP))
return "IPv4";
else if (isv6(IP))
return "IPv6";
else
return "Neither";
}

bool isv4(string IP) {
int len = IP.length();
int num_points = 0, number = 0;
bool is_begin = true;
for (int i = 0; i < len; i ++) {
if (number > 255)
return false;
else if (IP[i] == '.') {
num_points ++;
number = 0;
if (is_begin)
return false;
is_begin = true;
}
else if (!is_number(IP[i]))
return false;
else {
if (i < len-1 && is_begin && IP[i+1] != '.' && IP[i] == '0')
return false;
is_begin = false;
if (i > 0 && IP[i] == '0' && IP[i-1] == 0)
return false;
number = number * 10 + IP[i] - '0';
}
}
if (number > 255 || num_points != 3 || IP[len-1] == '.')
return false;
return true;
}

bool isv6(string IP) {
int len = IP.length();
int num_points = 0, number = 0, sum = 0;
bool is_begin = true;
for (int i = 0; i < len; i ++) {
if (IP[i] == ':') {
if (number > 4)
return false;
num_points ++;
number = 0;
if (is_begin)
return false;
is_begin = true;
}
else {
is_begin = false;
if (!(is_number(IP[i]) || is_char(IP[i])) )
return false;
number ++;
}
}
if (num_points != 7 || number > 4 || IP[len-1] == ':')
return false;
return 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
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
#pragma GCC optimize("Ofast")
static auto _ = [] () {ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);return 0;}();

class Solution
{
public:
string validIPAddress(string const& IP)
{
bool isV6 = IP.find(':') != string::npos;

if (isV6) { // Try to parse ipv6
int count = 0, segments = 0;

auto isValidHex = [](char c) {
return isdigit(c) ||
('a' <= c && c <= 'f') ||
('A' <= c && c <= 'F');
};

int ptr = -1, size = IP.size();
while (ptr < size && segments < 8) {
++ptr; // skip leading ':'

while (ptr < size && IP[ptr] != ':')
if (isValidHex(IP[ptr]))
++ptr, ++count;
else
return "Neither";

if (count == 0 || count > 4)
return "Neither";
else
count = 0,
++segments;
}

if (ptr == IP.size() && segments == 8)
return "IPv6";
else
return "Neither";
}
else { // Try to parse ipv4
int segments = 0, number = 0, count = 0;
int ptr = -1, size = IP.size();
while (ptr < size && segments < 4) {
++ptr; // skip initial dot

if (ptr < size && IP[ptr] == '0') {
++segments;
++ptr;
if (ptr == size || IP[ptr] == '.')
continue;
else
return "Neither";
}

while (ptr < size && IP[ptr] != '.')
if (number < 250 && isdigit(IP[ptr])) {
++count;
number *= 10;
number += IP[ptr] - '0';
++ptr;
}
else {
return "Neither";
}

if (1 <= count && count <= 4 && 1 <= number && number <= 255) {
count = 0;
number = 0;
++segments;
}
else {
return "Neither";
}
}

if (ptr == IP.size() && segments == 4)
return "IPv4";
else
return "Neither";
}
return "Neither";
}
};

Leetcode470. Implement Rand10() Using Rand7()

Given the API rand7() that generates a uniform random integer in the range [1, 7], write a function rand10() that generates a uniform random integer in the range [1, 10]. You can only call the API rand7(), and you shouldn’t call any other API. Please do not use a language’s built-in random API.

Each test case will have one internal argument n, the number of times that your implemented function rand10() will be called while testing. Note that this is not an argument passed to rand10().

Follow up:

  • What is the expected value for the number of calls to rand7() function?
  • Could you minimize the number of calls to rand7()?

Example 1:

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

Example 2:

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

Example 3:

1
2
Input: n = 3
Output: [3,8,10]

这道题给了我们一个随机生成 [1, 7] 内数字的函数rand7(),需要利用其来生成一个能随机生成 [1, 10] 内数字的函数rand10(),注意这里的随机生成的意思是等概率生成范围内的数字。这是一道很有意思的题目,由于rand7()只能生成1到7之间的数字,所以 8,9,10 这三个没法生成,那么怎么办?

大多数人可能第一个想法就是,再用一个呗,然后把两次的结果加起来,范围不就扩大了么,扩大成了 [2, 14] 之间,然后如果再减去1,范围不就是 [1, 13] 了么。想法不错,但是有个问题,这个范围内的每个数字生成的概率不是都相等的,为啥这么说呢,我们来举个简单的例子看下,就比如说rand2(),我们知道其可以生成两个数字1和2,且每个的概率都是 1/2。那么对于(rand2() - 1) +rand2()``呢,看一下:

1
2
3
4
5
rand2() - 1 + rand()2  =   ?
1 1 1
1 2 2
2 1 2
2 2 3

我们发现,生成数字范围 [1, 3] 之间的数字并不是等概率大,其中2出现的概率为 1/2,1和3分别为 1/4。这就不随机了。问题出在哪里了呢,如果直接相加,不同组合可能会产生相同的数字,比如 1+2 和 2+1 都是3。所以需要给第一个rand2()升一个维度,让其乘上一个数字,再相加。比如对于(rand2() - 1) * 2 +rand2()``,如下:

1
2
3
4
5
(rand2() - 1) * 2 + rand()2  =   ?
1 1 1
1 2 2
2 1 3
2 2 4

这时右边生成的 1,2,3,4 就是等概率出现的了。这样就通过使用rand2(),来生成rand4()了。那么反过来想一下,可以通过rand4()来生成rand2(),其实更加简单,我们只需通过rand4() % 2 + 1即可,如下:

1
2
3
4
5
rand4() % 2 + 1 =  ?
1 2
2 1
3 2
4 1

同理,我们也可以通过rand6()来生成rand2(),我们只需通过rand6() % 2 + 1即可,如下:

1
2
3
4
5
6
7
rand6() % 2 + 1 =  ?
1 2
2 1
3 2
4 1
5 2
6 1

所以,回到这道题,我们可以先凑出rand10*N(),然后再通过rand10*N() % 10 + 1来获得rand10()。那么,只需要将rand7()转化为rand10*N()即可,根据前面的讲解,我们转化也必须要保持等概率,那么就可以变化为(rand7() - 1) * 7 + rand7(),就转为了rand49()。但是 49 不是 10 的倍数,不过 49 包括好几个 10 的倍数,比如 40,30,20,10 等。这里,我们需要把rand49()转为rand40(),需要用到拒绝采样Rejection Sampling。这种采样方法就是随机到需要的数字就接受,不是需要的就拒绝,并重新采样,这样还能保持等概率。

当用 rand49()生成一个 [1, 49] 范围内的随机数,如果其在 [1, 40] 范围内,我们就将其转为rand10()范围内的数字,直接对 10 去余并加1,返回即可。如果不是,则继续循环即可,参见代码如下:

1
2
3
4
5
6
7
8
9
class Solution {
public:
int rand10() {
while (true) {
int num = (rand7() - 1) * 7 + rand7();
if (num <= 40) return num % 10 + 1;
}
}
};

我们可以不用 while 循环,而采用调用递归函数。

1
2
3
4
5
6
7
class Solution {
public:
int rand10() {
int num = (rand7() - 1) * 7 + rand7();
return (num <= 40) ? (num % 10 + 1) : rand10();
}
};

Leetcode472. Concatenated Words

Given an array of strings words (without duplicates), return all the concatenated words in the given list of words.

A concatenated word is defined as a string that is comprised entirely of at least two shorter words in the given array.

Example 1:

1
2
3
4
5
Input: words = ["cat","cats","catsdogcats","dog","dogcatsdog","hippopotamuses","rat","ratcatdogcat"]
Output: ["catsdogcats","dogcatsdog","ratcatdogcat"]
Explanation: "catsdogcats" can be concatenated by "cats", "dog" and "cats";
"dogcatsdog" can be concatenated by "dog", "cats" and "dog";
"ratcatdogcat" can be concatenated by "rat", "cat", "dog" and "cat".

Example 2:

1
2
Input: words = ["cat","dog","catdog"]
Output: ["catdog"]

这道题给了一个由单词组成的数组,某些单词是可能由其他的单词组成的,让我们找出所有这样的单词。我们首先把所有单词都放到一个unordered_set中,这样可以快速找到某个单词是否在数组中存在。对于当前要判断的单词,我们先将其从set中删去,然后调用之前的Word Break的解法。如果是可以拆分,那么我们就存入结果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
class Solution {
public:
vector<string> findAllConcatenatedWordsInADict(vector<string>& words) {
if (words.size() == 0)
return {};
vector<string> res;
unordered_set<string> dict(words.begin(), words.end());
for (int i = 0; i < words.size(); i ++) {
dict.erase(words[i]);
int len = words[i].size();
vector<bool> flag(len+1, false);
flag[0] = true;
for (int j = 0; j <= len; j ++)
for (int k = 0; k < j; k ++)
if (flag[k] && dict.count(words[i].substr(k, j-k))) {
flag[j] = true;
break;
}
if (flag[len])
res.push_back(words[i]);
dict.insert(words[i]);
}
return res;
}
};

Leetcode473. Matchsticks to Square

Remember the story of Little Match Girl? By now, you know exactly what matchsticks the little match girl has, please find out a way you can make one square by using up all those matchsticks. You should not break any stick, but you can link them up, and each matchstick must be used exactly one time.

Your input will be several matchsticks the girl has, represented with their stick length. Your output will either be true or false, to represent whether you could make one square using all the matchsticks the little match girl has.

Example 1:

1
2
3
Input: [1,1,2,2,2]
Output: true
Explanation: You can form a square with length 2, one side of the square came two sticks with length 1.

Example 2:

1
2
3
Input: [3,3,3,3,4]
Output: false
Explanation: You cannot find a way to form a square with all the matchsticks.

Note:

  • The length sum of the given matchsticks is in the range of 0 to 10^9.
  • The length of the given matchstick array will not exceed 15.

这道题让我们用数组中的数字来摆出一个正方形。这道题实际上是让我们将一个数组分成四个和相等的子数组。可以用优化过的递归来解,递归的方法基本上等于brute force。先给数组从大到小的顺序排序,这样大的数字先加,如果超过target了,就直接跳过了后面的再次调用递归的操作,效率会提高不少。我们建立一个长度为4的数组sums来保存每个边的长度和,我们希望每条边都等于target,数组总和的四分之一。然后我们遍历sums中的每条边,我们判断如果加上数组中的当前数字大于target,那么我们跳过,如果没有,我们就加上这个数字,然后对数组中下一个位置调用递归,如果返回为真,我们返回true,否则我们再从sums中对应位置将这个数字减去继续循环,参见代码如下:

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:
bool makesquare(vector<int>& nums) {
if (nums.empty() || nums.size() < 4) return false;
int sum = accumulate(nums.begin(), nums.end(), 0);
if (sum % 4 != 0) return false;
vector<int> sums(4, 0);
sort(nums.rbegin(), nums.rend());
return helper(nums, sums, 0, sum / 4);
}
bool helper(vector<int>& nums, vector<int>& sums, int pos, int target) {
if (pos >= nums.size()) {
return sums[0] == target && sums[1] == target && sums[2] == target;
}
for (int i = 0; i < 4; ++i) {
if (sums[i] + nums[pos] > target) continue;
sums[i] += nums[pos];
if (helper(nums, sums, pos + 1, target)) return true;
sums[i] -= nums[pos];
}
return false;
}
};

Leetcode474. Ones and Zeroes

In the computer world, use restricted resource you have to generate maximum benefit is what we always want to pursue.

For now, suppose you are a dominator of m 0s and n 1s respectively. On the other hand, there is an array with strings consisting of only 0s and 1s.

Now your task is to find the maximum number of strings that you can form with given m 0s and n 1s. Each 0 and 1 can be used at most once.

Note:

  • The given numbers of 0s and 1s will both not exceed 100
  • The size of given string array won’t exceed 600.

Example 1:

1
2
3
Input: Array = {"10", "0001", "111001", "1", "0"}, m = 5, n = 3
Output: 4
Explanation: This are totally 4 strings can be formed by the using of 5 0s and 3 1s, which are “10,”0001”,”1”,”0”

Example 2:

1
2
3
Input: Array = {"10", "0", "1"}, m = 1, n = 1
Output: 2
Explanation: You could form "10", but then you'd have nothing left. Better form "0" and "1".

这道题是一道典型的应用DP来解的题,我们需要建立一个二维的DP数组,其中dp[i][j]表示有i个0和j个1时能组成的最多字符串的个数,而对于当前遍历到的字符串,我们统计出其中0和1的个数为zeros和ones,然后dp[i - zeros][j - ones]表示当前的i和j减去zeros和ones之前能拼成字符串的个数,那么加上当前的zeros和ones就是当前dp[i][j]可以达到的个数,我们跟其原有数值对比取较大值即可,所以递推式如下:

1
dp[i][j] = max(dp[i][j], dp[i - zeros][j - ones] + 1);

有了递推式,我们就可以很容易的写出代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for (string str : strs) {
int zeros = 0, ones = 0;
for (char c : str) (c == '0') ? ++zeros : ++ones;
for (int i = m; i >= zeros; --i) {
for (int j = n; j >= ones; --j) {
dp[i][j] = max(dp[i][j], dp[i - zeros][j - ones] + 1);
}
}
}
return dp[m][n];
}
};

Leetcode475. Heaters

Winter is coming! Your first job during the contest is to design a standard heater with fixed warm radius to warm all the houses.

Now, you are given positions of houses and heaters on a horizontal line, find out minimum radius of heaters so that all houses could be covered by those heaters.

So, your input will be the positions of houses and heaters seperately, and your expected output will be the minimum radius standard of heaters.

Note:

  • Numbers of houses and heaters you are given are non-negative and will not exceed 25000.
  • Positions of houses and heaters you are given are non-negative and will not exceed 10^9.
  • As long as a house is in the heaters’ warm radius range, it can be warmed.
  • All the heaters follow your radius standard and the warm radius will the same.

Example 1:

1
2
3
Input: [1,2,3],[2]
Output: 1
Explanation: The only heater was placed in the position 2, and if we use the radius 1 standard, then all the houses can be warmed.

Example 2:
1
2
3
Input: [1,2,3,4],[1,4]
Output: 1
Explanation: The two heater was placed in the position 1 and 4. We need to use radius 1 standard, then all the houses can be warmed.

思路:

  1. 先对houses和heaters排序,result记录全局最小温暖半径,temp记录当前house的最小温暖半径。
  2. 依次为每个house查找最小的温暖半径(显然,每个house的最小半径只需考虑其左边最近的heaters和右边最近的heaters)。
  3. 对每一个house先查找位置不小于其位置的第一个heater,其位置为j。
  4. 若未找到,则当前house的最小温暖半径由左边最近的heaters决定。
  5. 若第一个heater的位置就不小于当前house的位置,则当前house的最小温暖半径由右边最近的heaters决定。
  6. 若找到的位置不小于当前house位置的第一个heater的位置大于当前house位置(若等于,则当前house的最小温暖半径等于0),则当前house的最小温暖半径是其与左边最近的heaters的距离和其与右边最近的heaters的距离的较小值。
  7. 若当前house的最小温暖半径大于全局result,则更新result。
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 findRadius(vector<int>& houses, vector<int>& heaters) {
int res = 0;
sort(houses.begin(), houses.end());
sort(heaters.begin(), heaters.end());
for(int house = 0, heater = 0; house < houses.size(); house ++) {
int temp = 0;
while(heater < heaters.size() && heaters[heater] < houses[house])
heater ++;
if(heater == heaters.size())
temp = houses[house] - heaters[heaters.size() - 1];
else if(heater == 0)
temp = heaters[0] - houses[house];
else if(heaters[heater] > houses[house])
temp = min(heaters[heater] - houses[house], houses[house] - heaters[heater-1]);
if(temp > res)
res = temp;
}
return res;
}
};

Leetcode476. Number Complement

Given a positive integer num, output its complement number. The complement strategy is to flip the bits of its binary representation.

Example 1:

1
2
3
Input: num = 5
Output: 2
Explanation: The binary representation of 5 is 101 (no leading zero bits), and its complement is 010. So you need to output 2.

Example 2:
1
2
3
Input: num = 1
Output: 0
Explanation: The binary representation of 1 is 1 (no leading zero bits), and its complement is 0. So you need to output 0.

给定一个正整数,对该数的二进制表示形式,从最高位的1开始向后按位取反。如果我们能知道该数最高位的1所在的位置,就可以构造一个长度和该数据所占位置一样长的一个掩码mask,然后概述和mask进行异或即可。例如:5的二进制是101,我们的构造的掩码为mask=111,两者异或则为010,即是所要的结果。
1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
int findComplement(int num) {
long mask = 1, temp = num;
while(temp > 0) {
mask = mask << 1;
temp = temp >> 1;
}
return num^(mask-1);
}
};

Leetcode477. Total Hamming Distance

The Hamming distance between two integers is the number of positions at which the corresponding bits are different.

Given an integer array nums, return the sum of Hamming distances between all the pairs of the integers in nums.

Example 1:

1
2
3
4
5
6
Input: nums = [4,14,2]
Output: 6
Explanation: In binary representation, the 4 is 0100, 14 is 1110, and 2 is 0010 (just
showing the four bits relevant in this case).
The answer will be:
HammingDistance(4, 14) + HammingDistance(4, 2) + HammingDistance(14, 2) = 2 + 2 + 2 = 6.

Example 2:

1
2
Input: nums = [4,14,4]
Output: 4

这道题是之前那道 Hamming Distance 的拓展,由于有之前那道题的经验,我们知道需要用异或来求每个位上的情况,那么需要来找出某种规律来,比如看下面这个例子,4,14,2 和1:

4: 0 1 0 0

14: 1 1 1 0

2: 0 0 1 0

1: 0 0 0 1

先看最后一列,有三个0和一个1,那么它们之间相互的汉明距离就是3,即1和其他三个0分别的距离累加,然后在看第三列,累加汉明距离为4,因为每个1都会跟两个0产生两个汉明距离,同理第二列也是4,第一列是3。仔细观察累计汉明距离和0跟1的个数,可以发现其实就是0的个数乘以1的个数,发现了这个重要的规律,那么整道题就迎刃而解了,只要统计出每一位的1的个数即可,参见代码如下:

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

Leetcode478. Generate Random Point in a Circle

Given the radius and x-y positions of the center of a circle, write a function randPoint which generates a uniform random point in the circle.

Note:

  • input and output values are in floating-point.
  • radius and x-y position of the center of the circle is passed into the class constructor.
  • a point on the circumference of the circle is considered to be in the circle.
  • randPoint returns a size 2 array containing x-position and y-position of the random point, in that order.

Example 1:

1
2
3
4
Input: 
["Solution","randPoint","randPoint","randPoint"]
[[1,0,0],[],[],[]]
Output: [null,[-0.72939,-0.65505],[-0.78502,-0.28626],[-0.83119,-0.19803]]

Example 2:

1
2
3
4
Input: 
["Solution","randPoint","randPoint","randPoint"]
[[10,5,-7.5],[],[],[]]
Output: [null,[11.52438,-8.33273],[2.46992,-16.21705],[11.13430,-12.42337]]

Explanation of Input Syntax:

  • The input is two lists: the subroutines called and their arguments. Solution’s constructor has three arguments, the radius, x-position of the center, and y-position of the center of the circle. randPoint has no arguments. Arguments are always wrapped with a list, even if there aren’t any.

这道题给了我们一个圆,包括中点位置和半径,让随机生成圆中的任意一个点。这里说明了圆上也当作是圆中,而且这里的随机意味着要等概率。

圆的方程表示为(x - a) ^ 2 + (y - b) ^ 2 = r ^ 2,这里的(a, b)是圆心位置,r为半径。那么如何生成圆中的任意位置呢,如果用这种方式来生成,先随机出一个x,随机出y的时候还要考虑其是否在圆中间,比较麻烦。继续回到高中时代,模糊的记忆中飘来了三个字,极坐标。是的,圆还可以用极坐标的形式来表示,只需随机出一个角度 theta,再随机出一个小于半径的长度,这样就可以得到圆中的坐标位置了。

先来生成 theta吧,由于一圈是 360 度,即 2pi,所以随机出一个 [0, 1] 中的小数,再乘以 2pi,就可以了。然后就是随机小于半径的长度,这里有个问题需要注意一下,这里并不是直接随机出一个 [0, 1] 中的小数再乘以半径r,而是要对随机出的 [0, 1] 中的小数取个平方根再乘以半径r。这是为啥呢,简单来说,是为了保证等概率。如果不用平方根的话,那么表示圆的时候(len * cos(theta)) ^ 2 + (len * sin(theta) ^ 2,这里就相当于对随机出的 [0, 1] 中的小数平方了,那么其就不是等概率的了,因为两个小于1的小数相乘了,其会更加靠近0,这就是为啥要平方一下的原因。最后在求点位置的时候要加上圆心的偏移即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
Solution(double radius, double x_center, double y_center) {
r = radius; centerX = x_center; centerY = y_center;
}

vector<double> randPoint() {
double theta = 2 * M_PI * ((double)rand() / RAND_MAX);
double len = sqrt((double)rand() / RAND_MAX) * r;
return {centerX + len * cos(theta), centerY + len * sin(theta)};
}

private:
double r, centerX, centerY;
};

这其实就是拒绝采样的经典应用,在一个正方形中有均匀分布的点,随机出其内切圆中的一个点,那么就是随机出x和y之后,然后算其平方和,如果小于等于r平方,说明其在圆内,可以返回其坐标,记得加上圆心偏移,否则重新进行采样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
Solution(double radius, double x_center, double y_center) {
r = radius; centerX = x_center; centerY = y_center;
}

vector<double> randPoint() {
while (true) {
double x = (2 * (double)rand() / RAND_MAX - 1.0) * r;
double y = (2 * (double)rand() / RAND_MAX - 1.0) * r;
if (x * x + y * y <= r * r) return {centerX + x, centerY + y};
}
}

private:
double r, centerX, centerY;
};

480. Sliding Window Median

Median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value.

Examples:

[2,3,4] , the median is 3

[2,3], the median is (2 + 3) / 2 = 2.5

Given an array nums , there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position. Your job is to output the median array for each window in the original array.

For example,

Given nums = [1,3,-1,-3,5,3,6,7], and k = 3.

1
2
3
4
5
6
7
8
Window position                Median
--------------- -----
[1 3 -1] -3 5 3 6 7 1
1 [3 -1 -3] 5 3 6 7 -1
1 3 [-1 -3 5] 3 6 7 -1
1 3 -1 [-3 5 3] 6 7 3
1 3 -1 -3 [5 3 6] 7 5
1 3 -1 -3 5 [3 6 7] 6

Therefore, return the median sliding window as [1,-1,-1,3,5,6].

Note:

  • You may assume k is always valid, ie: 1 ≤ k ≤ input array’s size for non-empty array.

这道题给了我们一个数组,还是滑动窗口的大小,让我们求滑动窗口的中位数。我想起来之前也有一道滑动窗口的题Sliding Window Maximum,于是想套用那道题的方法,可以用deque怎么也做不出,因为求中位数并不是像求最大值那样只操作deque的首尾元素。后来看到了史蒂芬大神的方法,原来是要用一个multiset集合,和一个指向最中间元素的iterator。我们首先将数组的前k个数组加入集合中,由于multiset自带排序功能,所以我们通过k/2能快速的找到指向最中间的数字的迭代器mid,如果k为奇数,那么mid指向的数字就是中位数;如果k为偶数,那么mid指向的数跟前面那个数求平均值就是中位数。当我们添加新的数字到集合中,multiset会根据新数字的大小加到正确的位置,然后我们看如果这个新加入的数字比之前的mid指向的数小,那么中位数肯定被拉低了,所以mid往前移动一个,再看如果要删掉的数小于等于mid指向的数(注意这里加等号是因为要删的数可能就是mid指向的数),则mid向后移动一个。然后我们将滑动窗口最左边的数删掉,我们不能直接根据值来用erase来删数字,因为这样有可能删掉多个相同的数字,而是应该用lower_bound来找到第一个不小于目标值的数,通过iterator来删掉确定的一个数字,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
vector<double> medianSlidingWindow(vector<int>& nums, int k) {
vector<double> res;
multiset<double> ms(nums.begin(), nums.begin() + k);
auto mid = next(ms.begin(), k / 2);
for (int i = k; ; ++i) {
res.push_back((*mid + *prev(mid, 1 - k % 2)) / 2);
if (i == nums.size()) return res;
ms.insert(nums[i]);
if (nums[i] < *mid) --mid;
if (nums[i - k] <= *mid) ++mid;
ms.erase(ms.lower_bound(nums[i - k]));
}
}
};

假定窗口里已经排序了,前半部分是x,后半部分是y,用multiset能自动排序。两个集合的size顶多相差1。

a[i-k]在x中,从x中删除;若不在x中则从y中删除。x中的最大值挪到y中成为最小值。维护x.size()-y.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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
class Solution {
public:

double calc(const multiset<int>& x, const multiset<int>& y, int k) {
if (k & 1) return *x.rbegin();
return (1.0 * *x.rbegin() + *y.begin()) / 2.0;
}

void remove(multiset<int>& x, multiset<int>& y, int val) {
if (x.count(val)) {
x.erase(x.find(val));
if (x.size() < y.size() && y.size()) {
int v = *y.begin();
x.insert(v);
y.erase(y.find(v));
}
return;
}

y.erase(y.find(val));
if (x.size() - y.size() > 1) {
int v = *x.rbegin();
x.erase(x.find(v));
y.insert(v);
}
}

void add(multiset<int>& x, multiset<int>& y, int val) {
if (x.empty() || val < *x.rbegin()) {
x.insert(val);
if (x.size() - y.size() > 1) {
int v = *x.rbegin();
x.erase(x.find(v));
y.insert(v);
}
return;
}

y.insert(val);
if (y.size() > x.size()) {
int v = *y.begin();
y.erase(y.find(v));
x.insert(v);
}
}

vector<double> medianSlidingWindow(vector<int>& a, int k) {
int n = a.size();
multiset<int> x, y;

// k 个元素
for (int i = 0 ; i < k; i ++)
x.insert(a[i]);

for (int i = 0; i < k/2; i ++) {
int val = *x.rbegin();
x.erase(x.find(val));
y.insert(val);
}

vector<double> res;
res.push_back(calc(x, y, k));

for (int l = 1, r = k; r < n; l ++, r ++) {
// 先删除a[l-1],这是上一个窗口的最小元素
remove(x, y, a[l-1]);
add(x, y, a[r]);
res.push_back(calc(x, y, k));
}
return res;
}
};

上面的方法用到了很多STL内置的函数,比如next,lower_bound啥的,下面我们来看一种不使用这些函数的解法。这种解法跟Find Median from Data Stream那题的解法很类似,都是维护了small和large两个堆,分别保存有序数组的左半段和右半段的数字,保持small的长度大于等于large的长度。我们开始遍历数组nums,如果i>=k,说明此时滑动窗口已经满k个了,再滑动就要删掉最左值了,我们分别在small和large中查找最左值,有的话就删掉。然后处理增加数字的情况(分两种情况:1.如果small的长度小于large的长度,再看如果large是空或者新加的数小于等于large的首元素,我们把此数加入small中。否则就把large的首元素移出并加入small中,然后把新数字加入large。2.如果small的长度大于large,再看如果新数字大于small的尾元素,那么新数字加入large中,否则就把small的尾元素移出并加入large中,把新数字加入small中)。最后我们再计算中位数并加入结果res中,根据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:
vector<double> medianSlidingWindow(vector<int>& nums, int k) {
vector<double> res;
multiset<int> small, large;
for (int i = 0; i < nums.size(); ++i) {
if (i >= k) {
if (small.count(nums[i - k])) small.erase(small.find(nums[i - k]));
else if (large.count(nums[i - k])) large.erase(large.find(nums[i - k]));
}
if (small.size() <= large.size()) {
if (large.empty() || nums[i] <= *large.begin()) small.insert(nums[i]);
else {
small.insert(*large.begin());
large.erase(large.begin());
large.insert(nums[i]);
}
} else {
if (nums[i] >= *small.rbegin()) large.insert(nums[i]);
else {
large.insert(*small.rbegin());
small.erase(--small.end());
small.insert(nums[i]);
}
}
if (i >= (k - 1)) {
if (k % 2) res.push_back(*small.rbegin());
else res.push_back(((double)*small.rbegin() + *large.begin()) / 2);
}
}
return res;
}
};

Leetcode481. Magical String

A magical string S consists of only ‘1’ and ‘2’ and obeys the following rules:

The string S is magical because concatenating the number of contiguous occurrences of characters ‘1’ and ‘2’ generates the string S itself.

The first few elements of string S is the following: S = “1221121221221121122……”

If we group the consecutive ‘1’s and ‘2’s in S, it will be:

1
1 22 11 2 1 22 1 22 11 2 11 22 ……

and the occurrences of ‘1’s or ‘2’s in each group are:

1
1 2 2 1 1 2 1 2 2 1 2 2 ……

You can see that the occurrence sequence above is the S itself.

Given an integer N as input, return the number of ‘1’s in the first N number in the magical string S.

Note: N will not exceed 100,000.

Example 1:

1
2
3
Input: 6
Output: 3
Explanation: The first 6 elements of magical string S is "12211" and it contains three 1's, so return 3.

这道题介绍了一种神奇字符串,只由1和2组成,通过计数1组和2组的个数,又能生成相同的字符串。而让我们求前n个数字中1的个数。让我们按规律生成这个神奇字符串,只有生成了字符串的前n个字符,才能统计出1的个数。其实这道题的难点就是在于找到规律来生成字符串。

根据第三个数字2开始往后生成数字,此时生成两个1,然后根据第四个数字1,生成一个2,再根据第五个数字1,生成一个1,以此类推,生成的数字1或2可能通过异或3来交替生成,在生成的过程中同时统计1的个数即可,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int magicalString(int n) {
if (n <= 0) return 0;
if (n <= 3) return 1;
int res = 1, head = 2, tail = 3, num = 1;
vector<int> v{1, 2, 2};
while (tail < n) {
for (int i = 0; i < v[head]; ++i) {
v.push_back(num);
if (num == 1 && tail < n) ++res;
++tail;
}
num ^= 3;
++head;
}
return res;
}
}

Leetcode482. License Key Formatting

You are given a license key represented as a string S which consists only alphanumeric character and dashes. The string is separated into N+1 groups by N dashes.

Given a number K, we would want to reformat the strings such that each group contains exactly K characters, except for the first group which could be shorter than K, but still must contain at least one character. Furthermore, there must be a dash inserted between two groups and all lowercase letters should be converted to uppercase.

Given a non-empty string S and a number K, format the string according to the rules described above.

Example 1:

1
2
3
4
Input: S = "5F3Z-2e-9-w", K = 4
Output: "5F3Z-2E9W"
Explanation: The string S has been split into two parts, each part has 4 characters.
Note that the two extra dashes are not needed and can be removed.

Example 2:
1
2
3
Input: S = "2-5g-3-J", K = 2
Output: "2-5G-3J"
Explanation: The string S has been split into three parts, each part has 2 characters except the first part as it could be shorter as mentioned above.

Note:

  • The length of string S will not exceed 12,000, and K is a positive integer.
  • String S consists only of alphanumerical characters (a-z and/or A-Z and/or 0-9) and dashes(-).
  • String S is non-empty.

繁琐的字符串拼接题。。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
string licenseKeyFormatting(string S, int K) {
string tmp, res;
for (int i = S.size() - 1; i >= 0; i--) {
if(S[i] == '-')
continue;
tmp = (char) toupper(S[i]) + tmp;
if(tmp.length() == K) {
res.insert(0, "-" + tmp);
tmp.clear();
}
}
if (tmp.empty() && !res.empty()) return res.substr(1);
res = tmp + res;
return res;
}
};

Leetcode485. Max Consecutive Ones

Given a binary array, find the maximum number of consecutive 1s in this array.

Example 1:

1
2
3
4
Input: [1,1,0,1,1,1]
Output: 3
Explanation: The first two digits or the last three digits are consecutive 1s.
The maximum number of consecutive 1s is 3.

计算最长的连续1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int findMaxConsecutiveOnes(vector<int>& nums) {
int res = 0, count = 0;;
for(int i = 0; i < nums.size(); i ++) {
if(nums[i] == 0) {
res = count > res ? count : res;
count = 0;
}
else
count ++;
}
res = count > res ? count : res;
return res;
}
};

Leetcode486. Predict the Winner

Given an array of scores that are non-negative integers. Player 1 picks one of the numbers from either end of the array followed by the player 2 and then player 1 and so on. Each time a player picks a number, that number will not be available for the next player. This continues until all the scores have been chosen. The player with the maximum score wins.

Given an array of scores, predict whether player 1 is the winner. You can assume each player plays to maximize his score.

Example 1:

1
2
3
4
5
6
Input: [1, 5, 2]
Output: False
Explanation: Initially, player 1 can choose between 1 and 2.
If he chooses 2 (or 1), then player 2 can choose from 1 (or 2) and 5. If player 2 chooses 5, then player 1 will be left with 1 (or 2).
So, final score of player 1 is 1 + 2 = 3, and player 2 is 5.
Hence, player 1 will never be the winner and you need to return False.

Example 2:

1
2
3
4
Input: [1, 5, 233, 7]
Output: True
Explanation: Player 1 first chooses 1. Then player 2 have to choose between 5 and 7. No matter which number player 2 choose, player 1 can choose 233.
Finally, player 1 has more score (234) than player 2 (12), so you need to return True representing player1 can win.

Note:

  • 1 <= length of the array <= 20.
  • Any scores in the given array are non-negative integers and will not exceed 10,000,000.
  • If the scores of both players are equal, then player 1 is still the winner.

这道题给了一个小游戏,有一个数组,两个玩家轮流取数,说明了只能从开头或结尾取,问我们第一个玩家能赢吗。而且当前玩家赢返回 true 的条件就是递归调用下一个玩家输返回 false。这里需要一个变量来标记当前是第几个玩家,还需要两个变量来分别记录两个玩家的当前数字和,在递归函数里面,如果当前数组为空了,直接比较两个玩家的当前得分即可,如果数组中只有一个数字了,根据玩家标识来将这个数字加给某个玩家并进行比较总得分。如果数组有多个数字,分别生成两个新数组,一个是去掉首元素,一个是去掉尾元素,然后根据玩家标识分别调用不同的递归,只要下一个玩家两种情况中任意一种返回 false 了,那么当前玩家就可以赢了,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
bool PredictTheWinner(vector<int>& nums) {
return canWin(nums, 0, 0, 1);
}
bool canWin(vector<int> nums, int sum1, int sum2, int player) {
if (nums.empty()) return sum1 >= sum2;
if (nums.size() == 1) {
if (player == 1) return sum1 + nums[0] >= sum2;
else if (player == 2) return sum2 + nums[0] > sum1;
}
vector<int> va = vector<int>(nums.begin() + 1, nums.end());
vector<int> vb = vector<int>(nums.begin(), nums.end() - 1);
if (player == 1) {
return !canWin(va, sum1 + nums[0], sum2, 2) || !canWin(vb, sum1 + nums.back(), sum2, 2);
} else if (player == 2) {
return !canWin(va, sum1, sum2 + nums[0], 1) || !canWin(vb, sum1, sum2 + nums.back(), 1);
}
}
};

两人依次拿,如果Player1赢,则Player1拿的>Player2拿的。我们把Player1拿的视为”+”,把Player2拿的视为”-“,如果最后结果大于等于0则Player1赢。

因此对于递归来说,beg ~ end的结果为max(nums[beg] - partition(beg + 1, end), nums[end] - partition(beg, end + 1));对于非递归来说DP[beg][end]表示即为beg ~ end所取的值的大小(最终与零比较)。

总结:

  1. 该问题没有直接比较一个选手所拿元素的和值,而是把问题转换为两个选手所拿元素的差值。这一点很巧妙,是关键的一步。
  2. 找出递推表达式:max(nums[beg] - partition(beg + 1, end), nums[end] - partition(beg, end + 1))
  3. 通过递推表达式构造递归算法是比较简单的。但是要构造一个非递归的算法难度较大。对于非递归算法,首先在dp中赋初始值,这是我们解题的第一步。在这个问题中,我们使用一个二位的数组dp来表示nums数组中任意开始和结束位置两人结果的差值。

初始的时候,我们仅仅知道对角线上的值。dp[i][i] = nums[i]

接下来既然是求任意的开始和结束,对于二维数组,那肯定是一个双层的循环。通过dp中已知的元素和动态规划的递推表达式,我们就可以构造出我们的需要的结果。非递归的方式是从小问题到大问题的过程。

1
2
3
4
5
6
7
8
9
10
public class Solution {
public boolean PredictTheWinner(int[] nums) {
return helper(nums, 0, nums.length-1) >= 0;
}

public int helper(int[] nums, int start, int end) {
if(start == end) return nums[start];
else return Math.max(nums[start]-helper(nums, start+1, end), nums[end]-helper(nums, start, end-1));
}
}

【java代码——递归2(保存中间状态)】

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Solution {
public boolean PredictTheWinner(int[] nums) {
return helper(nums, 0, nums.length-1, new Integer[nums.length][nums.length]) >= 0;
}

public int helper(int[] nums, int start, int end, Integer[][] dp) {
if(dp[start][end] == null) {
if(start == end) return nums[start];
else return Math.max(nums[start]-helper(nums, start+1,end, dp), nums[end]-helper(nums, start,end-1, dp));
}
return dp[start][end];
}
}

Leetcode488. 祖玛游戏

你正在参与祖玛游戏的一个变种。

在这个祖玛游戏变体中,桌面上有 一排 彩球,每个球的颜色可能是:红色 ‘R’、黄色 ‘Y’、蓝色 ‘B’、绿色 ‘G’ 或白色 ‘W’ 。你的手中也有一些彩球。

你的目标是 清空 桌面上所有的球。每一回合:

从你手上的彩球中选出 任意一颗 ,然后将其插入桌面上那一排球中:两球之间或这一排球的任一端。
接着,如果有出现 三个或者三个以上 且 颜色相同 的球相连的话,就把它们移除掉。
如果这种移除操作同样导致出现三个或者三个以上且颜色相同的球相连,则可以继续移除这些球,直到不再满足移除条件。
如果桌面上所有球都被移除,则认为你赢得本场游戏。
重复这个过程,直到你赢了游戏或者手中没有更多的球。
给你一个字符串 board ,表示桌面上最开始的那排球。另给你一个字符串 hand ,表示手里的彩球。请你按上述操作步骤移除掉桌上所有球,计算并返回所需的 最少 球数。如果不能移除桌上所有的球,返回 -1 。

示例 1:

1
2
3
4
5
6
输入:board = "WRRBBW", hand = "RB"
输出:-1
解释:无法移除桌面上的所有球。可以得到的最好局面是:
- 插入一个 'R' ,使桌面变为 WRRRBBW 。WRRRBBW -> WBBW
- 插入一个 'B' ,使桌面变为 WBBBW 。WBBBW -> WW
桌面上还剩着球,没有其他球可以插入。

示例 2:

1
2
3
4
5
6
输入:board = "WWRRBBWW", hand = "WRBRW"
输出:2
解释:要想清空桌面上的球,可以按下述步骤:
- 插入一个 'R' ,使桌面变为 WWRRRBBWW 。WWRRRBBWW -> WWBBWW
- 插入一个 'B' ,使桌面变为 WWBBBWW 。WWBBBWW -> WWWW -> empty
只需从手中出 2 个球就可以清空桌面。

示例 3:

1
2
3
4
5
6
输入:board = "G", hand = "GGGGG"
输出:2
解释:要想清空桌面上的球,可以按下述步骤:
- 插入一个 'G' ,使桌面变为 GG 。
- 插入一个 'G' ,使桌面变为 GGG 。GGG -> empty
只需从手中出 2 个球就可以清空桌面。

示例 4:

1
2
3
4
5
6
7
输入:board = "RBYYBBRRB", hand = "YRBGB"
输出:3
解释:要想清空桌面上的球,可以按下述步骤:
- 插入一个 'Y' ,使桌面变为 RBYYYBBRRB 。RBYYYBBRRB -> RBBBRRB -> RRRB -> B
- 插入一个 'B' ,使桌面变为 BB 。
- 插入一个 'B' ,使桌面变为 BBB 。BBB -> empty
只需从手中出 3 个球就可以清空桌面。

根据题目要求,桌面上最多有 1616 个球,手中最多有 55 个球;我们可以以任意顺序在 55 个回合中使用手中的球;在每个回合中,我们可以选择将手中的球插入到桌面上任意两球之间或这一排球的任意一端。

因为插入球的颜色和位置的选择是多样的,选择的影响也可能在多次消除操作之后才能体现出来,所以通过贪心方法根据当前情况很难做出全局最优的决策。实际每次插入一个新的小球时,并不保证插入后一定可以消除,因此我们需要搜索和遍历所有可能的插入方法,找到最小的插入次数。比如以下测试用例:

桌面上的球为 RRWWRRBBRR,手中的球为 WB,如果我们按照贪心法每次插入进行消除就会出现无法完全消除。因此,我们使用广度优先搜索来解决这道题。即对状态空间进行枚举,通过穷尽所有的可能来找到最优解,并使用剪枝的方法来优化搜索过程。

为什么使用广度优先搜索?
我们不妨规定,每一种不同的桌面上球的情况和手中球的情况的组合都是一种不同的状态。对于相同的状态,其清空桌面上球所需的回合数总是相同的;而不同的插入球的顺序,也可能得到相同的状态。因此,如果使用深度优先搜索,则需要使用记忆化搜索,以避免重复计算相同的状态。

因为只需要找出需要回合数最少的方案,因此使用广度优先搜索可以得到可以消除桌面上所有球的方案时就直接返回结果,而不需要继续遍历更多需要回合数更多的方案。而广度优先搜索虽然需要在队列中存储较多的状态,但是因为使用深度优先搜索也需要存储这些状态及这些状态对应的结果,因此使用广度优先搜索并不会需要更多的空间。

在算法的实现中,我们可以通过以下方法来实现广度优先:

使用队列来维护需要处理的状态队列,使用哈希集合存储已经访问过的状态。每一次取出队列中的队头状态,考虑其中所有可以插入球的方案,如果新方案还没有被访问过,则将新方案添加到队列的队尾。

下面,我们考虑剪枝条件:

第 1 个剪枝条件:手中颜色相同的球每次选择时只需要考虑其中一个即可
如果手中有颜色相同的球,那么插入这些球中的哪一个都没有区别。因此,手中颜色相同的球,我们只需要考虑其中一个即可。在具体的实现中,我们可以先将手中的球排序,如果当前遍历的球的颜色和上一个遍历的球的颜色相同,则跳过当前遍历的球。

第 2 个剪枝条件:只在连续相同颜色的球的开头位置或者结尾位置插入新的颜色相同的球
如果桌面上有一个红球,那么在其左侧和右侧插入一个新的红球没有区别;同理,如果桌面上有 2 个连续的红球,那么在其左侧、中间和右侧插入一个新的红球没有区别。因此,如果新插入的球和桌面上某组连续颜色相同的球(也可以是 1 个)的颜色相同,我们只需要考虑在其左侧插入新球的情况即可。在具体的实现中,如果新插入的球和插入位置左侧的球的颜色相同,则跳过这个位置。

第 3 个剪枝条件:只考虑放置新球后有可能得到更优解的位置
考虑插入新球的颜色与插入位置周围球的颜色的情况,在已经根据第 2 个剪枝条件剪枝后,还可能出现如下三种情况:插入新球与插入位置右侧的球颜色相同;插入新球与插入位置两侧的球颜色均不相同,且插入位置两侧的球的颜色不同;插入新球与插入位置两侧的球颜色均不相同,且插入位置两侧的球的颜色相同。

对于「插入新球与插入位置右侧的球颜色相同」的情况,这种操作可能可以构成连续三个相同颜色的球实现消除,是有可能得到更优解的。读者可以结合以下例子理解。

例如:桌面上的球为 WWRRBBWW,手中的球为 WWRB,答案为 2。

操作方法如下:WWRRBBWW -> WW(R)RRBBWW -> WWBBWW -> WW(B)BBWW -> WWWW “”。

对于「插入新球与插入位置两侧的球颜色均不相同,且插入位置两侧的球的颜色不同」的情况,这种操作可以将连续相同颜色的球拆分到不同的组合中消除,也是有可能得到更优解的。读者可以结合以下例子理解。

例如:桌面上的球为 RRWWRRBBRR,手中的球为 WB,答案为 2。

操作方法如下:RRWWRRBBRR→RRWWRRBBR(W)R→RRWWRR(B)BBRWR→RRWWRRRWR→RRWWWR→RRR→””。

对于「插入新球与插入位置两侧的球颜色均不相同,且插入位置两侧的球的颜色相同」的情况,这种操作并不能对消除顺序产生任何影响。如插入位置旁边的球可以消除的话,那么这种插入方法与直接将新球插入到与之颜色相同的球的旁边没有区别。因此,这种操作不能得到比「插入新球与插入位置右侧的球颜色相同」更好的情况,得到更优解。读者可以结合以下例子理解。

例如:桌面上的球为 WWRRBBWW,手中的球为 WWRB,答案为 2。

操作方法如下:WWRRBBWW→WWRRBB(R)WW→WWRRB(B)BRWW→WWRRRWW→WWWW→””。

细节

题目规定了如果在消除操作后,如果导致出现了新的连续三个或者三个以上颜色相同的球,则继续消除这些球,直到不再满足消除条件,实际消除时我们可以利用栈的特性,每次遇到连续可以消除的球时,我们就将其从栈中弹出。在实现中,我们可以在遍历桌面上的球时,使用列表维护遍历过的每种球的颜色和连续数量,从而通过一次遍历消除连续三个或者三个以上颜色相同的球。具体地:

使用 visited_ball 维护遍历过的每种球的颜色和连续数量,设其中最后一个颜色 last_color,其连续数量为last_num;遍历桌面上的球,设当前遍历到的球为cur_ball,其颜色为cur_color。

  • 首先,判断:如果visited_ball 不为空,且cur_color 与last_color 不同,则判断:如果last_num 大于等于 3,则从visited_ball 中移除last_color 和last_num。
  • 接着,判断:如果visited_ball 为空,或cur_color 与last_color 不同,则向visited_ball 添加cur_color 及连续数量 1;
  • 否则,累加last_num。
  • 最后,根据列表中维护的每种球的颜色和连续数量,重新构造桌面上的球的组合即可。
1
2
3
4
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
struct State {
string board;
string hand;
int step;
State(const string & board, const string & hand, int step) {
this->board = board;
this->hand = hand;
this->step = step;
}
};

class Solution {
public:
string clean(const string & s) {
string res;
vector<pair<char, int>> st;

for (auto c : s) {
while (!st.empty() && c != st.back().first && st.back().second >= 3) {
st.pop_back();
}
if (st.empty() || c != st.back().first) {
st.push_back({c,1});
} else {
st.back().second++;
}
}
if (!st.empty() && st.back().second >= 3) {
st.pop_back();
}
for (int i = 0; i < st.size(); ++i) {
for (int j = 0; j < st[i].second; ++j) {
res.push_back(st[i].first);
}
}
return res;
}

int findMinStep(string board, string hand) {
unordered_set<string> visited;
sort(hand.begin(), hand.end());

visited.insert(board + " " + hand);
queue<State> qu;
qu.push(State(board, hand, 0));
while (!qu.empty()) {
State curr = qu.front();
qu.pop();

for (int j = 0; j < curr.hand.size(); ++j) {
// 第 1 个剪枝条件: 当前选择的球的颜色和前一个球的颜色相同
if (j > 0 && curr.hand[j] == curr.hand[j - 1]) {
continue;
}
for (int i = 0; i <= curr.board.size(); ++i) {
// 第 2 个剪枝条件: 只在连续相同颜色的球的开头位置插入新球
if (i > 0 && curr.board[i - 1] == curr.hand[j]) {
continue;
}

// 第 3 个剪枝条件: 只在以下两种情况放置新球
bool choose = false;
// 第 1 种情况 : 当前球颜色与后面的球的颜色相同
if (i < curr.board.size() && curr.board[i] == curr.hand[j]) {
choose = true;
}
// 第 2 种情况 : 当前后颜色相同且与当前颜色不同时候放置球
if (i > 0 && i < curr.board.size() && curr.board[i - 1] == curr.board[i] && curr.board[i] != curr.hand[j]){
choose = true;
}
if (choose) {
string new_board = clean(curr.board.substr(0, i) + curr.hand[j] + curr.board.substr(i));
string new_hand = curr.hand.substr(0, j) + curr.hand.substr(j + 1);
if (new_board.size() == 0) {
return curr.step + 1;
}
if (!visited.count(new_board + " " + new_hand)) {
qu.push(State(new_board, new_hand, curr.step + 1));
visited.insert(new_board + " " + new_hand);
}
}
}
}
}

return -1;
}
};

Leetcode491. Increasing Subsequences

Given an integer array nums, return all the different possible increasing subsequences of the given array with at least two elements. You may return the answer in any order.

The given array may contain duplicates, and two equal integers should also be considered a special case of increasing sequence.

Example 1:

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

Example 2:

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

这道题让我们找出所有的递增子序列,应该不难想到,这题肯定是要先找出所有的子序列,从中找出递增的。首先来看一种迭代的解法,对于重复项的处理,最偷懒的方法是使用 TreeSet,利用其自动去处重复项的机制,然后最后返回时再转回 vector 即可。由于是找递增序列,所以需要对递归函数做一些修改,首先题目中说明了递增序列数字至少两个,所以只有子序列个数大于等于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<vector<int>> findSubsequences(vector<int>& nums) {
set<vector<int>> res;
vector<int> s;
helper(nums, 0, s, res);
return vector(res.begin(), res.end());
}

void helper(vector<int>& nums, int i, vector<int> s, set<vector<int>>& res) {
if (s.size() > 1)
res.insert(s);
for (; i < nums.size(); i ++) {
if (!s.empty() && s.back() > nums[i])
continue;
s.push_back(nums[i]);
helper(nums, i+1, s, res);
s.pop_back();
}
}
};

Leetcode492. Construct the Rectangle

For a web developer, it is very important to know how to design a web page’s size. So, given a specific rectangular web page’s area, your job by now is to design a rectangular web page, whose length L and width W satisfy the following requirements:

  1. The area of the rectangular web page you designed must equal to the given target area.
  2. The width W should not be larger than the length L, which means L >= W.
  3. The difference between length L and width W should be as small as possible.

You need to output the length L and the width W of the web page you designed in sequence.
Example:

1
2
3
4
Input: 4
Output: [2, 2]
Explanation: The target area is 4, and all the possible ways to construct it are [1,4], [2,2], [4,1].
But according to requirement 2, [1,4] is illegal; according to requirement 3, [4,1] is not optimal compared to [2,2]. So the length L is 2, and the width W is 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
class Solution {
public:
vector<int> constructRectangle(int area) {
int ii = sqrt(area);
vector<int> res;
res.push_back(1);
res.push_back(area);
for(int i = 1; i <= ii; i ++) {
int temp = area / i;
if(temp * i == area) {
if(abs(res[0]-res[1]) > abs(temp - i)) {
res[0] = i;
res[1] = temp;
}
}
}
if(res[0] < res[1]) {
int temp = res[0];
res[0] = res[1];
res[1] = temp;
}
return res;
}
};

Leetcode493. Reverse Pairs

Given an array nums, we call (i, j) an important reverse pair if i < j and nums[i] > 2*nums[j].

You need to return the number of important reverse pairs in the given array.

Example1:

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

Example2:

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

Note:

  • The length of the given array will not exceed 50,000.
  • All the numbers in the input array are in the range of 32-bit integer.

一种方法叫分割重现关系 (Partition Recurrence Relation),用式子表示是 T(i, j) = T(i, m) + T(m+1, j) + C。这里的C就是处理合并两个部分的子问题,那么用文字来描述就是“已知翻转对的两个数字分别在子数组 nums[i, m] 和 nums[m+1, j] 之中,求满足要求的翻转对的个数”,这里翻转对的两个条件中的顺序条件已经满足,就只需要找到满足大小关系的的数对即可。如果两个子数组是有序的,那么我们可以用双指针的方法在线性时间内就可以统计出符合题意的翻转对的个数。要想办法产生有序的子数组,那么这就和 MergeSort 的核心思想完美匹配了。我们知道混合排序就是不断的将数组对半拆分成子数组,拆到最小的数组后开始排序,然后一层一层的返回,最后原数组也是有序的了。这里我们在混合排序的递归函数中,对有序的两个子数组进行统计翻转对的个数,区间 [left, mid] 和 [mid+1, right] 内的翻转对儿个数就被分别统计出来了,此时还要统计翻转对儿的两个数字分别在两个区间中的情况,那么i遍历 [left, mid] 区间所有的数字,j则从 mid+1 开始检测,假如 nums[i] 大于 nums[j] 的二倍,则这两个数字就是翻转对,此时j再自增1,直到不满足这个条件停止,则j增加的个数就是符合题意的翻转对的个数,所以用当前的j减去其初始值 mid+1 即为所求,然后再逐层返回

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

Leetcode494. 目标和

给你一个整数数组 nums 和一个整数 target 。

向数组中的每个整数前添加 ‘+’ 或 ‘-‘ ,然后串联起所有整数,可以构造一个 表达式 :

例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-‘ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

示例 1:

1
2
3
4
5
6
7
8
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3

示例 2:

1
2
输入:nums = [1], target = 1
输出:1

这道题给了我们一个数组,和一个目标值,让给数组中每个数字加上正号或负号,然后求和要和目标值相等,求有多少中不同的情况。那么对于这种求多种情况的问题,博主最想到的方法使用递归来做。从第一个数字,调用递归函数,在递归函数中,分别对目标值进行加上当前数字调用递归,和减去当前数字调用递归,这样会涵盖所有情况,并且当所有数字遍历完成后,若目标值为0了,则结果 res 自增1,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
int res = 0;
helper(nums, S, 0, res);
return res;
}
void helper(vector<int>& nums, long S, int start, int& res) {
if (start >= nums.size()) {
if (S == 0) ++res;
return;
}
helper(nums, S - nums[start], start + 1, res);
helper(nums, S + nums[start], start + 1, res);
}
};

我们对上面的递归方法进行优化,使用 memo 数组来记录中间值,这样可以避免重复运算,参见代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
vector<unordered_map<int, int>> memo(nums.size());
return helper(nums, S, 0, memo);
}
int helper(vector<int>& nums, long sum, int start, vector<unordered_map<int, int>>& memo) {
if (start == nums.size()) return sum == 0;
if (memo[start].count(sum)) return memo[start][sum];
int cnt1 = helper(nums, sum - nums[start], start + 1, memo);
int cnt2 = helper(nums, sum + nums[start], start + 1, memo);
return memo[start][sum] = cnt1 + cnt2;
}
};

Leetcode495. Teemo Attacking

Our hero Teemo is attacking an enemy Ashe with poison attacks! When Teemo attacks Ashe, Ashe gets poisoned for a exactly duration seconds. More formally, an attack at second t will mean Ashe is poisoned during the inclusive time interval [t, t + duration - 1]. If Teemo attacks again before the poison effect ends, the timer for it is reset, and the poison effect will end duration seconds after the new attack.

You are given a non-decreasing integer array timeSeries, where timeSeries[i] denotes that Teemo attacks Ashe at second timeSeries[i], and an integer duration.

Return the total number of seconds that Ashe is poisoned.

Example 1:

1
2
3
4
5
6
Input: timeSeries = [1,4], duration = 2
Output: 4
Explanation: Teemo's attacks on Ashe go as follows:
- At second 1, Teemo attacks, and Ashe is poisoned for seconds 1 and 2.
- At second 4, Teemo attacks, and Ashe is poisoned for seconds 4 and 5.
Ashe is poisoned for seconds 1, 2, 4, and 5, which is 4 seconds in total.

Example 2:

1
2
3
4
5
6
Input: timeSeries = [1,2], duration = 2
Output: 3
Explanation: Teemo's attacks on Ashe go as follows:
- At second 1, Teemo attacks, and Ashe is poisoned for seconds 1 and 2.
- At second 2 however, Teemo attacks again and resets the poison timer. Ashe is poisoned for seconds 2 and 3.
Ashe is poisoned for seconds 1, 2, and 3, which is 3 seconds in total.

直接使用贪心算法,比较相邻两个时间点的时间差,如果小于duration,就加上这个差,如果大于或等于,就加上duration即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int findPoisonedDuration(vector<int>& timeSeries, int duration) {
int last = -1, res = 0;
for (int i = 0; i < timeSeries.size(); i ++) {
if (timeSeries[i] <= last) {
if (timeSeries[i] + duration > last) {
res = res + (timeSeries[i] + duration - last -1);
last = timeSeries[i] + duration - 1;
}
}
else {
res += duration;
last = timeSeries[i] + duration - 1;
}
}
return res;
}
};

Leetcode496. Next Greater Element I

You are given two arrays (without duplicates) nums1 and nums2 where nums1’s elements are subset of nums2. Find all the next greater numbers for nums1’s elements in the corresponding places of nums2.

The Next Greater Number of a number x in nums1 is the first greater number to its right in nums2. If it does not exist, output -1 for this number.

Example 1:

1
2
3
4
5
6
Input: nums1 = [4,1,2], nums2 = [1,3,4,2].
Output: [-1,3,-1]
Explanation:
For number 4 in the first array, you cannot find the next greater number for it in the second array, so output -1.
For number 1 in the first array, the next greater number for it in the second array is 3.
For number 2 in the first array, there is no next greater number for it in the second array, so output -1.

Example 2:
1
2
3
4
5
Input: nums1 = [2,4], nums2 = [1,2,3,4].
Output: [3,-1]
Explanation:
For number 2 in the first array, the next greater number for it in the second array is 3.
For number 4 in the first array, there is no next greater number for it in the second array, so output -1.

在num2中找到num1的每个元素,然后从这个元素往后找一个比它大的数,用标志位控制即可。

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<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
vector<int> res;
int first = nums1.size(), second = nums2.size();
int i, j;
bool find;
for(i = 0; i < first; i ++){
find = false;
for(j = 0; j < second; j ++) {
if(nums1[i] == nums2[j])
find = true;
if(find && nums1[i] < nums2[j])
break;
}
if(j == second)
res.push_back(-1);
else
res.push_back(nums2[j]);
}
return res;
}
};

Leetcode498. Diagonal Traverse

Given an m x n matrix mat, return an array of all the elements of the array in a diagonal order.

Example 1:

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

Example 2:

1
2
Input: mat = [[1,2],[3,4]]
Output: [1,2,3,4]

这道题给了我们一个mxn大小的数组,让我们进行对角线遍历,先向右上,然后左下,再右上,以此类推直至遍历完整个数组,题目中的例子和图示也能很好的帮我们理解。由于移动的方向不再是水平或竖直方向,而是对角线方向,那么每移动一次,横纵坐标都要变化,向右上移动的话要坐标加上[-1, 1],向左下移动的话要坐标加上[1, -1],那么难点在于我们如何处理越界情况,越界后遍历的方向怎么变换。向右上和左下两个对角线方向遍历的时候都会有越界的可能,但是除了左下角和右上角的位置越界需要改变两个坐标之外,其余的越界只需要改变一个。那么我们就先判断要同时改变两个坐标的越界情况,即在右上角和左下角的位置。如果在右上角位置还要往右上走时,那么要移动到它下面的位置的,那么如果col超过了n-1的范围,那么col重置为n-1,并且row自增2,然后改变遍历的方向。同理如果row超过了m-1的范围,那么row重置为m-1,并且col自增2,然后改变遍历的方向。然后我们再来判断一般的越界情况,如果row小于0,那么row重置0,然后改变遍历的方向。同理如果col小于0,那么col重置0,然后改变遍历的方向。参见代码如下:

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> findDiagonalOrder(vector<vector<int>>& mat) {
if (mat.empty() || mat[0].empty())
return {};
int x = 0, y = 0, dir = 0;
vector<int> res;
vector<vector<int>> dirs{{-1,1}, {1,-1}};
int m = mat.size(), n = mat[0].size(), size = m*n;
for (int i = 0; i < size; i ++) {
res.push_back(mat[x][y]);
x += dirs[dir][0];
y += dirs[dir][1];
if (x >= m) { x = m - 1; y += 2; dir = 1 - dir; }
if (y >= n) { y = n - 1; x += 2; dir = 1 - dir; }
if (x < 0) { x = 0; dir = 1 - dir; }
if (y < 0) { y = 0; dir = 1 - dir; }
}
return res;
}
};

Leetcode500. Keyboard Row

Given a List of words, return the words that can be typed using letters of alphabet on only one row’s of American keyboard like the image below.

Example:

1
2
Input: ["Hello", "Alaska", "Dad", "Peace"]
Output: ["Alaska", "Dad"]

给出n个字符串,从而判断每个字符串中的字符石头来自美式键盘上的同一行,若来自同一行,返回该string。过程将键盘上的每行字符存储到相应的vector或者数组中,然后循环Input中的每个string,并且循环string中的每个char,从而进行比较。

1
2
3
4
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<string> findWords(vector<string>& words) {
std::unordered_set <char> row1={'q','w','e','r','t','y','u','i','o','p'};
std::unordered_set <char> row2={'a','s','d','f','g','h','j','k','l'};
std::unordered_set <char> row3={'z','x','c','v','b','n','m'};
vector<string> res;

for(string word : words) {
bool d1=true, d2=true, d3=true;
for(char c : word) {
if(d1) {
auto re = row1.find(tolower(c));
if(re == row1.end())
d1 = false;
}
if(d2) {
auto re = row2.find(tolower(c));
if(re == row2.end())
d2 = false;
}
if(d3) {
auto re = row3.find(tolower(c));
if(re == row3.end())
d3 = false;
}
}
if(d1||d2||d3)
res.push_back(word);
}
return res;
}
};

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]);
}
};