目录
2019.12.04
4. Median of Two Sorted Arrays (两个有序数组的中位数)
2019.12.05
10. Regular Expression Matching(只需要.和*的正则匹配)
模拟面试
1. 博弈论水题
2.水题
2019.12.06
23. Merge k Sorted Lists(合并k个有序链表)
模拟面试
1. 随便求一个乱序数组的峰
2. 判断长度小于10000的字符串是否可以由自身子串重复组成
2019.12.08
25. Reverse Nodes in k-Group(反转链表加强版)
30. Substring with Concatenation of All Words(子串查找)
32. Longest Valid Parentheses(括号表达式匹配)
2019.12.09
37. Sudoku Solver
41. First Missing Positive(微软3面原题,查找最小的缺失正数)
模拟面试
1. 判断一个字符串是另一个字符串的子集
2. n个f面骰子,求sum=target的个数
2019.12.10
42. Trapping Rain Water
2019.12.12
44. Wildcard Matching
45. Jump Game II
2019.12.13
51. N-Queens
52. N-Queens II
2019.12.16
57. Insert Interval(区间模拟)
65. Valid Number(判断字符串是数字)
68. Text Justification(字符串模拟)
72. Edit Distance(编辑距离,微软1面原题)
76. Minimum Window Substring(O(n)字符串尺取)
2019.12.17
(待优化)84. Largest Rectangle in Histogram(直方图的矩形面积最大值)
2019.12.18
(待优化)85. Maximal Rectangle
2019.12.19
87. Scramble String
2019.12.23
97. Interleaving String
2019.12.24
99. Recover Binary Search Tree
115. Distinct Subsequences
2019.12.26
123. Best Time to Buy and Sell Stock III
124. Binary Tree Maximum Path Sum
打卡背景:
2019年11月底准备离开头条跳槽微软的时候,微软的三面居然给我出了道后缀数组。如果是三年前的我肯定没有问题,可惜我早就忘光了,现场一个字没写出来。虽然没能影响到最后的面试结果,可我后来发现居然是道LeetCode Hard原题。本来我是丝毫瞧不起LeetCode的,这下子被狠狠的打脸了。明年校招在即,重启博客,一周五道Hard+五道经典面试题,为来年校招做准备。
https://leetcode.com/problemset/all/?difficulty=Hard
https://leetcode.com/problems/median-of-two-sorted-arrays/
题目意思很简单,这里不再赘述,就是给2个有序数组,求中位数。
我们不求中位数,我们直接求第k大。设第一个数组长度为m,第二个数组长度为n,则当m+n为奇数,就是求第(m+n)/2+1大;当m+n为偶数,就是求第(m+n)/2+1大和第(m+n)/2大的平均数。这种数据我向来不喜欢证,而是喜欢mock数据。比如我要求的显然是(m+n)/2本身或者附近的一个数字,假设m=1,n=2,这里便是求第2大,而(m+n)/2=1,所以是求第(m+n)/2+1大。偶数同理。
思路也很简单,我们分别取上下2个数组的第k/2来比较。如果相等,则是我要的结果。如果一个小一个大,那么小的数字以及比他更小的数字,都不会是我要的结果,直接舍去他本身及他之前的结果即可。每次若舍去t个数字,我下一次递归则是求第k-t大。
显然是log级的时间复杂度。
边界条件:如果其中一个数组在递归过程中被我全部丢掉了,那么这2个数组的第k大,也就是另外一个数组的第k大,直接a[k-1]即可。不然,这2个数组都是有元素的。如果k=1,答案也就是2个数组第一个元素作比较。更大的边界看起来是没有必要的了。
特殊情况:在上述递归过程中,假设第一个数组是[l1, r1), 第二个数组是[l2, r2),(本人向来喜欢前闭后开区间,有美感)如果k=2,那我比较的应该是a[l1]和b[l2],舍去最小的那一个,第一个数字是l1+0,第二个数字目测是l2+(k-0-t),mock法显然t=2。当k=3,那第一个数字就是l1+1。因此,由k=2,3 -> 0, 1得出,第一个数字是l1+(k-1)/2。特别的,若l1+(k-1)/2>=r1,或者l2+(k-(k-1)/2-2)>=r2,需要直接取r1-1或者r2-1,且上下的和等于l1+l2+k-2,不难求出结果。
Runtime: 16 ms, faster than 90.69% of C++ online submissions for Median of Two Sorted Arrays.
Memory Usage: 9.9 MB, less than 44.33% of C++ online submissions for Median of Two Sorted Arrays.
class Solution {
public:
double findMedianSortedArrays(vector& nums1, vector& nums2) {
int m = nums1.size();
int n = nums2.size();
if ((m + n) % 2 == 1) {
return findkth(nums1, nums2, 0, m, 0, n, (m + n) / 2 + 1);
} else {
return (findkth(nums1, nums2, 0, m, 0, n, (m + n) / 2) + findkth(nums1, nums2, 0, m, 0, n, (m + n) / 2 + 1)) / 2.0;
}
}
double findkth(vector& nums1, vector& nums2, int l1, int r1, int l2, int r2, int k) {
int m = r1 - l1;
int n = r2 - l2;
if (m <= 0) {
return nums2[l2 + k - 1];
}
if (n <= 0) {
return nums1[l1 + k - 1];
}
if (k == 1) {
return min(nums1[l1], nums2[l2]);
}
int p = (k - 1) / 2, q1, q2;
if (l1 + p >= r1) {
q1 = r1 - 1;
q2 = l1 + l2 + k - q1 - 2;
} else if (l2 + k - p - 2 >= r2) {
q2 = r2 - 1;
q1 = l1 + l2 + k - q2 - 2;
} else {
q1 = l1 + p;
q2 = l2 + k - p - 2;
}
if (nums1[q1] == nums2[q2]) {
return nums1[q1];
} else if (nums1[q1] < nums2[q2]) {
return findkth(nums1, nums2, q1 + 1, r1, l2, r2, k - (q1 + 1 - l1));
} else {
return findkth(nums1, nums2, l1, r1, q2 + 1, r2, k - (q2 + 1 - l2));
}
}
};
https://leetcode.com/problems/regular-expression-matching/
一行过(误
class Solution(object):
def isMatch(self, s, p):
return re.match('^' + p + '$', s)
尝试递归求解,但可能会有tle或者爆栈了危险,也很暴力,但这其实已经是类似dp的思路的。
如果当前p开头是a,且a后面不是*,这时若s开头不是a,则返回false
如果当前p开头是a,且a后面不是*,这时若s开头是a,则递归求解s[1:],p[1:]即可
如果当前p开头是a,且a后面是*,这时若s开头不是a,则递归求解s[1:], p[2:]
如果当前p开头是a,且a后面是*,这时若s开头是a,则可以去递归求解s[1:], p[:],也可以求s[:],p[2:],这两者有一个true即可
如果当前p开头是.,且.后面不是*,则可以去递归求解s[1:], p[1:]
如果当前p开头是.,且.后面是*,则可以去递归求解s[1:], p[:],也可以求s[:],p[2:],这两者有一个true即可
边界:
若s\p均空,则返回true
若s空p非空,除非p是x*,否则返回false。如果真的p是x*,返回s[:],p[2:]。这里还涉及到p=1的情况,那就是false。
若s非空p空,则返回false
若s\p均非空,这时候只需要考虑p=1的情况即可。以上情况,我们对于s只讨论开头即可,而p需要讨论到前2个字符。因此这里的边界是p=1的情况。若p开头是.,返回s[1:],p[1:];若p开头是a,且s[0] == p[0],返回s[1:],p[1:];若p开头是a,且s[0] != p[0],返回false。
Runtime: 32 ms, faster than 29.46% of C++ online submissions for Regular Expression Matching.
Memory Usage: 8.3 MB, less than 98.31% of C++ online submissions for Regular Expression Matching.
class Solution {
public:
bool isMatch(string s, string p) {
int m = s.length();
int n = p.length();
return solve(s, p, 0, m, 0, n);
}
bool solve(string& s, string& p, int ls, int rs, int lp, int rp) {
int m = rs - ls;
int n = rp - lp;
if (m == 0 && n == 0) return true;
if (m == 0 && n != 0) {
if (n == 1) return false;
if (p[lp + 1] == '*') return solve(s, p, ls, rs, lp + 2, rp);
else return false;
}
if (m != 0 && n == 0) return false;
if (n == 1) {
if (p[lp] == '.' || p[lp] == s[ls]) return solve(s, p, ls + 1, rs, lp + 1, rp);
else return false;
}
if (p[lp] == '.' && p[lp + 1] != '*') {
return solve(s, p, ls + 1, rs, lp + 1, rp);
}
if (p[lp] == '.' && p[lp + 1] == '*') {
return solve(s, p, ls + 1, rs, lp, rp) || solve(s, p, ls, rs, lp + 2, rp);
}
if (p[lp] != '.' && p[lp + 1] == '*' && s[ls] == p[lp]) {
return solve(s, p, ls + 1, rs, lp, rp) || solve(s, p, ls, rs, lp + 2, rp);
}
if (p[lp] != '.' && p[lp + 1] == '*' && s[ls] != p[lp]) {
return solve(s, p, ls, rs, lp + 2, rp);
}
if (p[lp] != '.' && p[lp + 1] != '*' && s[ls] == p[lp]) {
return solve(s, p, ls + 1, rs, lp + 1, rp);
}
if (p[lp] != '.' && p[lp + 1] != '*' && s[ls] != p[lp]) {
return false;
}
return false; // cpp没有这行编译过不去
}
};
应@狗导 要求,这题再加上记忆化dp,用空间换时间,虽然复杂度是一毛一样的
Runtime: 4 ms, faster than 93.10% of C++ online submissions for Regular Expression Matching.
Memory Usage: 9.3 MB, less than 28.81% of C++ online submissions for Regular Expression Matching.
class Solution {
public:
bool isMatch(string s, string p) {
int m = s.length();
int n = p.length();
vector> dp(m+1, vector(n+1, -1));
return solve(dp, s, p, 0, m, 0, n);
}
bool solve(vector>& dp, string& s, string& p, int ls, int rs, int lp, int rp) {
if (dp[ls][lp] != -1) return dp[ls][lp];
int m = rs - ls;
int n = rp - lp;
if (m == 0 && n == 0) return dp[ls][lp] = true;
if (m == 0 && n != 0) {
if (n == 1) return dp[ls][lp] = false;
if (p[lp + 1] == '*') return dp[ls][lp] = solve(dp, s, p, ls, rs, lp + 2, rp);
else return dp[ls][lp] = false;
}
if (m != 0 && n == 0) return dp[ls][lp] = false;
if (n == 1) {
if (p[lp] == '.' || p[lp] == s[ls]) return dp[ls][lp] = solve(dp, s, p, ls + 1, rs, lp + 1, rp);
else return dp[ls][lp] = false;
}
if (p[lp] == '.' && p[lp + 1] != '*') {
return dp[ls][lp] = solve(dp, s, p, ls + 1, rs, lp + 1, rp);
}
if (p[lp] == '.' && p[lp + 1] == '*') {
return dp[ls][lp] = (solve(dp, s, p, ls + 1, rs, lp, rp) || solve(dp, s, p, ls, rs, lp + 2, rp));
}
if (p[lp] != '.' && p[lp + 1] == '*' && s[ls] == p[lp]) {
return dp[ls][lp] = (solve(dp, s, p, ls + 1, rs, lp, rp) || solve(dp, s, p, ls, rs, lp + 2, rp));
}
if (p[lp] != '.' && p[lp + 1] == '*' && s[ls] != p[lp]) {
return dp[ls][lp] = solve(dp, s, p, ls, rs, lp + 2, rp);
}
if (p[lp] != '.' && p[lp + 1] != '*' && s[ls] == p[lp]) {
return dp[ls][lp] = solve(dp, s, p, ls + 1, rs, lp + 1, rp);
}
if (p[lp] != '.' && p[lp + 1] != '*' && s[ls] != p[lp]) {
return dp[ls][lp] = false;
}
return dp[ls][lp] = false; // cpp没有这行编译过不去
}
};
You are playing the following Nim Game with your friend: There is a heap of stones on the table, each time one of you take turns to remove 1 to 3 stones. The one who removes the last stone will be the winner. You will take the first turn to remove the stones.
Both of you are very clever and have optimal strategies for the game. Write a function to determine whether you can win the game given the number of stones in the heap.
Example:
Input: 4
Output: false
Explanation: If there are 4 stones in the heap, then you will never win the game;
No matter 1, 2, or 3 stones you remove, the last stone will always be
removed by your friend.
class Solution {
public:
bool canWinNim(int n) {
/*
1 2 3必胜态
4无论怎么做都是对手的必胜态,所以是必败态
5 6 7可以变成4,也就是对手必败态,所以是先手的必胜态。
8必败态。
因此n%4==0必败
//*/
return n % 4 != 0;
}
};
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:
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.
class Solution {
public:
int hammingDistance(int x, int y) {
int z = x ^ y;
int ans = 0;
while (z) {
ans += z & 1;
z >>= 1;
}
return ans;
}
};
https://leetcode.com/problems/merge-k-sorted-lists/
这题如果能用额外空间,就不再赘述了,拿数组O(n)或者最小堆O(k)随便做。通常面试都不允许使用超过o(1)的额外空间,这里我们原地操作。
我不合并k个,我们合并2个有序链表,然后做k-1次就行。很常规的做法,具体细节不再赘述。
Runtime: 164 ms, faster than 23.70% of C++ online submissions for Merge k Sorted Lists.
Memory Usage: 10.8 MB, less than 90.48% of C++ online submissions for Merge k Sorted Lists.
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* mergeKLists(vector& lists) {
int len = lists.size();
if (len == 0) return NULL;
ListNode* ans = lists[0];
for (int i = 1; i < len; i++) {
ans = merge(ans, lists[i]);
}
return ans;
}
ListNode* merge(ListNode* a, ListNode* b) {
if (a == NULL) return b;
if (b == NULL) return a;
ListNode *p;
if (a->val < b->val) {
p = a;
a = a->next;
} else {
p = b;
b = b->next;
}
ListNode *ans = p;
while (a && b) {
if (a->val < b->val) {
p->next = a;
p = a;
a = a->next;
} else {
p->next = b;
p = b;
b = b->next;
}
}
if (a == NULL) p->next = b;
if (b == NULL) p->next = a;
return ans;
}
};
当然,也可以维护最小堆。本人更喜欢set/multiset,所以没用priority_queue。空间为O(k)。
Runtime: 20 ms, faster than 98.80% of C++ online submissions for Merge k Sorted Lists.
Memory Usage: 12.3 MB, less than 5.95% of C++ online submissions for Merge k Sorted Lists.
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
struct Node{
int val;
int category;
Node(int val, int category): val(val), category(category) {}
bool operator < (const Node& b) const {
return this->val < b.val;
}
};
ListNode* mergeKLists(vector& lists) {
int len = lists.size();
ListNode *ans = NULL, *p = NULL;
multiset s;
for (int i = 0; i < len; i++) {
if (lists[i]) {
s.insert(Node(lists[i]->val, i));
}
}
while (!s.empty()) {
set::iterator it = s.begin();
if (ans == NULL) {
ans = lists[it->category];
lists[it->category] = lists[it->category]->next;
if (lists[it->category]) {
s.insert(Node(lists[it->category]->val, it->category));
}
p = ans;
} else {
p->next = lists[it->category];
p = p->next;
lists[it->category] = lists[it->category]->next;
if (lists[it->category]) {
s.insert(Node(lists[it->category]->val, it->category));
}
}
s.erase(it);
}
return ans;
}
};
A peak element is an element that is greater than its neighbors.
Given an input array nums
, where nums[i] ≠ nums[i+1]
, find a peak element and return its index.
The array may contain multiple peaks, in that case return the index to any one of the peaks is fine.
You may imagine that nums[-1] = nums[n] = -∞
.
Example 1:
Input: nums = [1,2,3,1]
Output: 2
Explanation: 3 is a peak element and your function should return the index number 2.
Example 2:
Input: nums = [
1,2,1,3,5,6,4]
Output: 1 or 5
Explanation: Your function can return either index number 1 where the peak element is 2,
or index number 5 where the peak element is 6.
class Solution {
public:
int findPeakElement(vector& nums) {
return solve(nums, 0, nums.size(), nums.size());
}
int solve(vector& nums, int l, int r, int n) {
if (l == r - 1) return l;
int m = (l + r) / 2;
if (m == 0) {
return (nums[0] < nums[1]) ? 1 : 0;
}
if (m == n - 1) {
return (nums[m - 1] < nums[m]) ? m : m - 1;
}
if (nums[m] > nums[m-1] && nums[m] > nums[m+1]) {
return m;
}
if (nums[m] < nums[m-1] && nums[m] > nums[m+1]) {
return solve(nums, l, m, n);
}
if (nums[m] > nums[m-1] && nums[m] < nums[m+1]) {
return solve(nums, m, r, n);
}
return solve(nums, l, m, n);
}
};
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:
Input: "abab"
Output: True
Explanation: It's the substring "ab" twice.
Example 2:
Input: "aba"
Output: False
Example 3:
Input: "abcabcabcabc"
Output: True
Explanation: It's the substring "abc" four times. (And the substring "abcabc" twice.)
class Solution {
public:
bool repeatedSubstringPattern(string s) {
int len = s.length();
for (int ans = 1; ans < len; ans++) {
if (len % ans != 0) continue;
int p = len / ans;
int ok = true;
for (int i = 1; i < p; i++) {
for (int j = 0; j < ans; j++) {
if (s[j] != s[j+i*ans]) {
ok = false;
break;
}
}
if (!ok) break;
}
if (ok) return true;
}
return false;
}
};
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:
题解:反转链表加强版。我们先写一个根据头尾单次反转链表的函数,然后重复n/k次即可。为了方便,我们加一个假的头结点。针对单链表向后的特性这里的函数取的是前开后闭区间。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
ListNode* tmphead = new ListNode(0);
tmphead->next = head;
ListNode *p = tmphead, *q = tmphead->next;
int cnt = k - 1;
while(q) {
if (cnt == 0) {
p = reverse(p, q);
q = p->next;
cnt = k - 1;
} else {
cnt--;
q=q->next;
}
}
return tmphead->next;
}
ListNode* reverse(ListNode* p, ListNode* q) {
if (p->next == q) return q;
ListNode* res = p->next;
ListNode *s = res, *t = res->next;
while(t != q) {
ListNode* tmp = t->next;
t->next = s;
s = t;
t = tmp;
}
res->next = t->next;
t->next = s;
p->next = q;
return res;
}
};
You are given a string, s, and a list of words, words, that are all of the same length. Find all starting indices of substring(s) in s that is a concatenation of each word in words exactly once and without any intervening characters.
Example 1:
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.
题解:第一思路是将每个字符串出现的所有位置求出来,这样我们就得到了k个int数组,判断这k个数组能否前后相连即可。这里可以用kmp加快一下,我用的朴素匹配。注意,由于没有给数据范围,有超时的风险,虽然最后莽了一发没有tle就没有管。
坑:这里有2个坑需要注意,首先words里面的字符串是可以重复的,坑爹。。wa了第一发,于是将words压一个字典并修改逻辑;第二个坑,s可以是空字符串。这题由于words里面数组是等长的,因此不会出现一个字符串是另外一个字符串的前缀。
Runtime: 80 ms, faster than 66.81% of C++ online submissions for Substring with Concatenation of All Words.
Memory Usage: 16.2 MB, less than 73.91% of C++ online submissions for Substring with Concatenation of All Words.
class Solution {
public:
vector findSubstring(string s, vector& words) {
int oldsize = words.size();
map words2int;
for (int i = 0; i < oldsize; i++) words2int[words[i]]++;
words.clear();
for (map::iterator it = words2int.begin(); it != words2int.end(); it++) {
words.push_back(it->first);
}
vector a;
for (int i = 0; i < s.length(); i++) {
a.push_back(-1);
}
for (int i = 0; i < words.size(); i++) {
vector t = match(s, words[i]);
for (int j = 0; j < t.size(); j++) {
a[t[j]] = i;
}
}
vector ans;
if (oldsize) {
int len = words[0].length();
int sublen = oldsize*len;
if (sublen > s.length()) return ans;
map m;
bool ok;
for (int i = 0; i < s.length() - sublen + 1; i++) {
m.clear();
ok = true;
for (int j = 0; ok && j < oldsize; j++) {
if (a[i+j*len] == -1) ok = false;
else m[a[i+j*len]]++;
}
for (int j = 0; ok && j < words.size(); j++) {
if (m[j] < words2int[words[j]]) ok = false;
}
if (ok) ans.push_back(i);
}
}
return ans;
}
vector match(string& s, string& t) {
vector ret;
int ls = s.length(), lt = t.length();
bool ok;
for (int i = 0, j; i < ls - lt + 1; i++) {
bool ok = true;
for (j = 0; j < lt; j++) {
if (s[i+j] != t[j]) {
ok = false;
break;
}
}
if (ok) ret.push_back(i);
}
return ret;
}
};
Given a string containing just the characters '('
and ')'
, find the length of the longest valid (well-formed) parentheses substring.
题解:没有告诉数据范围,莽一发极限剪枝过了,具体方法见注释。但还是只打败了5%的时间,说明有更好的方法。
Runtime: 504 ms, faster than 5.03% of C++ online submissions for Longest Valid Parentheses.
Memory Usage: 9.5 MB, less than 82.14% of C++ online submissions for Longest Valid Parentheses.
//剪枝:题目理解为(=1,)=-1,求sum=0的最长子区间,且这个区间的任一前缀和pre>=0。
class Solution {
public:
int longestValidParentheses(string s) {
int len = s.length();
int* pre = new int[len+1];
pre[0] = 0;
for (int i = 1; i <= len; i++) {
pre[i] = pre[i-1] + ((s[i-1] == '(') ? 1 : -1);
}
int ans, i;
for (ans = len; ans > 0; ans--) {
if (ans & 1) continue;
for (i = 1; i + ans <= len + 1; i++) {
if (valid(pre, i, i + ans)) return ans;
}
}
return ans;
}
bool valid(int *pre, int l, int r) {
if (pre[r-1] - pre[l-1] != 0) return false;
for (int i = l; i < r - 1; i++) {
if (pre[i] - pre[l-1] < 0) return false;
}
return true;
}
};
换个角度想,采用动态规划的思想。假设dp[i]是以第i结尾的最长合法子串长度,则答案是maxdp。比较麻烦的是思考转移方程。如果s[i]=(,那么dp=0。如果s[i]=),且s[i-1]=(,则dp[i]=dp[i-2]+2。如果s[i]=),且s[i-1]=),则此时如果dp[i-1]=0,说明i-1的)已经溢出了,再加)也不会抵消,于是dp[i]=0;如果dp[i-1]!=0,说明以i-1结尾,且开头是i-1-dp[i-1]+1的字符串是合法子串,这时候如果开头之前的字符(注意越界)s[i-1-dp[i-1]+1-1]=(,则dp[i]=dp[i-1]+dp[i-1-dp[i-1]]+2,否则dp[i]=0。最后,初值只需设置dp[1]=0即可。
Runtime: 8 ms, faster than 60.53% of C++ online submissions for Longest Valid Parentheses.
Memory Usage: 9.6 MB, less than 67.86% of C++ online submissions for Longest Valid Parentheses.
class Solution {
public:
int longestValidParentheses(string s) {
int len = s.length();
if (len < 2) return 0;
int *dp = new int[len+1];
dp[0] = 0, dp[1] = 0;
int ans = 0;
for (int i = 2; i <= len; i++) {
if (s[i-1] == '(') dp[i] = 0;
else if (s[i-1] == ')' && s[i-2] == '(') dp[i] = dp[i-2] + 2;
else if (s[i-1] == ')' && s[i-2] == ')') {
if (dp[i-1] == 0) dp[i] = 0;
else if (i-dp[i-1]-2 < 0) dp[i] = 0;
else if (s[i-dp[i-1]-2] == ')') {
dp[i] = 0;
} else {
dp[i] = dp[i-dp[i-1]-2] + 2 + dp[i-1];
}
}
ans = max(ans, dp[i]);
}
return ans;
}
};
Write a program to solve a Sudoku puzzle by filling the empty cells.
A sudoku solution must satisfy all of the following rules:
1-9
must occur exactly once in each row.1-9
must occur exactly once in each column.1-9
must occur exactly once in each of the 9 3x3
sub-boxes of the grid.Empty cells are indicated by the character '.'
.
想了很久,没有想出很好的解法,于是暴力,没想到直接4ms过了。。。
Runtime: 4 ms, faster than 97.58% of C++ online submissions for Sudoku Solver.
Memory Usage: 9 MB, less than 44.83% of C++ online submissions for Sudoku Solver.
class Solution {
public:
void solveSudoku(vector>& board) {
bool row[9][9] = {false};
bool col[9][9] = {false};
bool seq[9][9] = {false};
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] != '.') {
int s = (i / 3) * 3 + j / 3;
int k = board[i][j] - '0' - 1;
row[i][k] = true;
col[j][k] = true;
seq[s][k] = true;
}
}
}
dfs(board, row, col, seq, 0, 0);
}
bool dfs(vector>& board, bool row[][9], bool col[][9], bool seq[][9], int r, int c) {
if (r == 9) return true;
int k = (board[r][c] == '.') ? -1 : board[r][c] - '0' - 1;
int s = (r / 3) * 3 + c / 3;
if (k == -1) {
for (int k = 0; k < 9; k++) {
if (!row[r][k] && !col[c][k] && !seq[s][k]) {
row[r][k] = true;
col[c][k] = true;
seq[s][k] = true;
board[r][c] = k + '0' + 1;
if (dfs(board, row, col, seq, r + (c == 8), (c + 1) % 9)) return true;
row[r][k] = false;
col[c][k] = false;
seq[s][k] = false;
board[r][c] = '.';
}
}
}
else {
if (dfs(board, row, col, seq, r + (c == 8), (c + 1) % 9)) return true;
}
return false;
}
};
Given an unsorted integer array, find the smallest missing positive integer.
Example 1:
Input: [1,2,0]
Output: 3
Example 2:
Input: [3,4,-1,1]
Output: 2
原来微软3面问的是原题。。。必须用O(n)来做,这题我们根据下标直接定位即可。
Runtime: 4 ms, faster than 65.46% of C++ online submissions for First Missing Positive.
Memory Usage: 8.6 MB, less than 90.00% of C++ online submissions for First Missing Positive.
class Solution {
public:
int firstMissingPositive(vector& nums) {
int len = nums.size();
for (int i = 0; i < len; i++) {
while (nums[i] > 0 && nums[i] <= len && nums[i] != i+1 && nums[i] != nums[nums[i]-1]) swap(nums[i], nums[nums[i]-1]);
}
for (int i = 0; i < len; i++) {
if (nums[i] != i+1) return i+1;
}
return len + 1;
}
};
You are given an array of strings words
and a string chars
.
A string is good if it can be formed by characters from chars
(each character can only be used once).
Return the sum of lengths of all good strings in words
.
Example 1:
Input: words = ["cat","bt","hat","tree"], chars = "atach"
Output: 6
Explanation:
The strings that can be formed are "cat" and "hat" so the answer is 3 + 3 = 6.
class Solution {
public:
int countCharacters(vector& words, string chars) {
int ans = 0;
map m1, m2;
for (int i = 0; i < chars.length(); i++) m1[chars[i]]++;
for (int i = 0; i < words.size(); i++) {
m2.clear();
for (int j = 0; j < words[i].length(); j++) m2[words[i][j]]++;
bool ok = true;
for (map::iterator it = m2.begin(); it != m2.end(); it++) {
if (it->second > m1[it->first]) {
ok = false;
break;
}
}
if (ok) ans += words[i].length();
}
return ans;
}
};
You have d
dice, and each die has f
faces numbered 1, 2, ..., f
.
Return the number of possible ways (out of fd
total ways) modulo 10^9 + 7
to roll the dice so the sum of the face up numbers equals target
.
Example 1:
Input: d = 1, f = 6, target = 3
Output: 1
Explanation:
You throw one die with 6 faces. There is only one way to get a sum of 3.
Example 2:
Input: d = 2, f = 6, target = 7
Output: 6
Explanation:
You throw two dice, each with 6 faces. There are 6 ways to get a sum of 7:
1+6, 2+5, 3+4, 4+3, 5+2, 6+1.
class Solution {
public:
const int MOD = 1e9+7;
int numRollsToTarget(int d, int f, int target) {
if (target > d * f) return 0;
vector> dp(d + 1, vector(target + 1, 0));
for (int j = 1; j <= f && j <= target; j++) {
dp[1][j] = 1;
}
for (int i = 2; i <= d; i++) {
for (int j = 2; j <= target; j++) {
for (int k = 1; j - k > 0 && k <= f; k++) {
dp[i][j] = (dp[i][j] + dp[i-1][j-k]) % MOD;
}
}
}
return dp[d][target];
}
};
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
题解:直接暴力模拟的复杂度是O(n),所以直接上即可。每一个点的出水量如果能储水,显然等于左侧的最大值与右侧的最大值的最小值做差
class Solution {
public:
int trap(vector& height) {
int n = height.size();
if (!n) return 0;
vector maxleft(n, 0);
vector maxright(n, 0);
for (int i = 1; i <= n-1; i++) {
maxleft[i] = max(maxleft[i-1], height[i-1]);
}
for (int i = n-2; i >= 0; i--) {
maxright[i] = max(maxright[i+1], height[i+1]);
}
int ans = 0, h;
for (int i = 1; i < n-1; i++) {
h = min(maxleft[i], maxright[i]);
ans += h > height[i] ? h - height[i] : 0;
}
return ans;
}
};
Given an input string (s
) and a pattern (p
), implement wildcard pattern matching with support for '?'
and '*'
.
'?' Matches any single character.
'*' Matches any sequence of characters (including the empty sequence).
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 *
.这题和之前的10.Regular Expression Matching乍看一样,但其实只是相似。10的.和这题的?是一模一样的,而10的*代表的是重复前一个字符0-n次,这里的*代表的是插入任意长度的字符串。但可以用同样的思路求解。
如果s开头是a,而p开头是a,说明匹配,继续看s[1:]和p[:1]即可
如果s开头是a,而p开头a以外的字符串,说明不匹配,不需要继续看
如果s开头是a,而p开头是?,说明匹配,继续看s[1:]和p[:1]即可
如果s开头是a,而p开头是*,说明匹配,且可以继续看s[0:]和p[:1]/s[1:]和p[:1]/s[2:]和p[:1]……
边界条件:s空p空,true。s非空p空,true。s空p非空,除非p="***",否则为false
当然,这很可能超时,上述最后一步*的时候,会重复计算,1614 / 1809 test cases passed
另外一种超时的可能是如a*******b这种*重复出现,去了个重,1708 / 1809 test cases passed.
没得办法,只能上大招,记忆化搜索,加一个初始化为-1的dp数组标记答案。
Runtime: 40 ms, faster than 61.40% of C++ online submissions for Wildcard Matching.
Memory Usage: 27.7 MB, less than 11.54% of C++ online submissions for Wildcard Matching.
class Solution {
public:
bool isMatch(string s, string p) {
int lens = s.length();
int lenp = p.length();
string pp = "";
for (int i = 0; i < lenp; i++) {
if (p[i] == '*') {
if (pp.length() == 0 || pp[pp.length() - 1] != '*') {
pp += p[i];
}
} else {
pp += p[i];
}
}
lenp = pp.length();
vector> dp(lens+1, vector(lenp+1, -1));
return match(s, pp, 0, lens, 0, lenp, dp) == 1;
}
int match(string& s, string& p, int ls, int rs, int lp, int rp, vector>& dp) {
if (dp[ls][lp] != -1) return dp[ls][lp];
int lens = rs - ls;
int lenp = rp - lp;
if (lens == 0 && lenp == 0) {
dp[ls][lp] = 1;
}
else if (lenp == 0 && lens != 0) {
dp[ls][lp] = 0;
}
else if (lens == 0 && lenp != 0) {
for (int i = lp; i < rp; i++) {
if (p[i] != '*') {
dp[ls][lp] = 0;
return dp[ls][lp];
}
}
dp[ls][lp] = 1;
} else {
if (p[lp] == '*') {
for (int i = ls; i <= rs; i++) {
if (match(s, p, i, rs, lp + 1, rp, dp)) {
dp[ls][lp] = 1;
return dp[ls][lp];
}
}
dp[ls][lp] = 0;
} else if (p[lp] == '?') {
dp[ls][lp] = match(s, p, ls + 1, rs, lp + 1, rp, dp);
} else {
if (s[ls] == p[lp]) dp[ls][lp] = match(s, p, ls + 1, rs, lp + 1, rp, dp);
else dp[ls][lp] = 0;
}
}
return dp[ls][lp];
}
};
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.
首先,不可能往左跳,因为这相当于上一步少跳一步,往左完全是多余的。因此这是递增的,一直往右跳,就好办多了。当前比如能走3步,那就是右边3步的答案最小值再加一,显而易见的dp。
91/92
class Solution {
public:
int jump(vector& nums) {
int n = nums.size();
vector dp(n, 999999);
dp[n-1] = 0;
for (int i = n - 2; i >= 0; i--) {
for (int j = 1; j <= nums[i] && i + j < n; j++) {
dp[i] = min(dp[i], dp[i + j] + 1);
}
}
return dp[0];
}
};
需要注意,复杂度O(nk),这题有个样例n=25000,直接二维dp会超时,我们另外维护一个单点更新线段树,表示dp[i]..dp[j]里面的最小值。复杂度O(nlogN)
Runtime: 44 ms, faster than 9.25% of C++ online submissions for Jump Game II.
Memory Usage: 15.3 MB, less than 5.00% of C++ online submissions for Jump Game II.
const int MAXN = 1e5 + 5, MAXV = 1e9 + 7;
class Solution {
public:
struct Node{
int value;
int left, right;
}node[MAXN << 2];
int pos[MAXN];
void buildTree(int n) {
build(1, 0, n - 1);
}
void build(int nodenum, int left, int right) {
node[nodenum].left = left;
node[nodenum].right = right;
if (left == right) {
node[nodenum].value = MAXV;
// node[nodenum].value = nums[left];
pos[left] = nodenum;
} else {
build(nodenum << 1, left, (left + right) >> 1);
build(nodenum << 1 | 1, ((left + right) >> 1) + 1, right);
node[nodenum].value = MAXV;
// node[nodenum].value = min(node[nodenum << 1].value, node[nodenum << 1 | 1].value);
}
}
void updateNode(int index, int value) {
node[pos[index]].value = value;
update(pos[index] >> 1, value);
}
void update(int nodenum, int value) {
if (nodenum) {
node[nodenum].value = min(node[nodenum << 1].value, node[nodenum << 1 | 1].value);
update(nodenum >> 1, value);
}
}
int queryTree(int left, int right) {
if (left > right) return MAXV;
return query(1, left, right);
}
int query(int nodenum, int left, int right) {
if (node[nodenum].left == left && node[nodenum].right == right) return node[nodenum].value;
int mid = (node[nodenum].left + node[nodenum].right) >> 1;
if (right <= mid) return query(nodenum << 1, left, right);
if (left > mid) return query(nodenum << 1 | 1, left, right);
return min(query(nodenum << 1, left, mid), query(nodenum << 1 | 1, mid + 1, right));
}
int jump(vector& nums) {
int n = nums.size();
buildTree(n);
updateNode(n-1, 0);
for (int i = n - 2; i >= 0; i--) {
updateNode(i, queryTree(i + 1, min(n-1, i + nums[i])) + 1);
}
return node[pos[0]].value;
}
};
有没有O(n)的做法呢?也是有的,直接上bfs即可。第一个元素先入队,一直bfs到最后一个元素即可,中间如果遇到已经访问过得节点,那么说明有更短的方法可以访问这个节点,不需要将其入队。因此,再加一个maxindex变量,比如说我现在10个元素已经入队了前6个,我现在front第3个元素开始加新元素,那我应该是直接从第7开始加,而不是从4,5,6再到这,这样就是标准的线性时间。
Runtime: 8 ms, faster than 97.00% of C++ online submissions for Jump Game II.
Memory Usage: 10.8 MB, less than 5.00% of C++ online submissions for Jump Game II.
class Solution {
public:
struct Node {
int index;
int step;
Node(int index=0, int step=0): index(index), step(step) {}
};
int jump(vector& nums) {
int n = nums.size();
if (n <= 1) return 0;
vector vis(n, false);
queue q;
q.push(Node(0, 0));
vis[0] = true;
Node front;
int maxindex = 0;
while (true) {
front = q.front();
q.pop();
for (int i = maxindex+1; i < n && i - front.index <= nums[front.index]; i++) {
if (i == n-1) return front.step+1;
if (!vis[i]) {
q.push(Node(i, front.step+1));
vis[i] = true;
}
maxindex = i;
}
}
return 0;
}
};
常规的n皇后,直接dfs即可。横用一个数组,竖用一个数组,副对角线用2n大小数组和i+j,主对角线数组i-j+n
Runtime: 0 ms, faster than 100.00% of C++ online submissions for N-Queens.
Memory Usage: 10 MB, less than 100.00% of C++ online submissions for N-Queens.
class Solution {
public:
vector> solveNQueens(int n) {
vector> ans;
vector t(n, string(n, '.'));
vector a(n, 0), b(n, 0), c(2*n, 0), d(2*n, 0);
for (int i = 0; i < n; i++) {
t[0][i] = 'Q';
a[i] = 1;
b[i] = 1;
c[0+i] = 1;
d[0-i+n] = 1;
dfs(n, 1, ans, t, a, b, c, d);
a[i] = 0;
b[i] = 0;
c[0+i] = 0;
d[0-i+n] = 0;
t[0][i] = '.';
}
return ans;
}
void dfs(int n, int cur, vector>& ans, vector& t, vector& a, vector& b, vector& c, vector& d) {
if (cur == n) {
ans.push_back(t);
return;
}
for (int i = 0; i < n; i++) {
if (!a[i] && !b[i] && !c[cur+i] && !d[cur-i+n]) {
t[cur][i] = 'Q';
a[i] = 1;
b[i] = 1;
c[cur+i] = 1;
d[cur-i+n] = 1;
dfs(n, cur + 1, ans, t, a, b, c, d);
a[i] = 0;
b[i] = 0;
c[cur+i] = 0;
d[cur-i+n] = 0;
t[cur][i] = '.';
}
}
}
};
上面求具体解,这道求具体解的个数,不是很懂这题存在的意义。。。
Runtime: 0 ms, faster than 100.00% of C++ online submissions for N-Queens II.
Memory Usage: 8.5 MB, less than 41.67% of C++ online submissions for N-Queens II.
class Solution {
public:
int totalNQueens(int n) {
int ans = 0;
vector t(n, string(n, '.'));
vector a(n, 0), b(n, 0), c(2*n, 0), d(2*n, 0);
for (int i = 0; i < n; i++) {
t[0][i] = 'Q';
a[i] = 1;
b[i] = 1;
c[0+i] = 1;
d[0-i+n] = 1;
dfs(n, 1, ans, t, a, b, c, d);
a[i] = 0;
b[i] = 0;
c[0+i] = 0;
d[0-i+n] = 0;
t[0][i] = '.';
}
return ans;
}
void dfs(int n, int cur, int& ans, vector& t, vector& a, vector& b, vector& c, vector& d) {
if (cur == n) {
ans++;
return;
}
for (int i = 0; i < n; i++) {
if (!a[i] && !b[i] && !c[cur+i] && !d[cur-i+n]) {
t[cur][i] = 'Q';
a[i] = 1;
b[i] = 1;
c[cur+i] = 1;
d[cur-i+n] = 1;
dfs(n, cur + 1, ans, t, a, b, c, d);
a[i] = 0;
b[i] = 0;
c[cur+i] = 0;
d[cur-i+n] = 0;
t[cur][i] = '.';
}
}
}
};
https://leetcode.com/problems/insert-interval/
模拟一遍即可,注意类内cmp函数需要static const
Runtime: 16 ms, faster than 84.17% of C++ online submissions for Insert Interval.
Memory Usage: 12.2 MB, less than 100.00% of C++ online submissions for Insert Interval.
class Solution {
public:
static bool cmp(const vector& a, const vector& b) {
if (a[0] != b[0]) return a[0] < b[0];
return a[1] < b[1];
}
vector> insert(vector>& intervals, vector& newInterval) {
int n = intervals.size();
sort(intervals.begin(), intervals.end(), cmp);
vector> ans;
for (int i = 0; i < n; i++) {
if (intervals[i][1] < newInterval[0] || intervals[i][0] > newInterval[1]) {
ans.push_back(intervals[i]);
continue;
}
if (intervals[i][1] <= newInterval[1] && intervals[i][0] >= newInterval[0]) {
continue;
}
if (intervals[i][1] >= newInterval[1] && intervals[i][0] <= newInterval[0]) {
newInterval[0] = intervals[i][0];
newInterval[1] = intervals[i][1];
continue;
}
if (intervals[i][0] < newInterval[0] && intervals[i][1] < newInterval[1]) {
newInterval[0] = intervals[i][0];
continue;
}
if (intervals[i][1] > newInterval[1] && intervals[i][0] > newInterval[0]) {
newInterval[1] = intervals[i][1];
continue;
}
}
ans.push_back(newInterval);
sort(ans.begin(), ans.end(), cmp);
return ans;
}
};
直接模拟,没什么技术含量,真正面试按部就班做即可,这里直接秒了
Runtime: 16 ms, faster than 91.20% of Python online submissions for Valid Number.
Memory Usage: 11.9 MB, less than 16.67% of Python online submissions for Valid Number.
class Solution(object):
def isNumber(self, s):
"""
:type s: str
:rtype: bool
"""
try:
float(s)
except:
return False
return True
https://leetcode.com/problems/text-justification/
直接模拟一遍过,特殊情况特判下,没什么好说的
Runtime: 0 ms, faster than 100.00% of C++ online submissions for Text Justification.
Memory Usage: 9.1 MB, less than 100.00% of C++ online submissions for Text Justification.
class Solution {
public:
string solve(queue& q, int maxWidth, int nowlen) {
string res = "";
int interval = maxWidth - nowlen;
if (q.size() == 1) {
res += q.front();
q.pop();
res += string(interval, ' ');
return res;
}
int interval_batch = q.size() - 1;
int interval_yu = interval % interval_batch;
bool first = true;
while (!q.empty()) {
if (first) {
first = false;
} else {
res += string(interval / interval_batch, ' ');
if (interval_yu) {
res += " ";
interval_yu--;
}
}
res += q.front();
q.pop();
}
return res;
}
vector fullJustify(vector& words, int maxWidth) {
queue q;
int nowlen = 0;
vector ans;
for (int i = 0; i < words.size(); i++) {
if (q.size() + nowlen + words[i].length() > maxWidth) {
ans.push_back(solve(q, maxWidth, nowlen));
nowlen = 0;
}
q.push(words[i]);
nowlen += words[i].length();
}
if (!q.empty()) {
string tmp = "";
bool first = true;
while (!q.empty()) {
if (first) {
first = false;
} else {
tmp += ' ';
}
tmp += q.front();
q.pop();
}
tmp += string(maxWidth - tmp.length(), ' ');
ans.push_back(tmp);
}
return ans;
}
};
https://leetcode.com/problems/edit-distance/
入门dp,没什么好说的,这里顺手给出dp的两种变式解法。
解法一:记忆化dp,优点是思路清晰,代码易懂,缺点是可能爆栈、时间常数、空间常数比较大
Runtime: 20 ms, faster than 15.27% of C++ online submissions for Edit Distance.
Memory Usage: 11.6 MB, less than 12.50% of C++ online submissions for Edit Distance.
class Solution {
public:
int minDistance(string word1, string word2) {
int l1 = word1.length(), l2 = word2.length();
vector> dp(l1 + 1, vector(l2 + 1, -1));
return dfs(word1, word2, l1, l2, dp);
}
int dfs(string& word1, string& word2, int p, int q, vector>& dp){
if (dp[p][q] == -1) {
if (p == 0 || q == 0) dp[p][q] = p + q;
else dp[p][q] = min(min(dfs(word1, word2, p - 1, q, dp), dfs(word1, word2, p, q - 1, dp)) + 1, dfs(word1, word2, p - 1, q - 1, dp) + (word1[p-1] == word2[q-1] ? 0 : 1));
}
return dp[p][q];
}
};
解法二:标准dp,赋初值,转移方程,递推,给终值,按部就班
Runtime: 8 ms, faster than 92.43% of C++ online submissions for Edit Distance.
Memory Usage: 11.2 MB, less than 65.63% of C++ online submissions for Edit Distance.
class Solution {
public:
int minDistance(string word1, string word2) {
int l1 = word1.length(), l2 = word2.length();
vector> dp(l1 + 1, vector(l2 + 1, 0));
for (int i = 0; i <= l1; i++) dp[i][0] = i;
for (int i = 0; i <= l2; i++) dp[0][i] = i;
for (int i = 1; i <= l1; i++) {
for (int j = 1; j <= l2; j++) {
dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1);
dp[i][j] = min(dp[i][j], dp[i-1][j-1] + (word1[i-1] == word2[j-1] ? 0 : 1));
}
}
return dp[l1][l2];
}
};
https://leetcode.com/problems/minimum-window-substring/
尺取法,两个指针模拟一遍,整个过程中的合法最小值就是答案,常数是256。
Runtime: 132 ms, faster than 7.26% of C++ online submissions for Minimum Window Substring.
Memory Usage: 19.3 MB, less than 8.00% of C++ online submissions for Minimum Window Substring.
class Solution {
public:
const int MAXCHAR = 256;
bool match(vector& ss, vector& st) {
for (int i = 0; i < MAXCHAR; i++) {
if (ss[i] < st[i]) return false;
}
return true;
}
string minWindow(string s, string t) {
string ans = "";
int lens = s.length(), lent = t.length();
vector ss(MAXCHAR, 0);
vector st(MAXCHAR, 0);
for (int i = 0; i < lent; i++) st[t[i]]++;
int l = 0, r = 0;
bool ok = false;
while (r <= lens) {
while(r <= lens) {
ok = match(ss, st);
if (ok) break;
ss[s[r]]++;
r++;
}
if (ok) {
if (ans == "" || r - l < ans.length()) {
ans = s.substr(l, r - l);
}
}
while(l < r) {
ok = match(ss, st);
if (!ok) break;
if (ans == "" || r - l < ans.length()) {
ans = s.substr(l, r - l);
}
ss[s[l]]--;
l++;
}
}
return ans;
}
};
https://leetcode.com/problems/largest-rectangle-in-histogram/
此题最直观的解法是模拟o(n^2)跑一遍,由于没有告诉数据范围,所以试了下,95/96,再加了个剪枝过了,但显然有更好的方法。
Runtime: 12 ms, faster than 88.85% of C++ online submissions for Largest Rectangle in Histogram.
Memory Usage: 10 MB, less than 97.14% of C++ online submissions for Largest Rectangle in Histogram.
class Solution {
public:
int largestRectangleArea(vector& heights) {
int n = heights.size();
if (!n) return 0;
vector minh(n, 0);
minh[n-1] = heights[n-1];
for (int i = n-2; i >= 0; i--) {
minh[i] = min(minh[i+1], heights[i]);
}
int ans = 0;
for (int i = 0; i < n; i++) {
int minv = heights[i], j = i;
if (minv > ans) ans = minv;
while (++j < n) {
minv = min(minv, heights[j]);
if (minv == minh[i]) {
ans = max(ans, (n-i)*minv);
break;
} else {
ans = max(ans, (j-i+1)*minv);
}
}
}
return ans;
}
};
https://leetcode.com/problems/maximal-rectangle/
首先直接硬暴力是n^6,软暴力穷举左上角然后向右下跑是n^4。这题显然复杂度不可能低于n^2。先试一下软暴力会不会超时。这种方法空间为O(1)。
Runtime: 56 ms, faster than 10.84% of C++ online submissions for Maximal Rectangle.
Memory Usage: 10.4 MB, less than 100.00% of C++ online submissions for Maximal Rectangle.
class Solution {
public:
int maximalRectangle(vector>& matrix) {
int m = matrix.size();
int n = m ? matrix[0].size() : 0;
int ans = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j] != '1') continue;
int t = j;
int mins = m;
while (t < n) {
int s = i;
while (s < mins && matrix[s][t] == '1') s++;
mins = min(mins, s);
ans = max(ans, (s - i) * (++t - j));
}
}
}
return ans;
}
};
当然,写完以后立马想到另外一种思路,每一次我都要去求每个点向下1的个数,冗余了很多,我只需要再拿一个数组预处理保存一下,就可以由n^4,1变成n^3,n^2
Runtime: 20 ms, faster than 94.23% of C++ online submissions for Maximal Rectangle.
Memory Usage: 11.3 MB, less than 55.56% of C++ online submissions for Maximal Rectangle.
class Solution {
public:
int maximalRectangle(vector>& matrix) {
int m = matrix.size();
int n = m ? matrix[0].size() : 0;
vector> cons(m, vector(n, 0));
for (int j = 0; j < n; j++) {
cons[m-1][j] = matrix[m-1][j] == '0' ? 0 : 1;
for (int i = m - 2; i >= 0; i--) {
cons[i][j] = matrix[i][j] == '0' ? 0 : cons[i+1][j] + 1;
}
}
int ans = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j] != '1') continue;
int t = j;
int mins = m;
while (t < n) {
mins = min(mins, cons[i][t]);
if (mins == 0) break;
ans = max(ans, mins * (++t - j));
}
}
}
return ans;
}
};
这题还有n^2的解法,模仿上面84,每一行跑过去,作为基线向上,看做当前行是x轴的直方图,求矩形最大面积,显然是n^2,不再赘述。
读了三遍愣是没读懂题目,后来仔细想了下样例1,将其分成2半字符能匹配上,有点像前序中序建树的意思。abcde和caebd,其中a/d,ab/bd,abc/ebd,abcd/aebd都无法一一对应,然后就是false。如果能对应上,再二分递归这个子字符串,如果只剩下一个了,相等即可,如果只剩下2个了,相等或交叉相等即可,如果3个或以上就从1,2,..,n-1穷举分判断。这中间可能需要上记忆化搜索。
193 / 283 test cases passed.
class Solution {
public:
bool isScramble(string s1, string s2) {
return bi(s1, s2, 0, s1.length(), 0, s2.length());
}
bool bi(string& s1, string& s2, int l1, int r1, int l2, int r2) {
int n = r1 - l1;
if (n == 0) {
return true;
} else if (n == 1) {
return s1[l1] == s2[l2];
} else if (n == 2) {
return (s1[l1] == s2[l2] && s1[l1+1] == s2[l2+1]) || (s1[l1+1] == s2[l2] && s1[l1] == s2[l2+1]);
} else {
if (s1.substr(l1, n) == s2.substr(l2, n)) return true;
for (int i = 1; i < n; i++) {
bool ok1 = s1.substr(l1, i) == s2.substr(l2, i);
bool ok2 = s1.substr(l1 + i, n - i) == s2.substr(l2 + i, n - i);
if (ok1 && ok2) return true;
else if (ok1 && !ok2) {
if (bi(s1, s2, l1 + i, r1, l2 + i, r2)) return true;
} else if (!ok1 && ok2) {
if (bi(s1, s2, l1, l1 + i, l2, l2 + i)) return true;
} else {
if ((bi(s1, s2, l1, l1 + i, r2 - i, r2) && bi(s1, s2, l1 + i, r1, l2, r2 - i)) || (bi(s1, s2, l1, l1 + i, l2, l2 + i) && bi(s1, s2, l1 + i, r1, l2 + i, r2))) return true;
}
}
return false;
}
}
};
上记忆化搜索
Runtime: 236 ms, faster than 5.00% of C++ online submissions for Scramble String.
Memory Usage: 203.5 MB, less than 9.52% of C++ online submissions for Scramble String.
class Solution {
public:
bool isScramble(string s1, string s2) {
int n = s1.length();
vector>>> dp(n+1, vector>>(n+1, vector>(n+1, vector(n+1, -1))));
return bi(dp, s1, s2, 0, n, 0, n);
}
bool bi(vector>>>& dp, string& s1, string& s2, int l1, int r1, int l2, int r2) {
if (dp[l1][r1][l2][r2] != -1) return dp[l1][r1][l2][r2];
int n = r1 - l1;
if (n == 0) {
return dp[l1][r1][l2][r2] = 1;
} else if (n == 1) {
return dp[l1][r1][l2][r2] = (s1[l1] == s2[l2]);
} else if (n == 2) {
return dp[l1][r1][l2][r2] = ((s1[l1] == s2[l2] && s1[l1+1] == s2[l2+1]) || (s1[l1+1] == s2[l2] && s1[l1] == s2[l2+1]));
} else {
if (s1.substr(l1, n) == s2.substr(l2, n)) return dp[l1][r1][l2][r2] = 1;
for (int i = 1; i < n; i++) {
bool ok1 = s1.substr(l1, i) == s2.substr(l2, i);
bool ok2 = s1.substr(l1 + i, n - i) == s2.substr(l2 + i, n - i);
if (ok1 && ok2) return dp[l1][r1][l2][r2] = 1;
else if (ok1 && !ok2) {
if (bi(dp, s1, s2, l1 + i, r1, l2 + i, r2)) return dp[l1][r1][l2][r2] = 1;
} else if (!ok1 && ok2) {
if (bi(dp, s1, s2, l1, l1 + i, l2, l2 + i)) return dp[l1][r1][l2][r2] = 1;
} else {
if ((bi(dp, s1, s2, l1, l1 + i, r2 - i, r2) && bi(dp, s1, s2, l1 + i, r1, l2, r2 - i)) || (bi(dp, s1, s2, l1, l1 + i, l2, l2 + i) && bi(dp, s1, s2, l1 + i, r1, l2 + i, r2))) return dp[l1][r1][l2][r2] = 1;
}
}
return dp[l1][r1][l2][r2] = 0;
}
}
};
虽然A了,但是时间和空间都很不理想,尝试压一下,这题我们的做法本质是维护一个四维dp数组,求第一个字符串i到j跟第二个字符串s到t是否匹配。冗余了一维,因为s-t===j-i
Runtime: 48 ms, faster than 21.18% of C++ online submissions for Scramble String.
Memory Usage: 23.5 MB, less than 14.29% of C++ online submissions for Scramble String.
class Solution {
public:
bool isScramble(string s1, string s2) {
int n = s1.length();
vector>> dp(n+1, vector>(n+1, vector(n+1, -1)));
return bi(dp, s1, s2, 0, n, 0, n);
}
bool bi(vector>>& dp, string& s1, string& s2, int l1, int r1, int l2, int r2) {
int n = r1 - l1;
if (dp[l1][l2][n] != -1) return dp[l1][l2][n];
if (n == 0) {
return dp[l1][l2][n] = 1;
} else if (n == 1) {
return dp[l1][l2][n] = (s1[l1] == s2[l2]);
} else if (n == 2) {
return dp[l1][l2][n] = ((s1[l1] == s2[l2] && s1[l1+1] == s2[l2+1]) || (s1[l1+1] == s2[l2] && s1[l1] == s2[l2+1]));
} else {
if (s1.substr(l1, n) == s2.substr(l2, n)) return dp[l1][l2][n] = 1;
for (int i = 1; i < n; i++) {
bool ok1 = s1.substr(l1, i) == s2.substr(l2, i);
bool ok2 = s1.substr(l1 + i, n - i) == s2.substr(l2 + i, n - i);
if (ok1 && ok2) return dp[l1][l2][n] = 1;
else if (ok1 && !ok2) {
if (bi(dp, s1, s2, l1 + i, r1, l2 + i, r2)) return dp[l1][l2][n] = 1;
} else if (!ok1 && ok2) {
if (bi(dp, s1, s2, l1, l1 + i, l2, l2 + i)) return dp[l1][l2][n] = 1;
} else {
if ((bi(dp, s1, s2, l1, l1 + i, r2 - i, r2) && bi(dp, s1, s2, l1 + i, r1, l2, r2 - i)) || (bi(dp, s1, s2, l1, l1 + i, l2, l2 + i) && bi(dp, s1, s2, l1 + i, r1, l2 + i, r2))) return dp[l1][l2][n] = 1;
}
}
return dp[l1][l2][n] = 0;
}
}
};
这题题目意思上来看错了,样例死都过不去。原来不是直接插,而是可以部分插,交错插……那就直接dp没得商量,记dp[i][j]为第一个字符串前i和第二个字符串前j是否匹配,0和1,那么答案就是dp[l1][l2]。转移方程,如果s3[i+j]==s1[i],那么dp[i][j]=dp[i-1][j],如果s3[i+j]==s2[j],那么dp[i][j]=dp[i][j-1],如果都相等就是取2者并集,不然就是0。初值:dp[0][0]=1,dp[1][0]=s1[0] == s3[0],dp[0][1]=s2[0]==s3[0],或者当i或j等于0时直接做字符串比较。再记忆化dp
Runtime: 0 ms, faster than 100.00% of C++ online submissions for Interleaving String.
Memory Usage: 9.3 MB, less than 7.14% of C++ online submissions for Interleaving String.
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
int l1 = s1.length(), l2 = s2.length(), l3 = s3.length();
if (l1 + l2 != l3) return false;
vector> dp(l1 + 1, vector(l2 + 1, -1));
return dfs(s1, s2, s3, dp, l1, l2);
}
bool dfs(string& s1, string& s2, string& s3, vector>& dp, int i, int j) {
if (dp[i][j] != -1) return dp[i][j];
if (i == 0) dp[i][j] = (s3.substr(0, j) == s2.substr(0, j));
else if (j == 0) dp[i][j] = (s3.substr(0, i) == s1.substr(0, i));
else {
bool ok1 = false, ok2 = false;
if (s3[i+j-1] == s1[i-1]) ok1 = dfs(s1, s2, s3, dp, i-1, j);
if (s3[i+j-1] == s2[j-1]) ok2 = dfs(s1, s2, s3, dp, i, j-1);
dp[i][j] = (ok1 || ok2);
}
return dp[i][j];
}
};
这题乍看一下有点无从下手,但是仔细从bst的特性下手即可解。从样例上看,题目认为的二叉搜索树必然是递增的那种,这就给我们的题目降低了复杂度。那么对于这样一棵树,第一反应想到的特性是,中序遍历是严格递增的。样例一的中序遍历是321,我们不能交换12或者23,反应到中序遍历上,我们先认为结点本身是不交换的,我们只是给这个结点的值换一下,交换也就是改变一下2个结点的值,因此交换结点直接反应在中序遍历的交换。因此对于321,我们可以从交换后必须递增发现只能交换13。于是求出其中序遍历的结果,最无脑的办法就是排序,当然读者可以思考一下对于只交换了2个元素的有序数组,哪种排序方式最快。然后比对一下位置上哪个值不一样。
Runtime: 16 ms, faster than 97.27% of C++ online submissions for Recover Binary Search Tree.
Memory Usage: 18.7 MB, less than 42.10% of C++ online submissions for Recover Binary Search Tree.
/**
* 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:
void recoverTree(TreeNode* root) {
vector v;
search(root, v);
vector w(v);
sort(w.begin(), w.end());
int n = v.size(), a, b;
for (int i = 0; i < n; i++) {
if (v[i] != w[i]) {
a = v[i];
b = w[i];
break;
}
}
dfs(root, a, b);
}
void search(TreeNode* root, vector& v) {
if (root->left) search(root->left, v);
v.push_back(root->val);
if (root->right) search(root->right, v);
}
void dfs(TreeNode* root, int a, int b) {
if (root->val == a) root->val = b;
else if (root->val == b) root->val = a;
if (root->left) dfs(root->left, a, b);
if (root->right) dfs(root->right, a, b);
}
};
直接暴力肯定是不行的,复杂度是C(n,m),是阶乘级。尝试用dp[i][j],表示字符串s前i个字符串有多少种完全包含了字符串前j的子序列。注意,此处s和t显著不等价。假设我们已知了当前全部dp,那么当i+1时,dp[i+1][j]首先是在dp[i][j]的基础上,也就是不以i+1结尾的全部子序列,然后加上所有以i+1结尾的子序列,即当s[i+1]=t[j]时再加上dp[i][j-1]。初值dp[0][0]=1,dp[0][1]=0, ...dp[0][lt]=0,dp[1][0]=1,dp[2][0]=1,...RE了一发,因为爆了int上限需要换成ll
Runtime: 12 ms, faster than 44.02% of C++ online submissions for Distinct Subsequences.
Memory Usage: 15.7 MB, less than 8.33% of C++ online submissions for Distinct Subsequences.
class Solution {
public:
int numDistinct(string s, string t) {
int ls = s.length(), lt = t.length();
vector> dp(ls+1, vector(lt+1, 0));
for (int i = 0; i < ls; i++) dp[i][0] = 1;
for (int j = 1; j <= lt; j++) {
for (int i = 1; i <= ls; i++) {
dp[i][j] = dp[i-1][j];
if (s[i-1] == t[j-1]) dp[i][j] += dp[i-1][j-1];
}
}
return dp[ls][lt];
}
};
一道easy的加强版,没什么好说的,直接暴力,不过我上来先优化了一下数组保证是严格交错减增减增数组。
Runtime: 12 ms, faster than 22.61% of C++ online submissions for Best Time to Buy and Sell Stock III.
Memory Usage: 12 MB, less than 14.29% of C++ online submissions for Best Time to Buy and Sell Stock III.
class Solution {
public:
int maxProfit(vector& prices) {
vector a;
int n = prices.size(), p, t;
a.push_back(99999999);
for (int i = 0; i < n; i++) {
if (i && prices[i] == prices[i-1]) continue;
a.push_back(prices[i]);
}
a.push_back(-99999999);
prices.clear();
prices.push_back(99999999);
for (int i = 1; i < a.size() - 1; i++) {
if (a[i] >= a[i-1] && a[i] <= a[i+1]) continue;
if (a[i] <= a[i-1] && a[i] >= a[i+1]) continue;
prices.push_back(a[i]);
}
prices.push_back(-99999999);
n = prices.size();
int ans = 0;
if (n == 2) ans = 0;
else if (n == 4) ans = prices[2] - prices[1];
else {
for (int i = 0; i < n/2 - 2; i++) {
ans = max(ans, solve(prices, 0, 2*i+3)+solve(prices, 2*i+3, n));
}
}
return ans;
}
int solve(vector& prices, int l, int r) {
vector pmax(r, 0);
pmax[r-1] = prices[r-1];
for (int i = r - 2; i >= l; i--) {
pmax[i] = max(prices[i], pmax[i+1]);
}
int ans = 0;
for (int i = l; i < r; i++) {
ans = max(ans, pmax[i] - prices[i]);
}
return ans;
}
};
当然了,在solve的时候计算十分冗余,重复计算了n次,我们再把时间复杂度从n^2优化到n。
Runtime: 4 ms, faster than 98.65% of C++ online submissions for Best Time to Buy and Sell Stock III.
Memory Usage: 10 MB, less than 35.71% of C++ online submissions for Best Time to Buy and Sell Stock III.
class Solution {
public:
int maxProfit(vector& prices) {
vector a;
int n = prices.size(), p, t;
a.push_back(99999999);
for (int i = 0; i < n; i++) {
if (i && prices[i] == prices[i-1]) continue;
a.push_back(prices[i]);
}
a.push_back(-99999999);
prices.clear();
prices.push_back(99999999);
for (int i = 1; i < a.size() - 1; i++) {
if (a[i] >= a[i-1] && a[i] <= a[i+1]) continue;
if (a[i] <= a[i-1] && a[i] >= a[i+1]) continue;
prices.push_back(a[i]);
}
prices.push_back(-99999999);
n = prices.size();
int ans = 0;
if (n == 2) ans = 0;
else if (n == 4) ans = prices[2] - prices[1];
else {
a[n-1] = prices[n-1];
for (int i = n - 2; i >= 0; i--) {
a[i] = max(prices[i], a[i+1]);
}
for (int i = 0; i < n; i++) {
a[i] = a[i] - prices[i];
}
vector b(n, 0);
b[0] = prices[0];
for (int i = 1; i < n; i++) {
b[i] = min(prices[i], b[i-1]);
}
int b0 = prices[2]-prices[1];
for (int i = 3; i < n; i++) {
b0 = max(b0, prices[i] - b[i]);
ans = max(ans, b0+a[i]);
}
}
return ans;
}
};
直接无脑暴力记忆化,因为时间是O(n)。记dp[root]是包含了本身结点,然后最多包含了一个子节点dp的最大路径值。这中间再加一个变量ans,存的是最多包含2个子节点dp的路径值。于是最后答案是max(dp[root],ans)。当然dfs发现dp值其实不需要记录。
Runtime: 28 ms, faster than 85.34% of C++ online submissions for Binary Tree Maximum Path Sum.
Memory Usage: 24.4 MB, less than 90.91% of C++ online submissions for Binary Tree Maximum Path Sum.
/**
* 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:
const int MINV = -1e9;
int maxPathSum(TreeNode* root) {
int ans = MINV;
int tmp = dfs(root, ans);
return max(ans, tmp);
}
int dfs(TreeNode*& root, int& ans) {
int t1 = 0, t2 = 0;
if (root->left != NULL) t1 = dfs(root->left, ans);
if (root->right != NULL) t2 = dfs(root->right, ans);
if (t1 < 0) t1 = 0;
if (t2 < 0) t2 = 0;
ans = max(ans, root->val + t1 + t2);
return root->val + max(t1, t2);
}
};