LeetCode 76. 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).

For example, S = "ADOBECODEBANC" T = "ABC"

Minimum window is "BANC".

Note: If there is no such window in S that covers all characters in T, return the empty string "".

If there are multiple such windows, you are guaranteed that there will always be only one unique minimum window in S.

这道题叫做MInimum Window Substring, 很容易想到使用滑动窗口. 使用一个ASCII码做索引的数组来保存每个字符出现的次数, 然后先找到以S的第一个字符为开头的满足条件的子串, 然后跳过子串的前部字符直到子串不满足条件, 此时得到的便是第一个最小值. 显然一个子串如果包含T中的所有元素并且尽量短, 那么它的两端字符肯定都在T中(因为如果不在, 那么删掉它们可得到更短的子串),

从刚才得到的第一个子串开始, 去掉开头的字符, 把子串向后延伸, 直到再次出现该字符为止, 然后不断去掉开头的字符, 直到子串尽可能短, 便可以得到下一个子串. 窗口以"伸-缩-伸-缩…"的方式前进, 算法是线性的(不考虑判断是否符合条件). 记录整个过程中最短的子串开始位置与长度.

另一个重要问题是如何判断一个子串是否符合条件, 即是否包含T中的所有字符. 最直接的方法是循环比较每个字符出现的次数(子串首尾移动时同时更新子串中字符的出现次数), 这样的话复杂度大约为O(CHAR_MAX*s.length()). 一般来说CHAR_MAX = 127, 这个系数还是比较大的.

因为除了第一个子串外, 后续的子串都以前一个子串为基础变化而来, 子串开头的下标start在前进时子串始终是处于符合要求的状态, 当不符合要求时start才停下, 所以每当start扫过一个字符, 就把它在子串中的出现次数减1, 然后只比较该字符与T中该字符的出现次数, 当小于时则子串不符合要求. 对于子串结尾end, 则只要找到上一个符合条件的子串的第一个元素就可以了.

说起来还是比较混乱, 其实代码写起来也比较混乱, 双指针start与end的++, --操作很多, 很容易出错…

1
2
3
4
5
6
7
8
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:
vector<int> tFlag = vector<int>(CHAR_MAX + 1, 0);
string minWindow(string s, string t) {
if (t.empty() || s.empty() || s.length() < t.length()) return "";

for (int i = 0; i < t.length(); i++) {
tFlag[t[i]]++;
}
vector<int> flag(CHAR_MAX + 1, 0);

int start = 0, end = start;
int minLen = INT_MAX, minStart;

for (; end <= s.length() && !matchEnd(flag, s[end - 1]); end++) {
flag[s[end]]++;
}
if (end > s.length()) return "";
while (matchStart(flag, s[start - 1])) {
flag[s[start]]--;
start++;
}
start--;
flag[s[start]]++;

minLen = end - start;
minStart = start;
end--;

while (true) {
char ch = s[start];
flag[ch]--;

end++;
while (end < s.length() && s[end] != ch) {
flag[s[end]]++;
end++;
}
if (end == s.length()) break;
flag[ch]++;

start++;
while (matchStart(flag, s[start - 1])) {
flag[s[start]]--;
start++;
}
start--;
flag[s[start]]++;
if (minLen > end - start + 1) {
minLen = end - start + 1;
minStart = start;
}
}

if (minLen == INT_MAX) return "";
return s.substr(minStart, minLen);
}

bool matchEnd(vector<int> &flag, int index) {
if(flag[index] < tFlag[index]){
return false;
}
for (int i = 0; i < CHAR_MAX + 1; i++) {
if (flag[i] < tFlag[i]) return false;
}
return true;
}

bool matchStart(vector<int> &flag, int index) {
return flag[index] >= tFlag[index];
}
};

LeetCode 384. Shuffle an Array

题目描述:

Shuffle a set of numbers without duplicates.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
// Init an array with set 1, 2, and 3.
int[] nums = {1,2,3};
Solution solution = new Solution(nums);

// Shuffle the array [1,2,3] and return its result. Any permutation of [1,2,3] must equally likely to be returned.
solution.shuffle();

// Resets the array back to its original configuration [1,2,3].
solution.reset();

// Returns the random shuffling of array [1,2,3].
solution.shuffle();

实现一个洗牌类, 实际的要求是生成1到n个不重复的随机数, 范围在[1,n]之间.

其实我不太明白为什么要有一个reset方法, 因为这种随机的排列中这个一开始的原始序列并没有什么特殊性, 也不会影响下一次调用shuffle时的概率.

在LeetCode的Discuss中有貌似题目作者的回复https://discuss.leetcode.com/topic/53984/reset-makes-no-sense/2:

The reason I designed it that way is to increase the likelihood that when shuffle is called it is shuffling based on the original array, not the previously shuffled array. This will increase the chance of detecting bugs in the main algorithm in shuffle.

关于生成随机排列, 我的方法是下标从低到高一次确定每个位置的函数, 假设下标范围为[0,n), 当前下标为i, 那么从[i,n)中取得一个随机数r, 交换下标i与r中的元素, 然后i增加1.

运行时间300多ms. 这个算法实际上叫做Fisher–Yates shuffle, Knuth在TAOCP中也介绍过.

1
2
3
4
5
6
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 {
vector<int> orignalNums, ret;
public:
Solution(vector<int> nums) {
srand((unsigned)time(NULL));
ret = orignalNums = nums;
}

/** Resets the array to its original configuration and return it. */
vector<int> reset() {
ret = orignalNums; // 这条语句其实没什么影响
return orignalNums;
}

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

/**
* Your Solution object will be instantiated and called as such:
* Solution obj = new Solution(nums);
* vector<int> param_1 = obj.reset();
* vector<int> param_2 = obj.shuffle();
*/

LeetCode 75. Sort Colors

题目描述:

Given an array with n objects colored red, white or blue, sort them 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’s sort function for this problem.

Follow up: A rather straight forward solution is a two-pass algorithm using counting sort. First, iterate the array counting number of 0’s, 1’s, and 2’s, then overwrite array with total number of 0’s, then 1’s and followed by 2’s.

Could you come up with an one-pass algorithm using only constant space?

由于数组中元素的值只有三种, 所以可以先遍历一遍数组, 分别记录下三种值的出现次数, 然后再按照值的大小和数量填充到数组中.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public:
void sortColors(vector<int>& nums) {
int colorNum[3] = {0,0,0};
for(int i = 0; i < nums.size(); i++){
colorNum[nums[i]]++;
}
int k = 0;
for(int i = 0; i < 3; i++){
for(int j = 0; j < colorNum[i]; j++){
nums[k++] = i;
}
}
}
};

关于Follow up中的单次遍历, 可以使用两个指针分别指向red(0)和blue(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:
void sortColors(vector<int>& nums) {
int redIndex = 0, blueIndex = nums.size() - 1;
while(nums[redIndex] == 0) redIndex++;
while(nums[blueIndex] == 2) blueIndex--;
int i = redIndex;
while(i <= blueIndex){
if(nums[i] == 0){
swap(nums[redIndex++], nums[i]);
i = max(redIndex, i); // 由于i有可能落后于redIndex, 所以要选择一个较大的值
}
else if(nums[i] == 2){
swap(nums[blueIndex--], nums[i]);
}
else{
i++;
}
}
}
};

LeetCode 383. 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.

Note: You may assume that both strings contain only lowercase letters.

1
2
3
canConstruct("a", "b") -> false
canConstruct("aa", "ab") -> false
canConstruct("aa", "aab") -> true

Ransom note的意思是勒索信或者绑架信, 题目要求判断是不是所有绑架信上的字母都可以用magazines中的字母拼出来. 使用的方法是类似hash表, 不过hash值就是字母在字母表中的位置.

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

LeetCode 74. 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.

For example,

Consider the following matrix:

1
2
3
4
5
[
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int row = getRow(matrix, target) - 1;
if(row == -1){
if(matrix[0][0] != target)
return false;
else
return true;
}
return getTarget(matrix, target, row);
}

int getRow(vector<vector<int>> &matrix, int target){
int n = matrix.size();
int left = 0, right = n, mid = (left + right) / 2;
while(left < right){
if(matrix[mid][0] == target)
return mid + 1;
if(matrix[mid][0] < target){
left = mid + 1;
}
else{
right = mid;
}
mid = (left + right) / 2;
}
return mid;
}

bool getTarget(vector<vector<int>> &matrix, int target, int row){
int n = matrix[row].size();
int left = 0, right = n, mid = (left + right) / 2;
while(left < right){
if(matrix[row][mid] == target)
return true;
if(matrix[row][mid] < target){
left = mid + 1;
}
else{
right = mid;
}
mid = (left + right) / 2;
}
return false;
}
};

LeetCode 73. 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.

**Follow up:**Did you use extra space? 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?

这道题主要的要求在于空间复杂度, O(mn)的复杂度就是使用另一个m×n的矩阵来保存结果. O(m+n)的复杂度则是用两个数组分别保存要设置为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
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m = matrix.size();
if(!m){
return;
}
int n = matrix[0].size();
vector<int> rowFlag(m, 1), colFlag(n, 1);
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(matrix[i][j] == 0){
rowFlag[i] = 0;
colFlag[j] = 0;
}
}
}
for(int i = 0; i < m; i++){
if(!rowFlag[i]){
setZeroRow(matrix, i, n);
}
}
for(int i = 0; i < n; i++){
if(!colFlag[i]){
setZeroCol(matrix, i, m);
}
}
}

void setZeroRow(vector<vector<int>> &matrix, int row, int n){
for(int i = 0; i < n; i++)
matrix[row][i] = 0;
}

void setZeroCol(vector<vector<int>> &matrix, int col, int m){
for(int i = 0; i < m; i++)
matrix[i][col] = 0;
}
};

而不使用额外空间的方法不是那么直观, 我的方法是先找到一个为0的位置(r,c), 然后使用它所在的行和列来保存要置为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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int x = -1, y = -1, m = matrix.size();
if(!m){
return;
}
int n = matrix[0].size();
// 以下找到第一个0所在的位置
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(matrix[i][j] == 0){
y = j;
break;
}
}
if(y != -1){
x = i;
break;
}
}
if(x == -1 && y == -1)
return;
// 因为之前的位置不存在0, 所以可以从x开始遍历
for(int i = x; i < m; i++){
for(int j = 0; j < n; j++){
if(matrix[i][j] == 0){
matrix[x][j] = 0;
matrix[i][y] = 0;
}
}
}

for(int i = 0; i < m; i++){
if(i == x)
continue; // 跳过x, 要在最后处理
if(matrix[i][y] == 0){
setZeroRow(matrix, i, n);
}
}

for(int i = 0; i < n; i++){
if(i == y)
continue;
if(matrix[x][i] == 0){
setZeroCol(matrix, i, m);
}
}

setZeroRow(matrix, x, n);
setZeroCol(matrix, y, m);
}

void setZeroRow(vector<vector<int>> &matrix, int row, int n){
for(int i = 0; i < n; i++)
matrix[row][i] = 0;
}

void setZeroCol(vector<vector<int>> &matrix, int col, int m){
for(int i = 0; i < m; i++)
matrix[i][col] = 0;
}
};

LeetCode 382. 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.

Follow up: What if the linked list is extremely large and its length is unknown to you? Could you solve this efficiently without using extra space?

Example:

1
2
3
4
5
6
7
8
// Init a singly linked list [1,2,3].
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
Solution solution = new Solution(head);

// getRandom() should return either 1, 2, or 3 randomly. Each element >should have equal probability of returning.
solution.getRandom();

要求设计一个类, 根据一个未知长度的单链表进行构造, 每次调用getRandom成员函数时返回一个随机节点.

O(n)空间复杂度, O(1)时间复杂度的方法就是用一个顺序容器保存所有节点的指针, 然后每次调用getRandom都根据下标来访问.

1
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 {
vector<ListNode*> nodes;
public:
/** @param head The linked list's head. Note that the head is guanranteed to be not null, so it contains at least one node. */
Solution(ListNode* head) {
srand(time(NULL));
ListNode *p = head;
while(p){
nodes.push_back(p);
p = p->next;
}
}

/** Returns a random node's value. */
int getRandom() {
int r = rand() % nodes.size();
return nodes[r]->val;
}
};

/**
* Your Solution object will be instantiated and called as such:
* Solution obj = new Solution(head);
* int param_1 = obj.getRandom();
*/

对于题目中的Follow up部分, 要使用O(1)空间复杂度, 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 {
ListNode *head, *mp;
int listLen;
public:
/** @param head The linked list's head. Note that the head is guanranteed to be not null, so it contains at least one node. */
Solution(ListNode* h) {
srand(time(NULL));
this->head = h;
this->mp = this->head;
listLen = 0;
ListNode *p = h;
while(p){
listLen++;
p = p->next;
}
}

/** Returns a random node's value. */
int getRandom() {
int r = rand() % listLen;
for(int i = 0; i < r; i++){
mp = mp->next;
if(mp == nullptr) mp = head;
}
return mp->val;
}
};

/**
* Your Solution object will be instantiated and called as such:
* Solution obj = new Solution(head);
* int param_1 = obj.getRandom();
*/

LeetCode 72. Edit Distance

题目描述:

Given two words word1 and word2, find the minimum number of steps required to convert word1 to word2. (each operation is counted as 1 step.)

You have the following 3 operations permitted on a word:

a) Insert a character b) Delete a character c) Replace a character

题目要求计算两个单词(word1, word2)的"距离", 就是说最少经过多少步可以从word1变幻到word2. 可以使用的操作有三种:

  1. 插入一个字符
  2. 删除一个字符
  3. 替换一个字符

这道题目使用动态规划来解决.

数组dp[i][j]表示word1[0:i-1](包括i-1)与word2[0:j-1]之间的距离.

根据最后一次操作的类型分为三类:

  1. word1插入字符, 此时dp[i][j] = dp[i][j - 1] + 1, 因为这是在word1[0:i-1]=>word2[0:j-2]需要dp[i][j - 1]的基础上, word2最后又增加了一个字符, 因此word1[0:i-1]=>word2[0:j-2]之后再增加一步插入字符的操作(也可以看作word2=>word1最后多删除了一个字符)
  2. word2插入字符, 此时dp[i][j] = dp[i - 1][j] + 1, 因为这是在word2[0:j-1]=>word1[0:i-2]需要dp[i - 1][j]的基础上, word1最后又增加了一个字符, 因此word2[0:j-1]=>word1[0:i-2]之后再增加一步插入字符的操作(也可以看作word1=>word2最后多删除了一个字符)
  3. 替换操作, 如果word1[i - 1] == word2[j - 1], 那么dp[i][j] = dp[i - 1][j - 1], 否则dp[i][j] = dp[i - 1][j - 1] + 1

因此递推方程为

dp[i][j] = min(dp[i-1][j-1] + (word1[i-1] == word2[j-1] ? 0 : 1), dp[i][j - 1] + 1, dp[i - 1][j] + 1)

初始条件为dp[i][0] = i; dp[0][j] = j;(全部为插入操作)

class Solution {
public:
    int minDistance(string word1, string word2) {
        int len1 = word1.length(), len2 = word2.length();
        int arr[len1 + 1][len2 + 1];
        arr[0][0] = 0;
        for(int i = 1; i <= len2; i++){
            arr[0][i] = i;
        }
        for(int i = 1; i <= len1; i++){
            arr[i][0] = i;
        }
        for(int i = 1; i <= len1; i++){
            for(int j = 1; j <= len2; j++){
                int tmp1 = arr[i - 1][j] + 1, tmp2 = arr[i][j - 1] + 1, tmp3;
                if(word1[i - 1] == word2[j - 1]) tmp3 = arr[i - 1][j - 1];
                else tmp3 = arr[i - 1][j - 1] + 1;
                arr[i][j] = min(tmp1, min(tmp2, tmp3));
            }
        }
        return arr[len1][len2];
    }
};

LeetCode 71. Simplify Path

题目描述:

Given an absolute path for a file (Unix-style), simplify it. For example, path = "/home/", => "/home" path = "/a/./b/../../c/", => "/c" Corner Cases:

  • Did you consider the case where path = "/../"? In this case, you should return "/".
  • Another corner case is the path might contain multiple slashes '/' together, such as "/home//foo/". In this case, you should ignore redundant slashes and return "/home/foo".

题目要求简化一个路径, 要做到两点:

  1. 去掉无效的/, 比如连续的/和结尾的/
  2. ...这种相对路径转换为绝对路径

先使用栈保存/分割的每个节点, 在处理过程中分为三种情况:

  1. 节点为.: 不做任何处理
  2. 节点为..: 若栈不为空则弹出栈顶元素
  3. 否则将节点入栈

最后把栈变为字符串输出.

class Solution {
public:
    string simplifyPath(string path) {
        vector<string> pathStack = splitPath(path);
        return getFinPath(pathStack);
    }

    string getFinPath(vector<string> &finStack){
        string re = "";
        for(int i = 0 ; i < finStack.size(); i++){
            re += "/";
            re += finStack[i];
        }
        if(re == "")
            re = "/";
        return re;
    }

    vector<string> splitPath(string path){
        vector<string> re;
        for(int i = 0; i < path.size(); i++){
            if(path[i] != '/'){
                int end = getEnd(path, i);
                string node = string(path.begin() + i, path.begin() + end);
                if(node == string(".")){
                    // do nothing
                }
                else if(node == string("..")){
                    if(!re.empty()) re.pop_back();
                }
                else{
                    re.push_back(node);
                }
                i = end;
            }
        }
        return re;
    }

    int getEnd(string path, int start){
        int i = start;
        for(; i < path.size() && path[i] != '/'; i++);
        return i;
    }
};

LeetCode 70. 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?

斐波那契数列, 到达第n层的路径数量等于到达第n - 1层的路径数量加到达第n - 2层的路径数量.

class Solution {
public:
    int climbStairs(int n) {
        if(n == 1)return 1;
        if(n == 2)return 2;
        int a = 1, b = 2, t;
        for(int i = 2; i < n; i++){
            t = b;
            b = a + b;
            a = t;
        }
        return b;
    }
};