除了前面25道题目,为了省时间,后面只做hard的题目
part1包含了1000题以内的题目(未加锁部分hard题)
500-1000见LeetCode题解part2
1000题以上的见LeetCode题解part3
1. Two Sum (Easy)
题意:给一个数组和一个数,求数组中哪两个数的和为这个数
题解:先排序,然后两个指针扫。容易证明这是可以的。因为如果当前的某个指针和已经扫过的某个数的和为目标值,那么则不可能达到现在这个状态。
时间复杂度是O(nlogn),不过如果数字都比较小,可以用计数的方法查找另一个数是否存在(或者用hash),这个解法为O(n)的解法。
语言:C++
#include
#include
#include
#include
#include
using namespace std;
class Solution {
struct Node{
int value;
int index;
const bool operator < (const Node & b) {
return value < b.value;
}
};
public:
vector twoSum(vector &nums, int target) {
int len = nums.size();
Node *node = new Node[nums.size()];
for(size_t i = 0;i < nums.size(); ++i){
node[i].value = nums[i];
node[i].index = i;
}
sort(node, node + len);
size_t left = 0, right = len - 1;
int idL = - 1, idR = -1;
while(left < right){
if(node[left].value + node[right].value == target){
idL = node[left].index;
idR = node[right].index;
break;
}else if(node[left].value + node[right].value < target){
++left;
}else{
--right;
}
}
delete node;
assert(idL != -1);
vector ans(2);
ans[0] = idL;
ans[1] = idR;
return ans;
}
};
int main()
{
vector num(3);
int target = 6;
num[0] = 3; num[1] = 2; num[2] = 4;
Solution s;
vector ans = s.twoSum(num, target);
cout<
2. Add Two Numbers (Medium)
题意:给两个链表,每个链表表示一个数字(逆序的,个位在最前面),求他们的和,返回同样的格式(一个链表)
题解:简单的模拟加法过程即可
语言:C++
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode rootNode(0);
ListNode *pCurNode = &rootNode;
int a = 0;
while (l1 || l2)
{
int v1 = (l1 ? l1->val : 0);
int v2 = (l2 ? l2->val : 0);
int temp = v1 + v2 + a;
a = temp / 10;
ListNode *pNode = new ListNode((temp % 10));
pCurNode->next = pNode;
pCurNode = pNode;
if (l1) l1 = l1->next;
if (l2) l2 = l2->next;
}
if (a > 0)
{
ListNode *pNode = new ListNode(a);
pCurNode->next = pNode;
}
return rootNode.next;
}
};
把上面的模换成下面的
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode rootNode(0);
ListNode *pCurNode = &rootNode;
int a = 0;
while (l1 || l2)
{
int v1 = (l1 ? l1->val : 0);
int v2 = (l2 ? l2->val : 0);
int temp = v1 + v2 + a;
a = temp > 9;
ListNode *pNode = new ListNode(temp - a * 10);
pCurNode->next = pNode;
pCurNode = pNode;
if (l1) l1 = l1->next;
if (l2) l2 = l2->next;
}
if (a > 0)
{
ListNode *pNode = new ListNode(a);
pCurNode->next = pNode;
}
return rootNode.next;
}
};
3. Longest Substring Without Repeating Characters(Medium)
题意:给定一个字符串,找出没有重复字符的最长子串。
题解:首先可以线性时间求解出当前字符上一次出现的位置,用一个数组last_pos保存下来。然后可以得到,一个子串(从begin开始到end位置结束的串),没有重复字符当且仅当在这个子串中last_pos() < b。
解法就是枚举子串开始位置i,然后用index索引枚举结束位置。注意到如果j > i,那么从j开始的最长不重复子串的末尾一定在以i开始的那个最长子串末尾的后面(至少相等)。也就是说index不用从i 枚举,而是从上一次枚举的位置开始,这样就保证了线性的时间,每次index都会加1。
渐进时间复杂度:O(n)
语言: python
class Solution(object):
def lengthOfLongestSubstring(self, s):
lens = len(s);
w = [-1] * 100;
last_pos = [0] * lens;
#pos位置的字符上一次出现的位置,第一次出现,标记为-1
for pos, c in enumerate(s):
last_pos[pos] = w[ord(c) - ord('a')];
w[ord(c) - ord('a')] = pos;
ans = 0;
index = 0;
for i in range(lens):
while(index < lens - 1 and last_pos[index + 1] < i):
index = index + 1;
ans = max(ans,index - i + 1);
return ans;
4. Median of Two Sorted Arrays (Hard)
题意:两个有序数组,求中值
题解:可以使用中间二分形式进行,每次判断中间的值的大小,舍弃不可能是答案的某个数组的一半,当数组一个已经为空时,直接得到答案。具体如下:
对于两个数组: a[apre, amid,asuf],b[apre,bmid,bsuf],pre表前半段,suf表后半段,mid表中间元素(pre和suf可为空)。则答案在这6个段中的一个。(假设pre和suf都非空,因为如果为空,答案显然)比较a[amid]和b[bmid],如果a[amid] < b[bmid],那么可以确定顺序的有a[apre] <= a[amid] <= b[bmid] <= b[bsuf]。故而可以根据实际情况舍弃掉a[apre]或者b[bsuf]。那什么情况舍弃哪一个呢?有比较多的情况需要考虑。这个 方法虽然可以,但是到最后又非常多的边界需要判断,非常容易混乱。而如果不是取中位,而是k/2位的话,边界简单得多,思路和上面一样,只不过舍弃的情况没那么复杂。
时间复杂度(O(logn)
#include
#include
using namespace std;
double findKth(int *a, int m, int *b, int n, int k)
{
if (m > n)
return findKth(b, n, a, m, k);
if (m == 0)
return b[k - 1];
if (k == 1)
return a[0] < b[0] ? a[0] : b[0] ;
int pa = k / 2 < m ? k / 2 : m, pb = k - pa;
if (a[pa - 1] < b[pb - 1])
return findKth(a + pa, m - pa, b, n, k - pa);
else if (a[pa - 1] > b[pb - 1])
return findKth(a, m, b + pb, n - pb, k - pb);
else
return a[pa - 1];
}
double findMedianSortedArrays(int* A, int m, int* B, int n) {
int total = m + n;
if (total & 0x1)
return findKth(A, m, B, n, total / 2 + 1);
else
return (findKth(A, m, B, n, total / 2)
+ findKth(A, m, B, n, total / 2 + 1)) / 2;
}
int main()
{
int *a ,b[] = {1};
cout<
5. Longest Palindromic Substring (Medium)
题意:最大回文子串
题解:manacher算法;枚举中心,利用前面的信息。具体见manacher算法,无需多说。
渐进时间复杂度(O(n))
#include
#include
using namespace std;
class Solution {
public:
string longestPalindrome(string s) {
string str(s.size() * 2 + 3,'#');
str[0] = '$';
str[s.size() * 2 + 2] = '@';
for(size_t i = 0;i < s.size(); ++i){
str[i * 2 + 2] = s[i];
}
return findLongestPalindrome(str, s);
}
string findLongestPalindrome(string str, string s){
size_t *d = new size_t[str.size()];
size_t mx = 0, mxId = 0, longestLenth = 1 , longestLenthPos = 0;
d[0] = 1;
for(size_t i = 1;i < str.size(); ++i){
if(mx <= i) d[i] = 1;
else d[i] = min(d[2 * mxId - i] , mx - i);
while(str[i - d[i]] == str[i + d[i]]) ++d[i];
if(d[i] + i > mx){
mx = d[i] + i;
mxId = i;
}
if(d[i] > longestLenth){
longestLenth = d[i] - 1;
longestLenthPos = i;
}
}
delete d;
size_t startPos = (longestLenthPos - longestLenth) / 2;
return s.substr(startPos, longestLenth);
}
};
int main()
{
Solution s;
string str = "ccd";
cout<
6. ZigZag Conversion
题意:将一个字符串转化成zigZag形状,然后按行重新排列输出。
题解:简单模拟。
不过编译器好像跟本地的不太一样,对关于vector初始化,需要push_back过才能取出,否则是runtimeError 而不是取出空串。
代码:
#include
#include
#include
using namespace std;
string convert(string s, int nRows) {
if(nRows <= 1 || s.length() <= nRows)
return s;
vector zigZags(nRows);
int curRow = 0, step = 1;
for (string::iterator s_iter = s.begin(); s_iter != s.end(); ++s_iter){
zigZags[curRow].push_back(*s_iter);
if(curRow == 0) step = 1;
else if(curRow == nRows - 1) step = -1;
curRow += step;
}
s.clear();
string::iterator s_it;
for(vector::iterator vit = zigZags.begin(); vit != zigZags.end(); ++vit){
s += *vit;
}
return s;
}
int main()
{
string s = "PAYPALISHIRING;
int nRows = 3;
cout<
7. Reverse Integer
题意:将数字翻转
题解:直接转,注意前导零和溢出即可
代码:
#include
#include
#include
#include
using namespace std;
int reverse(int x) {
if (x == 0) return 0;
int sign = (x > 0) * 2 - 1;
long long y = abs(x);
long long res = 0;
while(y > 0){
res = res * 10 + y % 10;
y /= 10;
}
res = sign * res;
if(res > (1LL << 31) - 1 || res < -(1LL << 31))
res = 0;
return res;
}
int main()
{
cout<
8. String to Integer (atoi)
题意:按照题目将字符串转成int。规则是,前缀不合法输出前缀,合法前缀为空输出0,超出int输出maxint,小于minint输出minint即可
题解:主要是题目各种输入情况没有说明
#include
#include
#include
#include
using namespace std;
int myAtoi(string str) {
int sign = 1;
int max_int = 2147483647;
int min_int = -2147483648;
string::iterator s_it = str.begin();
while(s_it != str.end() && *s_it == ' ') ++s_it;
if(s_it != str.end() && (*s_it == '+' || *s_it == '-')){
sign = ((*s_it == '+') * 2 - 1);
++s_it;
}
long long res = 0;
int num = 0;
for(; s_it != str.end() ; ++ s_it){
if(*s_it < '0' || *s_it > '9') break;
res = res * 10 + sign * (*s_it - '0');
++num;
if(res > max_int) {
res = max_int;
break;
}
if(res < min_int){
res = min_int;
break;
}
}
if(num == 0) return 0;
return res;
}
int main()
{
cout<
9. Palindrome Number
题意:判断一个整数是不是回文数
题解:将x的后半段倒置,然后和前半段比较即可,注意可能有奇数个数字或偶数,末尾有0,以及负数的情形即可。
class Solution {
public:
bool isPalindrome(int x) {
if(x < 0 || (x != 0 && x % 10==0))
return false;
int y = 0;
while(x > y){
y = y * 10 + x % 10;
x /= 10;
}
return (x == y) || (x == y / 10);
}
};
10. Regular Expression Matching
题意:简单的正则表达式,判断给定字符串是不是符合给定的模式(只包含字符和*以及.)
题解:按照多年的acm经验,一眼看出是简单DP题。
dp[i][j]表示字符串和0到i个位置的子串,是否能与模式串0到j匹配。然后就分几种情况进行转移即可。注意在前面增加了空串(即0为置表示空串),能够简化代码。
解如下:
设d[i][j]表示原字符串的0到i能否和模式串的0到j匹配。字符串从1位置开始,0表示空串(方便处理)
故d[0][0] = 1,下面求转移方程
分三种情况:
p[j] = '.', p[j] = '*’和其他
当p[j] = '*' 时,要使得d[i][j] = 1,必须有如下情况之一发生
a.d[i][j - 2] = 1 (p[j - 1], p[j] 匹配空串)
b.d[i - 1][j - 1] = 1且(s[i] = p[j - 1]或 p[j - 1] = '.')
当p[j] = '.', 要使得d[i][j] = 1,必须有
d[i - 1][j - 1] = 1
其他:
d[i - 1][j - 1] = 1且 s[i] = p[j]
class Solution {
public:
static const size_t maxN = 1000;
static bool dp[maxN][maxN];
bool isMatch(string s, string p) {
size_t slen = s.length();
size_t plen = p.length();
dp[0][0] = true;
for (size_t i = 0; i <= slen; ++i)
for (size_t j = 1; j <= plen; ++j)
if (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[slen][plen];
}
};
bool Solution::dp[maxN][maxN];
11. Container With Most Water
题意:给定一组数据,d0,d1,...dn,表示第i个位置有长为di的竖线,问这些竖线中那两根和x轴围成的容器能装最多的水?
题解:考虑双指针。从两边开始,注意到,如果两个指针(i和j)的一边比较短,那么,这一个边就可以抛弃掉,因为它不可能是最优解(不妨设i比较短(或者等长),假设它和另外一根线k组成最优解,那么k可能在i前面,ij中间,或者j后面。首先k不可能在i前面或者j后面,不然的话,我们不可能把k抛弃掉。另外,它也不可能在ij中间,因为如果在ij中间的话ik肯定没有ij这一对好,所以i可以抛弃掉)。
class Solution {
public:
int maxArea(vector& height) {
int ans = 0, left = 0, right = height.size() - 1;
while (left < right) {
ans = max(ans, min(height[left], height[right]) * (right - left));
if (height[left] < height[right])
++left;
else
--right;
}
return ans;
}
};
12. Integer to Roman
题意:将一个整数转换成罗马数字(罗马数字参考http://baike.baidu.com/link?url=uhmgSPP-MZpOKblUj9gO6pvpuLzgx2XteJGqyPoJjmrTpRZ8H_Jx_WUz2gmIxMwnLKKZKpnkPOUVbkMc4RY0fjYjnYU4Y6snyWn0lmLXu9gHxsUoLi7PgJBr_cdm-NNd)
数字在1到3999之间
简单题,这是别人写的代码
class Solution {
public:
string intToRoman(int num) {
string M[] = {"", "M", "MM", "MMM"};
string C[] = {"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"};
string X[] = {"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"};
string I[] = {"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"};
return M[num / 1000] + C[(num % 1000) / 100] + X[(num % 100) / 10] + I[num % 10];
}
};
13. Roman to Integer
题意:将罗马数字转换成整数
题解:简单题
别人的代码:http://blog.csdn.net/booirror/article/details/43197595
class Solution {
public:
int getVal(char a){
switch (a) {
case 'I':
return 1;
case 'V':
return 5;
case 'X':
return 10;
case 'L':
return 50;
case 'C':
return 100;
case 'D':
return 500;
case 'M':
return 1000;
}
return 0;
}
int romanToInt(string s){
int res = 0;
char max = 'I';
for (int i = s.size()-1; i >= 0; --i) {
if (getVal(s[i]) >= getVal(max)) {
max = s[i];
res += getVal(s[i]);
} else {
res -= getVal(s[i]);
}
}
return res;
}
};
14. Longest Common Prefix
题意:给定一个字符串数组,找出他们的最长公共前缀。
题解:简单题
class Solution {
public:
string longestCommonPrefix(vector& strs) {
string prefix = "";
if(strs.size() < 1) return prefix;
for(size_t i = 0; i < strs[0].size();prefix += strs[0][i++])
for(size_t j = 0;j < strs.size(); ++j)
if(i >= strs[j].size() || strs[j][i] != strs[0][i]) return prefix;
return prefix;
}
};
15. 3Sum
题意:给定一个数组,三个数和为0的数对有哪些
题解:为了避免重复,先排序。先枚举第一个数,然后2和3 用two sum的方法。下面代码修改自
1.two sum(不过这里不需要给出下标),渐进时间复杂度为)(n^2)。容易改成计数或者hash的做法,这里不给出。
quadruple
class Solution {
public:
vector> threeSum(vector& nums) {
sort(nums.begin(), nums.end());
vector> ans;
int len = nums.size();
for(size_t i = j + 1; i < len; ++i){
int target = -nums[i] - nums[j];
size_t left = i + 1, right = len - 1;
while(true){
while(left > i + 1 && left < right && nums[left] == nums[left - 1]) ++left;
while(right < len - 1 && left < right && nums[right] == nums[right + 1]) --right;
if(left >= right) break;
if(nums[left] + nums[right] == target){
vector triplet(3, 0);
triplet[0] = nums[i];
triplet[1] = nums[left++];
triplet[2] = nums[right--];
ans.push_back(triplet);
}else if(nums[left] + nums[right] < target){
++left;
}else{
--right;
}
}
}
return ans;
}
};
16. 3Sum Closest
题意:给定一个数组和一个目标值,求离目标最近的三个数的和是多少?
题解:和15没啥不一样,改一下代码即可
class Solution {
public:
int threeSumClosest(vector& nums, int target) {
sort(nums.begin(), nums.end());
int len = nums.size();
int ans = nums[0] + nums[1] + nums[2];
int dis = abs(ans - target);
for(size_t i = 0; i < len; ++i){
if(i > 0 && nums[i] == nums[i - 1]) continue;
size_t left = i + 1, right = len - 1;
int T = target - nums[i];
while(true){
while(left > i + 1 && left < right && nums[left] == nums[left - 1]) ++left;
while(right < len - 1 && left < right && nums[right] == nums[right + 1]) --right;
if(left >= right) break;
if(abs(nums[left] + nums[right] - T) < dis){
dis = abs(nums[left] + nums[right] - T);
ans = nums[left] + nums[right] + nums[i];
}
if(dis == 0) break;
if(nums[left] + nums[right] < T){
++left;
}else{
--right;
}
}
}
return ans;
}
};
17. Letter Combinations of a Phone Number
题意:给定一串数字字符串,问按照电话上的字母转换规则,能转换成那些字母的字符串。
题解:可以有多种方法解决,我是利用计数迭代的方法,遍历所有的排列。
class Solution {
public:
vector letterCombinations(string digits) {
string kvmaps[8] = {"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz" };
int length[8] = {3,3,3,3,3,4,3,4};
vector ans;
if(digits == "") return ans;
vector iter(digits.size(),0);
bool flag = true;
int digit;
while(flag){
string s = "";
for(int i = 0 ; i < digits.size(); ++i){
digit = digits[i] - '0';
if(digit == 0) digit = 10;
s += kvmaps[digit - 2][iter[i]];
}
ans.push_back(s);
++iter[0];
for(int i = 0; i < digits.size(); ++i){
digit = digits[i] - '0';
if(digit == 0) digit = 10;
if(iter[i] >= length[digit - 2]){
if(i == digits.size() - 1){flag = false; break;}
else{
iter[i] = 0;
++iter[i + 1];
}
}
}
}
return ans;
}
};
18. 4Sum
题意:给定一个数组和一个目标整数,求数组中不重复的四元组的和为目标的四元组
题解:在15题加一层循环即可
class Solution {
public:
vector> fourSum(vector& nums,int T) {
sort(nums.begin(), nums.end());
vector> ans;
int len = nums.size();
for(size_t j = 0;j < len; ++j){
if(j > 0 && nums[j] == nums[j - 1]) continue;
for(size_t i = j + 1; i < len; ++i){
if(i > j + 1 && nums[i] == nums[i - 1]) continue;
int target = T - nums[i] - nums[j];
size_t left = i + 1, right = len - 1;
while(true){
while(left > i + 1 && left < right && nums[left] == nums[left - 1]) ++left;
while(right < len - 1 && left < right && nums[right] == nums[right + 1]) --right;
if(left >= right) break;
if(nums[left] + nums[right] == target){
vector quadruples(4, 0);
quadruples[0] = nums[j];
quadruples[1] = nums[i];
quadruples[2] = nums[left++];
quadruples[3] = nums[right--];
ans.push_back(quadruples);
}else if(nums[left] + nums[right] < target){
++left;
}else{
--right;
}
}
}
}
return ans;
}
};
19. Remove Nth Node From End of List
题意:通过一次遍历,将链表的倒数第n个删掉
题解:虽然说是一次遍历,实际上只不过是两次遍历写在一个循环里面罢了。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* h1 = head, *h2 = head;
while(n-->0) h2 = h2->next;
if(!h2) return head->next;
h2 = h2->next;
while(h2 != NULL){
h1 = h1->next;
h2 = h2->next;
}
h1->next = h1->next->next; // the one after the h1 need to be removed
return head;
}
};
20. Valid Parentheses
题意: 给定一个包含三种括号的字符串,判断是否是合法的括号嵌套。
题解:模拟匹配过程即可
class Solution {
public:
bool isValid(string s) {
stackS;
if(s.empty()) return true;
if(s[0] != '(' && s[0] != '[' && s[0] != '{') return false;
S.push(s[0]);
for(int i = 1;i < s.length(); ++i){
if(s[i] == ')' || s[i] == ']' || s[i] == '}'){
if(S.empty()) return false;
if(s[i] == ')' && S.top() != '(' || s[i] == ']' && S.top() != '[' || s[i] == '}' && S.top() != '{') return false;
S.pop();
}else{
S.push(s[i]);
}
}
return S.empty();
}
};
21. Merge Two Sorted Lists
题意:将两个有序链表合并(从小到大)
题解:简单题
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1 == NULL) return l2;
if(l2 == NULL) return l1;
ListNode * listHead, *p;
listHead = p = new ListNode(0);
while(l1 && l2){
if(l1->val < l2->val) {
p->next = l1;
l1 = l1->next;
}else{
p->next = l2;
l2 = l2->next;
}
p = p->next;
}
if(l1 != NULL) p->next = l1;
else p->next = l2;
p = listHead->next;
delete listHead;
return p;
}
};
22. Generate Parentheses
题意:给定一个n,生成所有可能长度为n的嵌套括号组合
题解:可以用dfs求解。不过我这里用bfs,然后用位标记法代替字符串以节省内存。
class Solution {
public:
vector generateParenthesis(int n) {
vector ans;
n *= 2;
//1是右括号,0为左括号
cout<<'yes';
queue > Q;
Q.push(make_pair(0LL,0x01));
while(!Q.empty()){
pair p = Q.front();
Q.pop();
if(p.second == n){
ans.push_back(genString(p.first,n));
continue;
}
//添加左括号
char leftParenthesisNum = p.second - bitCount(p.first);
//cout< p.second - leftParenthesisNum)Q.push(make_pair(p.first | (1 << p.second), p.second + 1));1<>1) &0x5555555555555555) ;
n = (n &0x3333333333333333) + ((n >>2) &0x3333333333333333) ;
n = (n &0x0f0f0f0f0f0f0f0f) + ((n >>4) &0x0f0f0f0f0f0f0f0f) ;
n = (n &0x00ff00ff00ff00ff) + ((n >>8) &0x00ff00ff00ff00ff) ;
n = (n &0x0000ffff0000ffff) + ((n >>16) &0x0000ffff0000ffff) ;
n = (n &0x00000000ffffffff) + ((n >>32) &0x00000000ffffffff) ;
return char(n&0xff) ;
}
string genString(long long parentheses, int n){
string s = "";
for(int i = 0;i < n; ++i){
if((parentheses>>i) & 1) s+=')';
else s+= '(';
}
return s;
}
};
23. Merge k Sorted Lists
题意:将k个有序链表合并成一个
题解:用优先队列即可。主义c++优先队列是大顶堆
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
struct cmp
{
bool operator()(ListNode * &a,ListNode * &b){
return a->val > b->val;
}
};
ListNode* mergeKLists(vector& lists) {
priority_queue, cmp>Q;
ListNode *head,*p;
head = p = new ListNode(0);
for(size_t i = 0;i < lists.size(); ++i)
if(lists[i] != NULL) Q.push(lists[i]);
while(!Q.empty()){
ListNode *node = Q.top();
Q.pop();
p->next = node;
p = p->next;
if(node->next) Q.push(node->next);
}
p = head->next;
delete head;
return p;
}
};
24. Swap Nodes in Pairs
题意:交换链表中的连续的对(交换(1,2),(3,4 ) 以此类推)。
题解:模拟即可
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
int index = 0;
ListNode* l1, *l2, *tmp = new ListNode(0);
tmp->next = head;
head = tmp;
while(tmp){
if(!tmp->next) break;
l1 = tmp->next;
if(!l1->next) break;
l2 = l1->next;
tmp->next = l2;
l1->next = l2->next;
l2->next = l1;
tmp = l1;
}
tmp = head->next;
delete head;
return tmp;
}
};
31. Next Permutation
题意:给定一个数组,求排列中,下一个排列。
题解:注意可能有重复数字。模仿我们人计算过程。首先判断是否为最后一个排列,是就输出翻转后的结果。不然,将排列分成三部分(我们假设长度大于等于2,第一部分可为空)[a, b, c](a,c为序列,b为一个数字),其中c呈倒序,而且b小于c的第一个数字,那么下一个排列将为,a不变,将b和c最后一个大于它的数交换,然后将c翻转。(这个是很容易想到的,实现也简单)
class Solution {
public:
int decreaseLen(vector & nums){
int len = 1;
for(int i = nums.size() - 2; i >= 0 && nums[i] >= nums[i + 1] ;--i,++len);
return len;
}
void nextPermutation(vector& nums) {
if(nums.size() <= 1) return;
int len = decreaseLen(nums);
if(len == nums.size()){reverse(nums.begin(),nums.end());return;}
int id = nums.size() - len - 1;
int pos = id;
//找到后面最后一个大于它的数
for(int i = id + 1;nums[id] < nums[i] && i <= nums.size(); ++i)++pos;
pos = min(pos,int(nums.size() - 1) );
swap(nums[id],nums[pos]);
reverse(nums.begin() + id + 1,nums.end());
}
};
32. Longest Valid Parentheses
题意:给一个包含'('和')'的括号串,求最长的合法配对子括号串长度。
题解:动态规划,dp[i]表示以第i个位置作为结束时的最长串长度。
当s[i] == '('时,dp[i] = 0.
当s[i] == ')'时候,看前面那个配对到哪里,然后这个括号能否配对,如果不能,也是dp[i] = 0,能的话递推。
class Solution {
public:
int longestValidParentheses(string s) {
int n = s.length();
if(n <= 1) return 0;
vector dp(n);
int ans = 0;
dp[0] = 0;
for(int i = 1; i < n; ++i){
if(s[i] == '(') dp[i] = 0;
else{
if(s[i - 1] == '('){
if(i == 1) dp[i] = 2;
else dp[i] = dp[i - 2] + 2;
}else{
int idx = i - 1 - dp[i - 1];
if(idx >= 0 && s[idx] == '('){
if(idx > 0)
dp[i] = dp[i - 1] + dp[idx - 1] + 2;
else dp[i] = dp[i - 1] + 2;
}else dp[i] = 0;
}
}
ans = max(ans,dp[i]);
}
return ans;
}
};
在s前面补一个'('可以少个两个判断条件。
class Solution {
public:
int longestValidParentheses(string s) {
int n = s.length();
if(n <= 1) return 0;
s = ')' + s; ++n;
vector dp(n);
int ans = 0;
dp[0] = dp[1] = 0;
for(int i = 2; i < n; ++i){
if(s[i] == '(') dp[i] = 0;
else{
if(s[i - 1] == '('){
if(i == 1) dp[i] = 2;
else dp[i] = dp[i - 2] + 2;
}else{
int idx = i - 1 - dp[i - 1];
if(s[idx] == '('){
dp[i] = dp[i - 1] + dp[idx - 1] + 2;
}else dp[i] = 0;
}
}
ans = max(ans,dp[i]);
}
return ans;
}
};
37. Sudoku Solver
题意:求解一个数独
题解:深度搜索和剪枝
‘class Solution {
public:
void solveSudoku(vector>& board) {
vector >rowVis(9,vector(9,false));
vector >colVis(9,vector(9,false));
vector >lat (9,vector(9,false));
for(int i = 0;i < 9; ++i)
for(int j = 0;j < 9; ++j){
if(board[i][j] == '.') continue;
int x = board[i][j] - '1';
rowVis[i][x] = colVis[j][x] = lat[(i / 3) * 3 + j / 3][x] = true;
}
dfs(board,rowVis,colVis,lat,0,0);
}
bool dfs(vector>& board,vector >& rowVis,vector >& colVis,vector >&lat,int x,int y){
if(y == 9) y = 0,++x;
if(x == 9) return true;
if(board[x][y] == '.'){
for(int i = 0; i < 9; ++i){
if(rowVis[x][i] || colVis[y][i] || lat[(x / 3) * 3 + y / 3][i]) continue;
board[x][y] = i + '1';
rowVis[x][i] = colVis[y][i] = lat[(x / 3) * 3 + y / 3][i] = true;
if(dfs(board,rowVis,colVis,lat,x,y + 1)) return true;
board[x][y] = '.';
rowVis[x][i] = colVis[y][i] = lat[(x / 3) * 3 + y / 3][i] = false;
}
}else
return dfs(board,rowVis,colVis,lat,x,y + 1);
return false;
}
};
41. First Missing Positive
class Solution {
//ye keyi yong zhengfu biaoshi zhuangtai jiang ta fangdao ziji de weizhi shang
public:
int firstMissingPositive(vector& nums) {
int n = nums.size();
for(int i = 0; i < n; ++ i)
while(nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i])
swap(nums[i], nums[nums[i] - 1]);
for(int i = 0; i < n; ++ i)
if(nums[i] != i + 1)
return i + 1;
return n + 1;
}
};
42. Trapping Rain Water
题意:给定一个一维的柱形图,把它看成一容器,问最多能装多少雨水。
题解:计算出左边,右边的最高点即可。
class Solution {
public:
int trap(vector& height) {
int n = height.size();
if(n <= 1) return 0;
vector left(n,0),right(n,0);
left[0] = height[0];
right[n - 1] = height[n - 1];
for(int i = 1;i < n; ++i){
left[i] = max(left[i - 1],height[i]);
right[n - i - 1] = max(height[n - i - 1],right[n - i]);
}
int ans = 0;
for(int i = 0;i < n; ++i){
ans += min(left[i],right[i]) - height[i];
}
return ans;
}
};
44.Wildcard Matching
题意:给一个字符串和一个模式串,问能不能匹配。其中模式串中字符'?'能匹配任意一个字符,'*'能匹配任意多个字符(可以是0个)。
题解:dp(O(n^2))或双指针
动态规划思路:dp[i][j]表示模式串0到i - 1能不能匹配源串的0~j - 1的子串。
初始dp[0][0] = true, dp[i][0] = true,当模式串前缀0 到i - 1为'*'
分别分三种情况进行转移:
p[i - 1] == '?' 时, dp[i][j] = dp[i - 1][j - 1];
p[i - 1] 为字母 , dp[i][j] = dp[i - 1][j - 1] && s[j - 1] == p[i - 1]
p[i - 1] == '*', dp[i][j] = dp[i][j - 1] || dp[i - 1][j] (即*至少匹配一个字符和不匹配两种情况)
然后由于dp[i]只和dp[i - 1]有关系,可以利用滚动数组,利用一维数组就可以搞定。
class Solution {
public:
bool isMatch(string s, string p) {
int n,m;
n = s.length();
m = p.length();
if(m == 0 && n == 0) return true;
vector dp(n + 1,false);
bool curstate,prestate;
dp[0] = true;
for(int j = 1;j <= m; ++j){
prestate = dp[0];
dp[0] = dp[0] && p[j - 1] == '*';
for(int i = 1;i <= n; ++i){
curstate = dp[i];
if(p[j - 1] == '*')
dp[i] = dp[i - 1] || dp[i];
else
dp[i] = prestate && (s[i - 1] == p[j - 1] || p[j - 1] == '?');
prestate = curstate;
}
}
return dp[n];
}
};
看了题解发现还有双指针的写法:
class Solution {
public:
bool isMatch(string s, string p) {
int slen = s.size(), plen = p.size(), i, j, iStar=-1, jStar=-1;
for(i=0,j=0 ; i=0)
{ // met a '*' before, then do traceback
i = iStar++;
j = jStar;
}
else return false; // otherwise fail
}
}
}
while(p[j]=='*') ++j;
return j==plen;
}
};
45. Jump Game II
题意:给定一个数组,其中每一个位置的数值表示从这个位置可以往后跳至多多少个位置。问从第开头位置开始,至少多少次可以跳到末尾
题解:简单的BFS。由于是至多可以往后跳多少个位置,所以我们只需要标记最后一个进队的元素下标(或者是第一个还没进队的)。因为进队肯定是从前往后的。每个元素至多进一次,出一次,所以是O(n)时间复杂度
class Solution {
public:
int jump(vector& nums) {
int n = nums.size();
if(n <= 1) return 0;
queue > Q;
Q.push(make_pair(0,0));
vector vis(n, false);
int index = 1;
while(!Q.empty()){
pair u = Q.front();
Q.pop();
int cur = u.first;
int step = u.second;
if(cur == n - 1) return step;
for(;index <= nums[cur] + cur && index < n; ++index){
Q.push(make_pair(index,step + 1));
}
}
return 0;
}
};
51. N-Queens
题意:返回n皇后所有合法状态,以字符串的形式,Q为皇后,.为空
题解:dfs枚举
class Solution {
public:
vector> solveNQueens(int n) {
vector> res;
if(n == 0) return res;
vector colVis(n,false);
vector hypVis(2*n - 1,false);
vector rhypVis(2*n -1,false);
vector tmp;
dfs(n,0,colVis,hypVis,rhypVis,tmp,res);
return res;
}
void dfs(const int n,int row,vector &colVis,vector &hypVis,vector &rhypVis,vector &pos,vector> &res){
if(row == n){
vector tmp = vector();
for(int i = 0;i < n; ++i){
string s(n,'.');
s[pos[i]] = 'Q';
tmp.push_back(s);
}
res.push_back(tmp);
}else{
for(int i = 0;i < n; ++i){
if(colVis[i] || hypVis[row - i + n - 1] || rhypVis[row + i]) continue;
colVis[i] = hypVis[row - i + n - 1] = rhypVis[row + i] = true;
pos.push_back(i);
dfs(n, row + 1,colVis,hypVis,rhypVis,pos,res);
pos.pop_back();
colVis[i] = hypVis[row - i + n - 1] = rhypVis[row + i] = false;
}
}
}
};
52. N-Queens II
题意:和51一样,只不过是返回数目
题解:只要计算数目,改下51的代码即可
class Solution {
public:
int totalNQueens(int n) {
if(n <= 1) return 1;
int res = 0;
vector colVis(n,false);
vector hypVis(2*n - 1,false);
vector rhypVis(2*n -1,false);
vector tmp;
dfs(n,0,colVis,hypVis,rhypVis,tmp,res);
return res;
}
void dfs(const int n,int row,vector &colVis,vector &hypVis,vector &rhypVis,vector &pos, int & res){
if(row == n){
++res;
}else{
for(int i = 0;i < n; ++i){
if(colVis[i] || hypVis[row - i + n - 1] || rhypVis[row + i]) continue;
colVis[i] = hypVis[row - i + n - 1] = rhypVis[row + i] = true;
pos.push_back(i);
dfs(n, row + 1,colVis,hypVis,rhypVis,pos,res);
pos.pop_back();
colVis[i] = hypVis[row - i + n - 1] = rhypVis[row + i] = false;
}
}
}
};
57. Insert Interval
题意:给定一个互不相交区间的数组,还有一个区间。将区间合并后输出
题解:模拟手动合并过程,不知道为什么是hard的。
/**
* Definition for an interval.
* struct Interval {
* int start;
* int end;
* Interval() : start(0), end(0) {}
* Interval(int s, int e) : start(s), end(e) {}
* };
*/
class Solution {
public:
vector insert(vector& intervals, Interval newInterval) {
int s = 0,n = intervals.size();
vector ans;
for(s = 0;s < n && intervals[s].end < newInterval.start;++s){
Interval t(intervals[s].start,intervals[s].end);
ans.push_back(t);
}
int e = s ;
while(e < n && intervals[e].start <= newInterval.end){
++e;
}
if(e == s) {
ans.push_back(newInterval);
}
else{
Interval t(min(newInterval.start,intervals[s].start), max(newInterval.end,intervals[e-1].end));
ans.push_back(t);
}
for(int i = e;i < n; ++i){
Interval t(intervals[i].start,intervals[i].end);
ans.push_back(t);
}
return ans;
}
};
65.Valid Number
题意:给一个字符串,判断是不是数字
题解:用状态机异常简洁
列出合法情况,考虑每一种情况的转移矩阵转移矩阵
1.空格+ 数字 +空格
2.空格+ 点 + 数字 +空格
3.空格+ 符号 + 数字 + 空格
4.空格 + 符号 + 点 + 数字 +空格
5.空格 + (1, 2, 3, 4) + e + (1, 2, 3, 4) +空格
有限状态机的状态转移过程:
状态-1:非法状态
状态0:空格串
状态1:数字(非指数,没有小数点)
状态2:带小数点的数字(非指数,最后是小数点)
状态3:符号
状态4:带小数点数字
状态5:指数状态(最后字符为E)
状态6:指数带符号(最后一个为符号)
状态7:指数数字
最后为数字的终止状态有:1,4,7,8
状态8:数字合法结束状态(只接受空格)
起始为0:
当输入空格时,状态仍为0,
输入为符号时,状态转为3,3的转换和0是一样的,除了不能再接受符号,故在0的状态的基础上,把接受符号置为-1;
当输入为数字时,状态转为1, 状态1的转换在于无法再接受符号,可以接受空格,数字,点,指数;状态1为合法的结束状态;
当输入为点时,状态转为2,状态2必须再接受数字,接受其他均为非法;
当输入为指数时,非法;
状态1:
接受数字时仍转为状态1,
接受点时,转为状态4,可以接受空格,数字,指数,状态4为合法的结束状态,
接受指数时,转为状态5,可以接受符号,数字,不能再接受点,因为指数必须为整数,而且必须再接受数字;
状态2:
接受数字转为状态4;
状态3:
和0一样,只是不能接受符号;
状态4:
接受空格,合法接受;
接受数字,仍为状态4;
接受指数,转为状态5,
状态5:
接受符号,转为状态6,状态6和状态5一样,只是不能再接受符号,
接受数字,转为状态7,状态7只能接受空格或数字;状态7为合法的结束状态;
状态6:
只能接受数字,转为状态7;
状态7:
接受空格,转为状态8,状态7为合法的结束状态;
接受数字,仍为状态7;
状态8:
接受空格,转为状态8,状态8为合法的结束状态;
class Solution
{
public:
bool isNumber(string str)
{
enum InputType
{
INVALID, // 0
SPACE, // 1
SIGN, // 2
DIGIT, // 3
DOT, // 4
EXPONENT, // 5
//NUM_INPUTS // 6
};
int transitionTable[][NUM_INPUTS] =
{
-1, 0, 3, 1, 2, -1, // next states for state 0
-1, 8, -1, 1, 4, 5, // next states for state 1
-1, -1, -1, 4, -1, -1, // next states for state 2
-1, -1, -1, 1, 2, -1, // next states for state 3
-1, 8, -1, 4, -1, 5, // next states for state 4
-1, -1, 6, 7, -1, -1, // next states for state 5
-1, -1, -1, 7, -1, -1, // next states for state 6
-1, 8, -1, 7, -1, -1, // next states for state 7
-1, 8, -1, -1, -1, -1, // next states for state 8
};
int state = 0;
string::iterator s = str.begin();
while (s != str.end())
{
InputType inputType = INVALID;
if (isspace(*s))
inputType = SPACE;
else if (*s == '+' || *s == '-')
inputType = SIGN;
else if (isdigit(*s))
inputType = DIGIT;
else if (*s == '.')
inputType = DOT;
else if (*s == 'e' || *s == 'E')
inputType = EXPONENT;
state = transitionTable[state][inputType];
if (state == -1)
return false;
else
++s;
}
return state == 1 || state == 4 || state == 7 || state == 8;
}
};
68. Text Justification
题意:把一个集合的单词按照每行L个字符放,每行要两端对齐,如果空格不能均匀分布在所有间隔中,那么左边的空格要多于右边的空格,最后一行靠左对齐。
题意:模拟即可,不难。
class Solution {
public:
vector fullJustify(vector& words, int maxWidth) {
vector ans;
for(int i = 0;i < words.size(); ++i){
int len = words[i].length(), totw = 1;
int j = i;
while(j + 1 < words.size() && words[j + 1].length() + len + 1 <= maxWidth){
++j; ++totw;
len += words[j].length() + 1;
}
string s = "";
if(totw == 1){
s = words[i] + string(maxWidth - words[i].length(),' ');
}else{
if(j + 1 == words.size()){ //last line
s = words[i];
for(int k = i + 1; k <= j; ++k){
s = s + ' ' + words[k];
}
s += string(maxWidth - len,' ');
}else{
int totSpace = maxWidth - len + totw - 1;
int ave = totSpace / (totw - 1);
int remain = totSpace - ave * (totw - 1);
s = words[i];
for(int k = i + 1; k <= j; ++k){
s = s + string(ave + (remain > 0),' ') + words[k];
--remain;
}
}
}
i = j;
ans.push_back(s);
}
return ans;
}
};
69. Sqrt(x)
题意:给定给一个整数,对它开方
题解:二分或任意或其他优化算法
class Solution {
public:
int mySqrt(int x) {
//可以用牛顿迭代求解,这个一般优化方法也可以解的,这里用二分足够了
int left = 1,right = x;
if(x<=1)return x;
int mid, ans = 0;
while(left <= right){
mid = (right - left) / 2 + left;
if(mid <= x / mid){
ans = mid;
left = mid + 1;
}else{
right = mid - 1;
}
}
return ans;
}
};
72. Edit Distance
题意:编辑距离,允许插入,删除,修改一个字符,问最少经过多少次操作,将串1变成串2
题解:dp[i][j]表示串1的前缀1~i变成串2的1~j需要多少次操作,答案是dp[len1][len2];
可以利用滚动数组,将空间变成一维。
class Solution {
public:
int minDistance(string word1, string word2) {
int n = word1.length();
int m = word2.length();
if(n == 0 || m == 0) return max(n,m);
vector > dp(n + 1,vector(m + 1,0));
for(int i = 1;i <= n; ++i) dp[i][0] = i;
for(int i = 1;i <= m; ++i) dp[0][i] = i;
for(int i = 1;i <= n; ++i){
for(int j = 1;j <= m; ++j){
if(word1[i -1] == word2[j - 1]){
dp[i][j] = dp[i - 1][j - 1];
}else{
dp[i][j] = 1 + min(dp[i - 1][j - 1],min(dp[i][j - 1],dp[i - 1][j]));
}
}
}
return dp[n][m];
}
};
滚动数组:
class Solution {
public:
int minDistance(string word1, string word2) {
int n = word1.length();
int m = word2.length();
if(n == 0 || m == 0) return max(n,m);
vector dp(m + 1,0);
for(int i = 0; i <= m; ++i) dp[i] = i;
for(int i = 1;i <= n; ++i){
int pre = dp[0];
dp[0] = i;
for(int j = 1;j <= m; ++j){
int cur = dp[j];
if(word1[i -1] == word2[j - 1]){
dp[j] = pre;
}else{
dp[j] = 1 + min(pre,min(dp[j - 1],dp[j]));
}
pre = cur;
}
}
return dp[m];
}
};
76. Minimum Window Substring
题意:给定两个字符串,s,t问s中含有t所有字符的最小子段。(保证唯一)
题解:计数就好了。没有什么难度
class Solution {
public:
string minWindow(string s, string t) {
if(t.size() == 0 || s.size() == 0) return "";
int vis[256] = {0};
int contain[256] = {0};
int left = 0,right = 0,pos = INT_MAX,hasvis = 0,len = INT_MAX;
for(int i = 0;i < t.size(); ++i) ++contain[t[i] + 127];
while(left < s.size()){
if(hasvis == t.size()){
++vis[s[left++] + 127];
}
while(left < s.size() && hasvis < t.size()){
if( vis[s[left] + 127]++ < contain[s[left] + 127]) ++hasvis;
++left;
}
while(right < left && vis[s[right] + 127] - 1 >= contain[s[right] + 127]){
--vis[s[right] + 127];
++right;
}
if(hasvis != t.size()) return "";
if(left - right < len){
len = left - right ;
pos = right;
}
}
return s.substr(pos,len);
}
};
78. Subsets
题意:给定一个数字各不相同的数字的集合(数组),给出它的所有子集
题解:因为数字都不相同,那就很容易了,从子集大小慢慢一个个网上加,以递增的顺序保证不重复即可。
class Solution {
public:
vector> subsets(vector& nums) {
queue> Q;
//sort(nums.begin() , nums.end());
vector> ans;
Q.push(vector());
while(!Q.empty()){
vector q = Q.front(); Q.pop();
ans.push_back(q);
if(q.size() >= nums.size()) continue;
for(int i = 0;i < nums.size(); ++i){
if(q.size() && nums[i] <= q[q.size() - 1]) continue;
vectortmp(q);
tmp.push_back(nums[i]);
Q.push(tmp);
}
}
return ans;
}
};
84. Largest Rectangle in Histogram
题目:如题
题解:见85。单调栈dp。计算的是以当前点为最低点的最大长方形。
class Solution {
public:
int largestRectangleArea(vector &height) {
height.push_back(0);
stack s;
int maxSize = height[0];
for(int i = 0; i < height.size(); ){
if(s.empty() || height[i] >= height[s.top()]){
s.push(i++);
}
else{
int temp = height[s.top()];
s.pop();
maxSize = max(maxSize, temp * (s.empty() ? i : i - 1 - s.top()));
}
}
return maxSize;
}
};
85. Maximal Rectangle
题意:给定一个0-1矩阵,问其中最大的都为1的矩形面积。
题解:动态规划,先枚举矩的底边所在的行,然后对于每个点,记录这个点连续1的高度。问题变成了在一个条状图中寻找最大矩形(84题)。假设这个条状图为height数组。这个可以用动态规划+单调栈优化解决。用dp[i]表示以以height[i]为最高度且包含这一列的最大矩形面积,则只需要向左边和找到第一个比它低的即可,但是这样子太慢,需要用单调栈优化。以下有两种写法,第二中理解简单,第一种简洁。第二种写法是,分别计算左边的面积和右边的面积,加起来。第一种则一次算完。注意到S.top出栈的时候,我们就可以知道左边第一个比它低的是栈的下一个元素,而右边比它低的是当前元素。
class Solution {
public:
int maximalRectangle(vector > &matrix) {
int n = matrix.size();
if(n == 0) return 0;
int m = matrix[0].size();
vector height(m + 1, 0);
int maxRec = 0;
for(int i = 0; i < n; ++i){
for(int j = 0; j < m; ++j){
if(matrix[i][j] == '0')
height[j] = 0;
else
++height[j];
}
maxRec = max(maxRec, largestRectangleArea2(height,m));
//maxec = max(mexRec,maxRec, largestRectangleArea(height))
}
return maxRec;
}
int largestRectangleArea(vector &height) {
stack s;
int maxSize = 0;
for(int i = 0; i < height.size(); i++){
if(s.empty() || height[i] >= height[s.top()]){
s.push(i);
}
else{
int temp = height[s.top()];
s.pop();
maxSize = max(maxSize, temp * (s.empty() ? i : i - 1 - s.top()));
i--;
}
}
return maxSize;
}
//more easier to understand
int largestRectangleArea2(vector &height , int n){
stack S;
int maxRec;
vector tmp(height.size(),0);
tmp[0] = maxRec = height[0];
S.push(0);
for(int i = 1;i < n; ++i){
while(!S.empty() && height[S.top()] >= height[i]) S.pop();
if(S.empty()) tmp[i] = height[i] * (i + 1);
else tmp[i] = height[i] * (i - S.top());
S.push(i);
}
S = stack();
S.push(n - 1);
maxRec = max(maxRec,tmp[n- 1]);
for(int i = n - 2; i >= 0 ; --i){
while(!S.empty() && height[S.top()] >= height[i]) S.pop();
if(S.empty()) maxRec = max(maxRec, tmp[i] + height[i] * (n - i - 1));
else maxRec = max(maxRec,tmp[i] + height[i] * (S.top() - i - 1));
S.push(i);
}
return maxRec;
}
};
97. Interleaving String
题意:给三个串,问第三个串是不是前两个串的交叉得到。
题解:动态规划,dp[i][j]表示第一个的子串1~i和第二个串1~j交叉能否得到第三个串的子串1~i+j。然后用滚动数组,可以变成一维的空间。
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
int n,m;
n = s1.size();
m = s2.size();
if(n == 0) return s2 == s3;
if(m == 0) return s1 == s3;
if(m + n != s3.size()) return false;
vector dp(m + 1, 0);
dp[0] = true;
for(int i = 0;i < m; ++i) dp[i + 1] = dp[i] && (s2[i] == s3[i]);
for(int i = 1;i <= n; ++i){
dp[0] = dp[0] && (s1[i - 1] == s3[i - 1]);
for(int j = 1;j <= m; ++j){
dp[j] = ((s1[i - 1] == s3[i + j - 1]) && dp[j]) ||
((s2[j - 1] == s3[i + j - 1]) && dp[j - 1]);
}
}
return dp[m];
}
};
99. Recover Binary Search Tree
题意:给定一个二叉排序树,其中有两个节点的值交换了。目的是把这棵树恢复(但是只能用O(1)的辅助空间
题解:对树进行中序遍历,注意到,第一个点比它的后继大,第二个比它前驱小。找出这两个点然后交换即可。中序遍历用morris traversal。
/**
* 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 *a,*b;
void recoverTree(TreeNode* root){
a = b = NULL;
inorderMorrisTraversal(root);
int tmp = a->val;
a->val = b->val;
b->val = tmp;
}
void inorderMorrisTraversal(TreeNode *root) {
TreeNode *cur = root, *prev = NULL,*c = NULL, *p = NULL;
while (cur != NULL)
{
if (cur->left == NULL) .
{
p = c;
c = cur;
cur = cur->right;
}
else
{
prev = cur->left;
while (prev->right != NULL && prev->right != cur)
prev = prev->right;
if (prev->right == NULL)
{
prev->right = cur;
cur = cur->left;
}
else
{
prev->right = NULL;
p = c;
c = cur;
cur = cur->right;
}
}
if(p != NULL){
if(a == NULL && p->val >= c->val)
a = p;
if(a != NULL && p->val >= c->val)
b = c;
}
}
}
};
115. Distinct Subsequences
题意:给定两个字符串,问第一个串中,有多少个和第二个串匹配的子序列
题解:动态规划,dp[i][j] 表示第一个串第0到i位置有多少个第二个串0到j这样的子序列。转移方程是显然的。
class Solution {
public:
int numDistinct(string s, string t) {
int **dp= new int* [s.length() + 1];
for(int i = 0;i <= s.length(); ++i)
dp[i] = new int[t.length() + 1];
for(int i = 0;i <= t.length(); ++i) dp[0][i] = 0;
for(int i = 1;i <= s.length(); ++i)
for(int j = 1;j <= t.length(); ++j){
dp[i][j] = dp[i - 1][j];
if(j == 1){
dp[i][j] += (s[i - 1] == t[j - 1]);
}else{
if(s[i - 1] == t[j - 1]) dp[i][j] += dp[i - 1][j - 1];
}
}
return dp[s.length()][t.length()];
}
};
或者
class Solution {
public:
int numDistinct(string s, string t) {
if(s == "") return 0;
int **dp= new int* [s.length()];
for(int i = 0;i < s.length(); ++i)
dp[i] = new int[t.length()];
for(int i = 0;i < t.length(); ++i) dp[0][i] = 0;
dp[0][0] = (s[0] == t[0]);
for(int i = 1;i < s.length(); ++i)
for(int j = 0;j < t.length(); ++j){
dp[i][j] = dp[i - 1][j];
if(j == 0){
dp[i][j] += (s[i] == t[j]);
}else{
if(s[i] == t[j]) dp[i][j] += dp[i - 1][j - 1];
}
}
return dp[s.length() - 1][t.length() - 1];
}
};
123. Best Time to Buy and Sell Stock III
题意:给定一个数组,表示每一天的股票价格。你只能进行至多两次交易(买入卖出,交易时间不想交),问最大的获益。
题解:动态规划。先遍历一次,用dp[i]算出第一次卖出时间点为i时的最大获利。然后枚举第二次卖出的时间点即可,再进行一次和前面一样的操作。多次也是一样的。具体见代码。
class Solution {
public:
int maxProfit(vector& prices) {
int n = prices.size();
if(n <= 1) return 0;
vector dp(n),dp2(n);
int minimum = prices[0];
dp[0] = 0;
int ans = 0;
for(int i = 1;i < n; ++i){
minimum = min(minimum,prices[i]);
dp[i] = prices[i] - minimum;
ans = max(ans,dp[i]);
}
int maximum ,tmp;
maximum = tmp = -prices[0];
for(int i = 1;i < n; ++i) {
maximum = max(maximum, dp[i - 1]);
tmp = max(tmp, maximum - prices[i]);
ans = max(ans,prices[i] + tmp);
}
return ans;
}
};
124. 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:
int maxPathSum(TreeNode *root) {
int maxPath = INT_MIN;
dfsMaxPath(root, maxPath);
return maxPath;
}
int dfsMaxPath(TreeNode *root, int &maxPath) {
if (!root) return 0;
int l = max(0, dfsMaxPath(root->left, maxPath));
int r = max(0, dfsMaxPath(root->right, maxPath));
maxPath = max(maxPath, l + r + root->val);
return root->val + max(l, r);
}
};
126. Word Ladder II
题意:给定一个begin_word,一个end_word。以及一个wordList字符串数组,问所有从begin_word转换到end_word的最短序列。每次转换只能改变一个字符,改变的字符串必须在wordList中
题解:如果不是要输出所有结果而只求最少的转换次数,就是直接建图,然后bfs完事。而现在需要保存所有最短转换序列。我们只需要在bfs的时候记录下每个节点的遍历深度,然后再用一次dfs按照可能的边走一遍即可(用深度判断边是不是应该加入)。
class Solution {
public:
bool is_connected(const string &a,const string &b){
int diff = 0;
for(int i = 0;i < a.length(); ++i){
if(a[i] != b[i]) ++diff;
if(diff > 1) break;
}
return diff == 1;
}
void dfs(int cur_node, const int &end_node, const vector& wordList, const vector> &graph, const vector &depth, vector &cur_list, vector> &ans){
cur_list.push_back(wordList[cur_node]);
if(cur_node == end_node){
ans.push_back(vector(cur_list));
}else{
for(int i = 0;i < graph[cur_node].size(); ++i) {
int x = graph[cur_node][i];
if(depth[cur_node] - 1 == depth[x]){
dfs(x, end_node, wordList, graph, depth, cur_list, ans);
}
}
}
cur_list.pop_back();
}
vector> findLadders(string beginWord, string endWord, vector& wordList) {
int n = wordList.size();
int begin_node = -1, end_node = -1;
for(int i = 0;i < wordList.size(); ++i){
if(wordList[i] == beginWord) begin_node = i;
else if(wordList[i] == endWord) end_node = i;
if(begin_node != -1 and end_node != -1) break;
}
//end_word不在wordList中,自然不可能转换到
if(end_node == -1){
return {};
}
if(begin_node == -1){
begin_node = n++;
wordList.push_back(beginWord);
}
vector> graph(n, vector());
vector vis(n, 0);
//建图,可以用TrieTree加速
for(int i = 0;i < n; ++i){
for(int j = i + 1;j < n; ++j){
if(is_connected(wordList[i], wordList[j])){
graph[i].push_back(j);
graph[j].push_back(i);
}
}
}
//bfs
int min_step = INT_MAX;
queue> Q;
vector depth(n, 0);
//正向BFS也一样。只不过一开始写这样,后面懒得改了
Q.push(make_pair(end_node, 1));
vis[end_node] = true;
while(!Q.empty()){
pair u = Q.front(); Q.pop();
int x = u.first;
int d = u.second;
if(d > min_step) continue;
if(x == begin_node){
min_step = d;
}
depth[x] = d;
for(int i = 0;i < graph[x].size(); ++i){
int v = graph[x][i];
if(vis[v]) continue;
vis[v] = true;
Q.push(make_pair(v, d + 1));
}
}
if(min_step > n) return {};
//dfs 按照深度找边
vector> ans;
vector cur_list;
dfs(begin_node, end_node, wordList, graph, depth, cur_list, ans);
return ans;
}
};
128. Longest Consecutive Sequence
题意:给定一个无序数组,求最长的连续数字长度
题解:用hash_set标记即可。
class Solution {
public:
int longestConsecutive(vector& nums) {
if(nums.size() == 0) return 0;
unordered_set S;
int ans = 0;
for(int i = 0;i < nums.size(); ++i) S.insert(nums[i]);
for(int i = 0;i < nums.size(); ++i){
if(S.erase(nums[i])){
int count = 1;
int val = nums[i];
while(S.erase(val - 1)) ++count,--val;
val = nums[i];
while(S.erase(val + 1)) ++count,++val;
ans = max(count,ans);
}
}
return ans;
}
};
132. Palindrome Partitioning II
题意:给定一个字符串,问至少多少次分割,可以把它分成一些回文串
题解: dp[i]表示第i个位置截止,经过多少次可以分成回文串。通常我们需要记录i到j是不是回文串,会用到O(n^2)的空间,但是如果枚举回文串的中间位置的话,就不需要任何记录了。
class Solution {
public:
int minCut(string s) {
int n = s.size();
if(n <= 1) return 0;
vector minCut(n + 1, 0);
for (int i = 0; i <= n; i++) minCut[i] = i - 1;
for (int i = 0; i < n; ++i) {
for (int j = 0; i - j >= 0 && i + j < n && s[i - j]==s[i + j] ;++j) // odd length palindrome
minCut[i+j+1] = min(minCut[i + j + 1],1 + minCut[i - j]);
for (int j = 1; i - j + 1 >= 0 && i + j < n && s[i - j + 1] == s[i + j]; ++j) // even length palindrome
minCut[i + j + 1] = min(minCut[i + j + 1],1 + minCut[i - j + 1]);
}
return minCut[n];
}
};
135. Candy
题意:有一些小朋友排成一列,每个人有个rating。现在要分给小朋友糖。rating比旁边大的要分更多,最少得一个。问最少需要多少糖才够分
题解:贪心。从rating小的开始分。如果旁边有人已经分有了,那么如果数值不是一样大,那么需要比他多一个。如果两边都一样大,那么分一个。
class Solution {
public:
struct cmp{
bool operator()(pair&a,pair&b){
return a.first < b.first;
}
};
int candy(vector& ratings) {
vector>v;
vector c(ratings.size() + 2,0);
if(ratings.size() <= 1) return ratings.size();
for(int i = 0;i < ratings.size(); ++i){
v.push_back(make_pair(ratings[i],i + 1));
}
sort(v.begin(),v.end(),cmp());
for(int i = 0;i < v.size(); ++i){
pair u = v[i];
int pos = u.second;
c[u.second] = 1;
if(u.second > 1 && ratings[u.second - 2] < ratings[u.second - 1]) c[u.second] = c[u.second - 1] + 1;
if(u.second <= ratings.size() && ratings[u.second] < ratings[u.second - 1]) c[u.second] = max(c[u.second],c[u.second + 1] + 1);
}
int ans = 0;
for(int i = 0;i < c.size(); ++i) ans += c[i];
return ans;
}
};
140. Word Break II
题意:给定一个串和一个字典,输出这个串按字典分割的所有可能结果
题解:记忆搜索,动态规划
class Solution {
public:
map> maps;
vector wb(string s, set& wordDict) {
if(maps.count(s)) return maps[s];
vector res;
int n = s.length();
if(n == 0) return res;
//judge if s can be break
for(int j = n - 1; j >= 0; j--){
if(wordDict.count(s.substr(j)))
break;
else if(j == 0)
return res;
}
for(int i = 1; i < n; ++i)
{
if(wordDict.count(s.substr(0,i)))
{
vector strs = wb(s.substr(i),wordDict);
for(int j = 0;j < strs.size(); ++j){
res.push_back(s.substr(0,i) + " " + strs[j]);
}
}
}
if(wordDict.count(s)) res.push_back(s);
maps[s] = res;
return res;
}
vector wordBreak(string s, vector& wordDict) {
set dict(wordDict.begin(),wordDict.end());
maps.clear();
return wb(s,dict);
}
};
146. LRU Cache
题意:设置一个支持两种O(1)操作的cache(插入和查询),key,val都是整数,val为正数。插入(key,val),如果容量不足,那么删除一个上一次访问时间最前的,再插入。查询,如果key存在,返回val,否则返回-1。
题解:用hashmap和链表。
class LRUCache {
public:
int capacity,curc;
struct Node{
int val,key;
Node(){}
Node(int a,int b){key = a;val = b;}
Node *pre,*next;
}*head,*tail,h,t;
unordered_map map;
LRUCache(int capacity) {
h = Node();
t = Node();
tail = &h;
head = &t;
head ->next = tail;
tail -> pre = head;
curc = 0;
this->capacity = capacity;
map = unordered_map();
}
int get(int key) {
Node *node = map[key];
if(node == NULL) return -1;
Node *pre = node->pre,*next = node->next;
pre->next = next;
next->pre = pre;
next = head->next;
head ->next = node;
node ->pre = head;
node ->next = next;
next ->pre = node;
return node->val;
}
void put(int key, int value) {
if(get(key) == -1){
checkCap();
addNode(key,value);
}else{
rmNode(map[key]);
addNode(key,value);
}
}
void rmNode(Node *node){
Node *pre = node ->pre;
Node *next = node ->next;
pre -> next = next;
next ->pre = pre;
map[node->key] = NULL;
delete node;
--curc;
}
void addNode(int key,int value){
Node *node = new Node(key,value);
map[key] = node;
Node * next = head ->next;
head -> next = node;
node ->pre = head;
node ->next = next;
next -> pre = node;
++curc;
}
void checkCap(){
if(curc == capacity){
rmNode(tail->pre);
}
}
};
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache obj = new LRUCache(capacity);
* int param_1 = obj.get(key);
* obj.put(key,value);
*/
149. Max Points on a Line
题意:给n个二维平面的整数点,位于同一直线的点最多多少个
题解:枚举每个点。然后以这个点,计算出所有直线,由于都是整数,直线可以用两个整点表示。而我们固定了其中一个点,对于一条直线,如果我们取这条直线上离它最近的且位于右上方的不同整点,那么表示是唯一的。复杂的O(n^2logn),如果用hashmap,可以是O(n^2)。这题没啥意义。
/**
* Definition for a point.
* struct Point {
* int x;
* int y;
* Point() : x(0), y(0) {}
* Point(int a, int b) : x(a), y(b) {}
* };
*/
class Solution {
public:
int gcd(int a,int b){return b? gcd(b,a%b) : a;}
static bool cmp(pair &a,pair &b){
if(a.first == b.first){
return a.second < b.second;
}
return a.first < b.first;
}
int maxPoints(vector& points) {
int n = points.size();
if(n <= 1) return n;
int ans = 1;
for(int i = 0; i < n; ++i){
vector > lines;
int same = 1,maxp = 0;
for(int j = i + 1; j < n; ++j){
int x = points[i].x - points[j].x;
int y = points[i].y - points[j].y;
if(x == 0 && y == 0) {++same; continue;}
int d = gcd(x,y);
lines.push_back(make_pair(x/d,y/d));
}
sort(lines.begin(),lines.end(),cmp);
int tmp = lines.size() > 0;
for(int j = 1;j < lines.size(); ++j){
if(lines[j] == lines[j - 1]) ++tmp;
else{
maxp = max(maxp,tmp);
tmp = 1;
}
}
maxp = max(maxp,tmp);
ans = max(ans,maxp + same);
}
return ans;
}
};
153. Find Minimum in Rotated Sorted Array
见154,只是没有重复的而已。154的代码可直接用。
154. Find Minimum in Rotated Sorted Array II
题意:给定一个由有序序列翻转过的数组,求其中的最小值(可能存在重复值)。所谓翻转,就是讲前面一段移到后面去。
题解:二分查找即可,用最后一个元素做查找,由于有重复,我们将和最后一个元素相同的元素去掉,这样就没有任何不同了。
class Solution {
public:
int findMin(vector& nums) {
int n = nums.size();
if(n == 1) return nums[0];
int left,right,mid,sentry = nums[n - 1];
while(n > 1 && nums[n - 2] == sentry) --n;
left = 0;
while(left < n && nums[left] == sentry) ++left;
int ans = sentry;
right = n - 1;
if(n == 1) return nums[0];
while(left <= right){
mid = (left + right) / 2;
if(nums[mid] > sentry){
left = mid + 1;
}else if(nums[mid] <= sentry){
right = mid - 1;
ans = mid;
}
}
return nums[ans];
}
};
164. Maximum Gap
题解:给定一个无序正整数数组,问它排序后的相邻两数的最大间隔是多少?
题解:桶排序。设min为最小值,max为最大值,数组大小为n。那么最大gap的最小可能就是ceil((max - min) / n),只有当均匀分布时才达到。所以我们只要准备ceil((max - min) / ceil((max - min) / n))个桶,每个桶只要保存它的最小和最大值即可,gap肯定在相邻桶达到。
另一种是基数排序,因为数字是32位的,我们能使用基数排序。
桶排序:
class Solution {
public:
int maximumGap(vector &num) {
int n = num.size();
if(n <= 1) return 0;
int res =0;
int minV, maxV;
minV = maxV = num[0];
for(int i = 1; i < n; ++i){
if(minV > num[i]) minV = num[i];
else if(maxV < num[i]) maxV = num[i];
}
int bucket_size = max(1, (maxV - minV ) / (n - 1)); //the size of every bucket
int bucket_num = (maxV - minV) / bucket_size + 1; //num of buckets
if(bucket_num <= 1) return (maxV - minV);
vector bucket_max(bucket_num, INT_MIN);
vector bucket_min(bucket_num, INT_MAX);
vector bucket_count(bucket_num, 0);
for(int i = 0; i < n; ++i){
int bucket_id = (num[i] - minV) / bucket_size;
++bucket_count[bucket_id];
bucket_min[bucket_id] = bucket_min[bucket_id] = min(num[i], bucket_min[bucket_id]);
bucket_max[bucket_id] = bucket_max[bucket_id] = max(num[i], bucket_max[bucket_id]);
}
int pre_max = minV;
int maxGap = 0;
for(int i = 0; i < bucket_num; ++i)
{
if(bucket_count[i])
{
maxGap = max(maxGap, bucket_min[i]- pre_max);
pre_max = bucket_max[i];
}
}
return maxGap;
}
};
基数排序:
class Solution {
public:
void radixSort(vector &num){
for(int i = 0;i < 31; ++i){ //positive number
vector zeroBucket,oneBucket;
int neddle = 1 << i;
for(auto &n: num){
if(neddle & n) oneBucket.push_back(n);
else zeroBucket.push_back(n);
}
num = zeroBucket;
num.insert(num.end(),oneBucket.begin(),oneBucket.end());
}
}
int maximumGap(vector &num) {
int n = num.size();
if(n <= 1) return 0;
radixSort(num);
int maxGap = 0;
for(int i = 1;i < n;++i){
maxGap = max(maxGap,num[i] - num[i - 1]);
}
return maxGap;
}
};
166. Fraction to Recurring Decimal
题意:将有理数转成小数,循环小数的循环部分用括号括起来
题解:直接模拟,记录出现过的余数(我用map存)。注意int溢出还有符号问题。
class Solution {
public:
string fractionToDecimal(int numerator, int denominator) {
long long z = 1LL * numerator / denominator;
long long remainder;
int sign = (numerator < 0 && denominator > 0) || (numerator > 0 && denominator < 0);
remainder = abs(1LL * numerator);
long long denominatorL = abs(1LL * denominator);
stringstream strStream;
if(sign) strStream<<"-";
strStream< maps;
while(remainder){
maps[remainder] = index++;
remainder *= 10;
tmp += char('0' + remainder / denominatorL);
remainder %= denominatorL;
unordered_map::iterator it = maps.find(remainder);
if(it != maps.end()){
tmp = tmp.substr(0,(*it).second ) + "(" + tmp.substr((*it).second) + ")";
break;
}
}
return ans + tmp;
}
};
174. Dungeon Game
题意: 给定一个二维的矩阵,每一个点有一个数字,给定一个初始值t,要从左上角移动到右下角(只能往下和往右),每经过一个点,当前值要和格子的数相加。问初始值至少为多少,才能使得能够从左上角到达右下角的过程中,值始终为正。
题解:设dp[i][j]表示从位置i,j到达右下角时到达该点的值至少是多少。那么dp[0][0]即为答案。从右下往左上递推即可。然后用滚动数组,只需一维辅助空间。
class Solution {
public:
int calculateMinimumHP(vector >& dungeon) {
if(dungeon.empty()) return 1;
int n = dungeon.size();
int m = dungeon[0].size();
vector dp(m + 1, INT_MAX);
dp[m - 1] = 1;
for(int i = n - 1;i >= 0; --i){
for(int j = m - 1;j >= 0; --j){
int tmp = min(dp[j], dp[j + 1]) - dungeon[i][j];
dp[j] = tmp <= 0 ? 1 : tmp;
}
}
return dp[0];
}
};
185. Department Top Three Salaries
sql
187. Repeated DNA Sequences
题意:给定一个DNA序列(字符串,含ACGT四种字符),问长度为10且出现超过一次的串有哪些
题解:由于只有四种字符(我们可以用两位来编码一个字符,长度为10的字符只需要20位,能存在一个int数中)。而注意将字母表示成二进制,A ,C,G,T 分别为0101, 0103,0107, 0124.,他们最后一位都不一样,而且最大为7,所以我们能用3位来编码字符,虽然有冗余,但是存在一个int足够了,而且方便。编码后,用map存起来,每次只要找是否存在过,存在则输出当前十个字符。
大神的代码:
lass Solution {
public:
vector findRepeatedDnaSequences(string s) {
unordered_map m;
vector r;
int t = 0, i = 0, ss = s.size();
while (i < 9)
t = t << 3 | s[i++] & 7;
while (i < ss)
if (m[t = t << 3 & 0x3FFFFFFF | s[i++] & 7]++ == 1)
r.push_back(s.substr(i - 10, 10));
return r;
}
};
188. Best Time to Buy and Sell Stock IV
题意:和123题类似,只不过换成至多k次交易
题解: 下面使用O(kn)的动态规划,和123思路一致
class Solution {
public:
int maxProfit(int k,vector& prices) {
int n = prices.size();
if(k == 0) return 0;
if(n <= 1) return 0;
vector > dp(2,vector(n,0));
int minimum = prices[0];
dp[0][0] = 0;
int ans = 0;
for(int i = 1;i < n; ++i){
minimum = min(minimum,prices[i]);
dp[0][i] = prices[i] - minimum;
ans = max(ans,dp[0][i]);
}
if(k == 1) return ans;
if(k > n) k = (n + 1) / 2;
int maximum ,tmp;
for(int j = 1; j < k; ++j){
maximum = tmp = - prices[0];
for(int i = 1;i < n; ++i) {
maximum = max(maximum, dp[1 - (j & 1)][i - 1]);
tmp = max(tmp, maximum - prices[i]);
dp[j & 1][i] = prices[i] + tmp;
ans = max(ans,dp[j & 1][i]);
}
}
return ans;
}
};
这里有个更加好的解法:https://discuss.leetcode.com/topic/9522/c-solution-with-o-n-klgn-time-using-max-heap-and-stack/2
190. Reverse Bits
题意:32位无符号整数二进制翻转后的结果
题解:前导0也要翻转,就是整个32bit翻转
通常就是一位一位翻转,如下代码,当然可以更加快速,利用类似分治和位操作的思想。这个类似于那个bitCount。
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
uint32_t ans = 0;
int num = 0;
while(n){
ans <<= 1;
ans |= (n & 1);
n >>= 1;
++num;
}
while(num++ < 32) ans <<= 1;
return ans;
}
};
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
n = (n >> 16) | (n << 16);
n = ((n & 0xff00ff00) >> 8) | ((n & 0x00ff00ff) << 8);
n = ((n & 0xf0f0f0f0) >> 4) | ((n & 0x0f0f0f0f) << 4);
n = ((n & 0xcccccccc) >> 2) | ((n & 0x33333333) << 2);
n = ((n & 0xaaaaaaaa) >> 1) | ((n & 0x55555555) << 1);
return n;
}
};
199. Binary Tree Right Side View
题意:给定一颗二叉树,找出它的右视图
题解:dfs,右子树先遍历即可。
/**
* 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 findAns(TreeNode* root, int depth, vector &ans){
if(!root) return;
if(depth >= ans.size()) ans.push_back(root->val);
findAns(root->right,depth + 1,ans);
findAns(root->left,depth + 1,ans);
}
vector rightSideView(TreeNode* root) {
vector ans;
findAns(root,0,ans);
return ans;
}
};
212. Word Search II
题意:给定一个board,每个位置是一个字符。还有一个words字符串数组。问这个board可以组成那些words中的字符串。组成的方式是从board的任意一个位置开始,沿着上下左右四个位置所能遍历的字符组连接成的字符串(不能重复通过同一个位置)。
题解:将words用一个trie树存起来。然后查询。查询通过dfs进行。(反过来用dfs建树然后word查询会导致内存不足或者超时,可能是board比较大)。我用的是数组版的trie,比较占内存,而且比较耗时
class Trie {
public:
//对于字符串比较多的要统计个数的,map被卡的情况下,直接用字典树
//很多题都是要用到节点下标来表示某个字符串
static const int maxn = 6e4+1;
//const int maxn =2e6+5;//如果是64MB可以开到2e6+5,尽量开大
//int tree[maxn][26];//tree[i][j]表示节点i的第j个儿子的节点编号
vector> tree;
int idx[maxn];//表示以该节点结尾是一个单词
int tot;//总节点数
Trie():tree(vector(maxn, vector(26, 0))){
tot = 0;
memset(idx, -1, sizeof(idx));
//memset(tree, 0, sizeof(tree));
//init();
}
// void init(){
// for(int i = 0;i <= tot; ++i){
// //flag[i] = false;
// for(int j = 0; j < 26; ++j)
// tree[i][j] = 0;
// }
// tot = 0;//RE有可能是这里的问题
// }
// void insert_dfs(const int n, const int m, const vector> &board, int x,int y, int depth, vector> &vis, int cur_node){
// if(depth <= 0) return ;
// //int x = pos.first, y = pos.second;
// vis[x][y] = true;
// int cur_id = board[x][y] - 'a';
// if (!tree[cur_node][cur_id]) tree[cur_node][cur_id] = ++tot;
// cur_node = tree[cur_node][cur_id];
// int dir_x[] = {0, 1, 0 , -1};
// int dir_y[] = {1, 0, -1, 0};
// for(int d = 0; d < 4; ++d){
// int next_x = x + dir_x[d];
// int next_y = y + dir_y[d];
// if(next_x < 0 || next_y < 0 || next_x >= n || next_y >= m || vis[next_x][next_y]) continue;
// insert_dfs(n, m, board, next_x, next_y, depth - 1, vis, cur_node);
// }
// vis[x][y] = false;
// }
void insert(string str, int i){
int len = str.length();
int root = 0;
for(int i = 0; i < len; ++i){
int id = str[i] - 'a';
if (!tree[root][id]) tree[root][id] = ++tot;
root = tree[root][id];
}
idx[root] = i;
}
//n*m是board的大小,x,y是当前的位置,depth的剩余的长度,vis表示访问过的位置,cur_node是trie_tree当前的node索引,vis_word_idx记录访问到的单词下标
void find_dfs(const vector> &board, int x, int y, int depth, vector> &vis, int cur_node, vector &vis_words_idx){
if(depth <= 0) return ;
int cur_id = board[x][y] - 'a';
cur_node = tree[cur_node][cur_id];
if(~idx[cur_node])
vis_words_idx[idx[cur_node]] = true;
//没有子节点了,直接返回
if(!cur_node) return;
static const int dir_x[] = {0, 1, 0 , -1};
static const int dir_y[] = {1, 0, -1, 0};
vis[x][y] = true;
for(int d = 0; d < 4; ++d){
int next_x = x + dir_x[d];
int next_y = y + dir_y[d];
if(next_x < 0 || next_y < 0 || next_x >= board.size() || next_y >= board[0].size() || vis[next_x][next_y]) continue;
find_dfs(board, next_x, next_y, depth - 1, vis, cur_node, vis_words_idx);
}
vis[x][y] = false;
}
// bool find_word(string str){
// int len = str.length();
// int root = 0;
// for(int i = 0; i < len; ++i){
// int id = str[i] - 'a';
// if(!tree[root][id]) return false;
// root = tree[root][id];
// }
// return true;
// }
};
class Solution {
public:
vector findWords(vector>& board, vector& words) {
int max_len = 0;
Trie trie_tree;
for(int i = 0;i < words.size(); ++i){
max_len = max(max_len, int(words[i].length()));
trie_tree.insert(words[i], i);
}
int n = board.size(), m = board[0].size();
vector > vis(n, vector(m, false));
// for(int i = 0;i < n; ++i){
// for(int j = 0;j < m; ++j){
// trie_tree.insert_dfs(n, m, board, i, j, max_len, vis, 0);
// }
// }
vector vis_words_idx(words.size(), false);
for(int i = 0;i < n; ++i){
for(int j = 0;j < m; ++j){
//cout<<"----"< ans;
for(int i = 0;i < words.size(); ++i){
if(vis_words_idx[i])
ans.emplace_back(words[i]);
}
return ans;
}
};
214. Shortest Palindrome
题意:给定一个串,可以在前面加字符。求经过添加字符,产生的最短回文串。
题解:实际上只要我们求出从起始位置开始的最长回文串,把后面非回文串部分颠倒添加到前面即为答案。求最大回文串就有很多方法了,一个是使用经典的manacher算法,筛选出其中起始位置为开头的最大回文串。另一个是用KMP,把串颠倒,我们求的是这两个串的最长匹配长度。如果我们通过将s翻转得到s1,令s2 = s + '#'+s1,则按照next的定义我们从s2的next数组的最后一个值就可以得到我们要的最长长度。以下是这两种解法,都是O(n)
class Solution {
public:
string shortestPalindrome(string s) {
if(s.length() <= 1) return s;
int n = s.length();
char *str = new char[2 * n + 2];
char *ns = new char[2 * n + 2];
for(int i = 0;i < n; ++i) str[2 * i] = '#',str[2 * i + 1] = s[i];
str[2 * n] = '#'; str[2 * n + 1] = '\0';
n = 2 * n + 1;
vector longestPailindrome(n,1);
int right = 0,left,pos = 0,dis = 0,id = 0,len;
for(int i = 1;i < n ; ++i){
if(right <= i) left = i;
else left = 2 * id - i;
len = min(longestPailindrome[left],right - i + 1);
while(i + len < n && i - len >= 0 && str[i + len] == str[i - len]) ++len;
longestPailindrome[i] = len;
if(i + len - 1 >= right){
right = i + len - 1;
id = i;
}
if(longestPailindrome[i] == i + 1) pos = i;
}
right = pos + longestPailindrome[pos];
pos = 0;
for(int i = n - 1;i >= right; --i)
if(str[i] != '#') ns[pos++] = str[i];
for(int i = 0;i < n; ++i)
if(i & 1) ns[pos++] = str[i];
ns[pos] = '\0';
s = string(ns);
delete ns,str;
return s;
}
};
/*
class Solution {
public:
vector getNext(string s){
vector next(s.length(),0);
int index = 0;
for(int i = 1;i < s.length(); ++i){
if(s[index] == s[i]){
next[i] = next[i - 1] + 1;
++index;
}else{
index = next[i - 1];
while(index > 0 && s[index] != s[i]){
index = next[index - 1];
}
if(s[index] == s[i]){
++index;
}
next[i] = index;
}
}
return next;
}
string shortestPalindrome(string s) {
if(s.length() <= 1) return s;
int n = s.length();
string tmp = s;
cout< next = getNext(tmp);
int len = next[next.size() - 1];
return tmp.substr(s.length() + 1, s.length() - len) + s;
}
};
*/
next数组求法
vector getNext2(string s){
vector next(s.length(),0);
int index = 0,i = 1;
while(i < s.length()){
while(index > 0 && s[index] != s[i])
index = next[index - 1];
if(s[index] == s[i]) ++index;
next[i++] = index;
}
return next;
}
vector getNext(string s){
vector next(s.length(),0);
int index = 0,i = 1;
while(i < s.length()){
if(s[index] == s[i]){
next[i++] = ++index;
}else{
if(!index) next[i++] = 0;
else index = next[index - 1];
}
}
return next;
}
还可以有优化版本:
vector getNext(string s){
vector next(s.length(),0);
int index = 0;
for(int i = 1;i< s.length(); ++i) {
while(index > 0 && s[i] != s[index]) index = next[index - 1];
if(s[index] == s[i]) ++index;
if(s[i] == s[next[i - 1]]) next[i] = next[next[i - 1]] + 1;
else next[i] = index;
}
return next;
}
218. The Skyline Problem
题意:给定一些矩形(left,right,height)的形式给出,如下图所示。求出其中的关键点,就是覆盖以后那些产生高度变化的点
题解:我是用优先队列求解的。首先这些点肯定是某个矩形的角。所以把所有可能的点先排个序,从小到大。我们判断当前位置是否为关键点。每次判断当前点的最高矩形高度,看看是否产生变化。而求最高高度可用一个优先队列维护。
class Solution {
struct cmp{
bool operator () (const vector &a, const vector&b) const{
if(a[0] != b[0]) return a[0] < b[0];
return a[2] >= b[2];
}
};
public:
vector> getSkyline(vector>& buildings) {
int n = buildings.size();
vector pos(n << 1);
for(int i = 0;i < n; ++i) {
pos[i<<1] = buildings[i][0];
pos[(i<<1) | 1] = buildings[i][1];
}
sort(pos.begin(), pos.end());
sort(buildings.begin(), buildings.end(), cmp());
//去重
int uq_pos_size = unique(pos.begin(), pos.end()) - pos.begin();
vector> ans;
priority_queue> Q;
int building_pos = 0;
int prev_height = 0, next_right = 0;
for(int i = 0;i < uq_pos_size; ++i){
//if(i > 0 and pos[i] == pos[i - 1]) continue;
int &cur_pos = pos[i];
while(!Q.empty() and Q.top().second <= cur_pos) Q.pop();
while(building_pos < n and buildings[building_pos][0] <= cur_pos){
Q.push(make_pair(buildings[building_pos][2], buildings[building_pos][1]));
++building_pos;
}
int cur_height = Q.empty() ? 0 : Q.top().first;
if(cur_height != prev_height){
ans.push_back(vector{cur_pos, prev_height = cur_height});
}
}
return ans;
}
};
224. Basic Calculato
题意:计算一个只含有+-()和空格的表达式的值,其中数字是正整数
题解:用栈保存括号前的结果。
class Solution {
public:
int calculate(string s) {
stack nums, ops;
int num = 0;
int rst = 0;
int sign = 1;
for (char c : s) {
if (isdigit(c)) {
num = num * 10 + c - '0';
}
else {
rst += sign * num;
num = 0;
if (c == '+') sign = 1;
if (c == '-') sign = -1;
if (c == '(') {
nums.push(rst);
ops.push(sign);
rst = 0;
sign = 1;
}
if (c == ')' && ops.size()) {
rst = ops.top() * rst + nums.top();
ops.pop(); nums.pop();
}
}
}
rst += sign * num;
return rst;
}
};
233. Number of Digit One
题意:给定一个数字,问小于等于它的正整数中,1在各个数字的各个位中总共出现多少次。
题解:我们只要计算每个位上出现的1的个数加起来即可。假设我们现在有数字12145,我们要计算
百位上出现的1的个数。首先考虑前缀数字为0到11之间,对于每个前缀有100个,共有1200个。
再考虑前缀为12的,共46个,所以总共1246个。按这个思路写即可,对于每一位,将数字分成两半,然后计算。
class Solution {
public:
int countDigitOne(int n) {
int ans = 0;
for (long long m = 1; m <= n; m *= 10)
ans += (n/m + 8) / 10 * m + (n/m % 10 == 1) * (n%m + 1);
return ans;
}
};
239. Sliding Window Maximum
题意:给定一个序列,和一个数k,以k大小的窗口滑动,求每k个数中的最大值。
题解:单调队列经典应用。维护一个单调递减的队列,队列头部就是当前的最大值。当队列前超过窗口时移除,然后新进来的数,将小于它的移除。
class Solution {
public:
vector maxSlidingWindow(vector& nums, int k) {
if(k <= 1) return nums;
deque Q;
vectorans;
for(int i = 0 ;i < nums.size(); ++i){
while(!Q.empty() && i - Q.front() >= k) Q.pop_front();
while(!Q.empty() && nums[i] > nums[Q.back()]) Q.pop_back();
Q.push_back(i);
if(i >= k - 1) ans.push_back(nums[Q.front()]);
}
return ans;
}
};
260. Single Number III
题意:给定一个整数数组,其中除了两个数外的其他数都出现两次,找出这两个出现一次的数。
要求:常数辅助空间和线性时间
题解:利用位操作。注意到除了这两个数,其他数是一对对的。所以对整个数组求异或,我们得到这两个数的异或。
然后找这个异或结果中,不为0的一位r,这一位不为0表示两个数在这一位不一样。接着将数组分成两部分(按r位0或1分),分别异或就得到这两个数字。取得一位不为零,我们可以取最后一位,利用补码的性质它是x&(-x)
class Solution {
public:
vector singleNumber(vector& nums) {
int xorr = 0;
for(int i = 0;i < nums.size() ; ++i) xorr ^= nums[i];
int r = xorr & (-xorr);
int axor=0,bxor=0;
for(int i = 0;i < nums.size(); ++i){
if(nums[i] & r) axor^=nums[i];
else bxor^=nums[i];
}
vectorans;
ans.push_back(bxor);
ans.push_back(axor);
return ans;
}
};
273. Integer to English Words
题意:数字转英文
题解:直接做,用递归
class Solution {
public:
vector unit = {"", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine"} ;
vector ten2twenty = {"Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"};
vector decade = {"", "Ten", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"};
vector name = {" Hundred "," Thousand "," Million "," Billion "};
vector format = {100,1000,1000000,1000000000};
string numberToWords(int num) {
if(num == 0) return "Zero";
return getString(num);
}
string getString(int num){
string result;
if (num < 10) result = unit[num];
else if (num < 20) result = ten2twenty[num - 10];
else if (num < 100) result = decade[num / 10] + " " + getString(num % 10);
else{
int dig = 0;
while(dig < 4 && format[dig] <= num) ++dig;
result = getString(num / format[dig - 1]) + name[dig - 1] + getString(num % format[dig - 1]);
}
result.erase(0,result.find_first_not_of(" "));
result.erase(result.find_last_not_of(" ") + 1);
return result;
}
};
282. Expression Add Operators
题意:给定一串数字和一个整数,在这串数字中添加+-*符号成为一个表达式,输出所有表达式的值和这个整个相同的结果。
题解:深度搜索。
class Solution {
public:
vector addOperators(string num, int target) {
vector result;
if(num.size()==0) return result;
help(result, "", num, target, 0, 0, 0);
return result;
}
void help(vector &result, string path, string num, int target, int pos, long cur, long prev){
if(pos==num.size()){
if(cur==target) result.push_back(path);
return;
}
for(int i=pos; ipos) break;
string _str=num.substr(pos, i-pos+1);
long _value=stol(_str);
if(pos==0) {
help(result, path+_str, num, target, i+1, _value, _value);
}
else{
help(result, path+"+"+_str, num, target, i+1, cur+_value, _value);
help(result, path+"-"+_str, num, target, i+1, cur-_value, -_value);
help(result, path+"*"+_str, num, target, i+1, cur-prev+prev*_value, prev*_value);
}
}
}
};
295. Find Median from Data Stream
题意:动态增加数字,查找中位数
题解:利用480的算法即可。还可以维护两个堆,一个保存小于的一半数的大顶堆,一个维护大的一半的小顶堆。
class MedianFinder {
public:
/** initialize your data structure here. */
multiset window;
multiset ::iterator mid;
int totNum = 0;
MedianFinder() {
window = multiset();
mid = window.end();
}
void addNum(int num) {
++totNum;
window.insert(num);
if (mid != window.end() ){
if(num < *mid && totNum & 1) --mid;
else if(num >= *mid && (totNum & 1) == 0) ++mid;
}else mid = window.begin();
}
double findMedian() {
return (double(*mid) + *prev(mid, 1 - totNum % 2)) / 2;
}
};
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder obj = new MedianFinder();
* obj.addNum(num);
* double param_2 = obj.findMedian();
*/
297. Serialize and Deserialize Binary 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 Codec {
public:
// Encodes a tree to a single string.
string mySerialize(TreeNode* root) {
if(root == NULL) return string("#");
string s1 = serialize(root -> left);
string s2 = serialize(root -> right);
return to_string(root->val) + ',' + s1 + ',' + s2;
}
string serialize(TreeNode* root) {
return mySerialize(root);
}
// Decodes your encoded data to tree.
TreeNode* myDeserialize(string &data){
if(data[0] == '#') {
if(data.size() > 1) data = data.substr(2);
return NULL;
}
int iter = data.find(',');
TreeNode *root = new TreeNode(stoi(data.substr(0,iter)));
data = data.substr(iter + 1);
root -> left = myDeserialize(data);
root -> right = myDeserialize(data);
return root;
}
TreeNode* deserialize(string data) {
return myDeserialize2(data);
}
};
// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));
301. Remove Invalid Parentheses
题意:给定一个括号的字符串(可能带其他字符),计算出所有最长的合法的括号序列(左右能匹配的)
题解:首先用贪心计算合法序列最长长度,然后dfs(bfs也可以)。剪枝就不多说了,还要避免重复的情况。怎么避免重复呢,只要对于连续相同的半边括号,删除总是从后面开始就可以了,(因为是最长度,是不可能跳过两个可以匹配的对的)。
class Solution {
public:
void dfs(const string &s,const int cur,int depth,vector &ans,const string curString,const vector &rightPar,int left,int right,char last){
if(right > left) return;
if(depth == 0){
if(left != right ) return;
ans.push_back(curString);
return;
}
if(cur == s.length()) return;
if(rightPar[cur] < left - right) return;
if(s[cur] == '('){
if(last != '(')
dfs(s,cur + 1,depth - 1,ans,curString+'(',rightPar,left + 1,right,'0');
dfs(s,cur + 1,depth,ans,curString,rightPar,left,right,'(');
}else if(s[cur] == ')'){
if(last != ')')
dfs(s,cur + 1,depth - 1,ans,curString+')',rightPar,left,right + 1,'0');
dfs(s,cur + 1,depth,ans,curString,rightPar,left,right,')');
}else{
dfs(s,cur + 1,depth - 1,ans,curString+s[cur],rightPar,left,right,'0');
}
}
vector removeInvalidParentheses(string s) {
int depth = 0,remove = 0;
stack S;
for(int i = 0;i < s.length(); ++i){
if(s[i] != ')' && s[i] != '(') continue;
if(s[i] == '(') S.push(1);
else{
if(!S.empty())S.pop();
else ++remove;
}
}
vector rightParentheses(1 + s.length(),0);
for(int i = s.length() - 1; i >= 0; --i){
rightParentheses[i] = rightParentheses[i + 1] + (s[i] == ')');
}
remove += S.size();
depth = s.length() - remove;
vector ans;
dfs(s,0,depth,ans,string(""),rightParentheses,0,0,'0');
return ans;
}
};
312. Burst Balloons
题意:有n个数的数组nums,每次从中拿走一个,得分是nums[left] * nums[i] * nums[right](即它乘以它旁边的数,如果旁边只有一个,就只乘一个,没有就是它本身),拿走后,left和right变成邻居。问最大得分是多少。
题解:动态规划,在两端加两个1。设dp[i][j]表示从i到j依次拿走可以的得分。然后枚举i到j最后剩下的那个即可进行状态转移。 dp[i][j] = max(dp[i][j], nums[i-1]*nums[k]*nums[j+1] + dp[i][k-1] + dp[k+1][j])
class Solution {
public:
int maxCoins(vector& nums) {
int n = nums.size();
if(n == 0) return 0;
if(n == 1) return nums[0];
nums.insert(nums.begin(), 1);
nums.push_back(1);
vector > dp(n + 2, vector(n + 2, 0));
for (int len = 1; len <= n; ++len)
for (int left = 1; left <= n - len + 1; ++left) {
int right = left + len - 1;
for (int k = left; k <= right; ++k){
dp[left][right] = max(dp[left][right], nums[left-1]*nums[k]*nums[right+1] + dp[left][k-1] + dp[k+1][right]);
}
}
return dp[1][n];
}
};
315. Count of Smaller Numbers After Self
题意:给定一个数组,计算出每一个数字后面比它大的数字的个数
题解:先把数组转成数字和下标pair的形式,然后排序(如果一样大,那么下标小的为小)。排完序之后,问题变成了,对于下标,前面有多少个比它大的。然后就可以用树状数组或者归并排序解决之。这里用的是树状数组。
class Solution {
public:
struct cmp{
bool operator()(const pair &a,const pair &b){
if(a.first != b.first)
return a.first < b.first;
else return a.second < b.second;
}
};
struct BIT{ //binary indexed tree
int n;
vectora;
BIT(int s){
n = s;
a = vector(n + 1,0);
}
int lowbit(int x){return (x&(-x));}
int sum(const int &x){
int res = 0;
for(int i = x;i > 0; i -= lowbit(i)) res += a[i];
return res;
}
void update(const int &x){
for(int i = x;i <= n; i += lowbit(i))
++a[i] ;
}
};
vector countSmaller(vector& nums) {
int n = nums.size();
if(n < 1) return vector();
vector ans(n,0);
vector > v;
BIT bit(n);
for(int i = 0;i < n; ++i){
v.push_back(make_pair(nums[i],i));
}
sort(v.begin(),v.end(),cmp());
for(int i = 0;i < n; ++i){
ans[v[i].second] = i - bit.sum(v[i].second + 1);
bit.update(v[i].second + 1);
}
return ans;
}
};
316. Remove Duplicate Letters
题意:给定一个字符串,要求删除其中重复的字符,要求得到的字符串字典序最小。
题解:贪心。每次枚举最前面的那个字符,从小到大枚举。具体实现需要先记录每个字符的位置,然后判断先枚举第一个字符,然后删掉前面(包括自己)和重复的这个字符,然后得到和原先问题一样的子问题。
class Solution {
public:
bool done(vector> & nums){
for(int i = 0;i < 26; ++i) if(!nums[i].empty()) return false;
return true;
}
bool checkFirstPos(vector> &nums, int pos){
for(int i = 0;i < 26; ++i){
if(nums[i].empty()) continue;
if(nums[i][0] < nums[pos].back()) return false;
}
return true;
}
void remove(vector> &nums,int pos){
int tmp = nums[pos].back();
for(int i = 0;i < 26; ++i){
if(nums[i].empty()) continue;
while(nums[i].back() < tmp) nums[i].pop_back();
}
nums[pos].clear();
}
string removeDuplicateLetters(string s) {
vector> nums(26);
for(int i = 0;i < s.length(); ++i){
nums[s[i] - 'a'].push_back(i);
}
for(int i = 0;i < 26; ++i) reverse(nums[i].begin(),nums[i].end());
int iter = 0;
string ans = "";
while(true){
if(done(nums)) break;
for(int i = 0;i < 26; ++i){
if(nums[i].empty()) continue;
if(checkFirstPos(nums,i)){
ans += 'a' + i;
remove(nums,i);
break;
}
}
}
return ans;
}
};
321. Create Maximum Number
题意:给定两个数组,和一个数k,从这两个数组(每个数组一个数)中选取k个数,组成一个最大的数。其中从各个数组选取的数保持在原数组中的次序
题解:枚举数据1选取的位数和数组2选取的位数,各自产生最大的子序列。然后合并。不过过程我写复杂了。在产生子序列的部分,我采用优先队列贪心选取,实际上并不用,而且在合并部分,情况比较复杂。
第一份是我的代码,写的比较挫,也比较耗时。所以我找了一份更好的代码,修改了一些小地方,加上注释,放在下面。就是第二份代码。会相对比较难理解,但是效率更高。
class Solution {
vector gen_max_subs(vector &nums, int k){
if(k == 0) return vector();
vector n;
priority_queue > Q;
for(int i = 0;i < nums.size() - k + 1; ++i){
Q.push(make_pair(nums[i], -i));
}
int last_pos = -1;
while(k--){
pair u = Q.top(); Q.pop();
if(-u.second > last_pos){
n.push_back(u.first);
last_pos = -u.second;
}
while(!Q.empty() && -Q.top().second <= last_pos) Q.pop();
if(k > 0){
Q.push(make_pair(nums[nums.size() - k], k - int(nums.size())) );
}
}
return n;
}
bool check_max(vector &n1, vector &n2, int pos1 = 0, int pos2 = 0){
bool t = n1.size() - pos1 > n2.size() - pos2;
for(int i = 0;pos1 + i < n1.size() && pos2 + i < n2.size(); ++i){
if(n1[pos1 + i] > n2[pos2 + i]) return true;
else if(n1[pos1 + i] < n2[pos2 + i]) return false;
}
return t;
}
vector merge_subs(vector& n1, vector& n2){
vector n;
int i = 0, j = 0;
while(i < n1.size() or j < n2.size()){
if(i < n1.size() && j < n2.size() && n1[i] == n2[j]){
if(check_max(n1, n2, i, j)){
n.push_back(n1[i++]);
}else{
n.push_back(n2[j++]);
}
continue;
}
if(j < n2.size() && (i == n1.size() || n1[i] < n2[j])){
n.push_back(n2[j++]);
}else{
n.push_back(n1[i++]);
}
}
return n;
}
public:
vector maxNumber(vector& nums1, vector& nums2, int k) {
vector ans;
//i表示从nums1中取i个数
for(int i = max(0, k - int(nums2.size())); i <= min(k, int(nums1.size())); ++i){
vector n1 = gen_max_subs(nums1, i);
vector n2 = gen_max_subs(nums2, k - i);
vector n = merge_subs(n1, n2);
if(ans.size() == 0 or check_max(n, ans)){
ans.swap(n);
}
}
cout<
class Solution {
void combine(std::vector &res, std::vector &v1, std::vector &v2)
{
int len1 = v1.size();
int len2 = v2.size();
int len = res.size();
int left = 0;
int right = 0;
for (int i = 0; i < len;) {
//第二个已经取光或者两个都还有且第一个比第二个当前位置大,push第一个
if (right == len2 || left != len1 && v1[left] > v2[right]) {
res[i++] = v1[left++];
//反过来,即第一个为空,或者两个不为空,且第二个比第一个大,push第二个
} else if (left == len1 || v2[right] > v1[left]) {
res[i++] = v2[right++];
//两个相等,相等的情况比较复杂,需要进一步判断后面的数,直到能确定哪个位置开始选择另一个数组。后面的数如果相等,虽然我们当前不能判断选哪个数组,但是选哪些数我们是直到的
} else {
//保留原先的left和right
int ori_left = left;
int ori_right = right;
int tar = left;
int last_greater = right;
res[i++] = v1[left];
//从当前位置一直比较,一直到两个不相等,或者有一个结束,或者
while (true) {
++left;
++right;
//如果第一个到尽头,或者两个都还有,且第一个小
if (left == len1 || right != len2 && v1[left] < v2[right]) {
left = ori_left;
break;
//如果第二个到尽头,或者两个还有,且第二个小
} else if (right == len2 || v1[left] > v2[right]) {
right = ori_right;
break;
}
//两个是数组对应位置两个值相等
//如果是在上面跳出循环的,说明是两个数组比较出大小了,情况比较简单,哪个大哪个先取
//如果当前还不能判断出大小
//当后面的数比第一个数大时,就一直进行下去,因为一定是先取一方取完再取另一个数组(数已经确定,只是先取哪个还没确定)
//如果出现相同的需要再进一步判断,比如两个数组
//6 8 7 6 * * *
//6 8 7 6 * * *
//到第二个6这里,如果下个位置比8小,则到此为止(不取这个6,取上面的6比较好),必须取另外一个数组了
//6 8 7 6 7 * *
//6 8 7 6 7 * *
//如果大于8,则继续进行,取同一方回归原来的状态
//6 8 7 6 9 * *
//6 8 7 6 9 * *
//如果等于8,则需要继续判断下一位,直到能判断是继续取同一方还是,另外一个数组,如果是同一方,则继续
//所以var就是保存比较到的下标
//last_greater就是保存跳转的位置。当到某个位置的时候,满足下面第一个条件,就是先取数组1的一段,再取2的这一段,然后继续后面的
//比如
//6 8 7 6 5 *
//6 8 7 6 5 *
//这里5比6小,所以肯定是先取6 8 7 6 然后取另一段6 8 7 6,所以我们默认先取第二段,令right = last_greater
if (v1[left] < v1[tar]) {
left = tar;
right = last_greater + 1;
break;
} else if (v1[left] > v1[tar]) {
last_greater = right;
tar = ori_left;
//相等
} else {
++tar;
}
res[i++] = v1[left];
}
}
}
}
void max_num(std::vector &res, std::vector &nums, int k)
{
res.clear();
int len = nums.size();
int diff = len - k;
//这里不是用堆贪心。而是直接用栈贪心就可以了。这和堆贪心本质是一样的。第一个数,一定是0到len - k + 1中最大的数,而第二个数一定是下标比第二个大且小于len - k + 2以此类推。所以当前值如果比栈尾大,且满足下面的条件说明属于它的位置在前面。
//k - res_size > len - i and res_size > 0 变成下面res_size > max(i - diff, 0)
//就是剩余的数和当前已经选取的要大于k,才能pop掉末尾
for (int i = 0; i < len; ++i) {
while (res.size() > std::max(i - diff, 0) && nums[i] > res.back()) {
res.pop_back();
}
if (res.size() < k) {
res.push_back(nums[i]);
}
}
}
public:
vector maxNumber(vector& nums1, vector& nums2, int k) {
std::vector res(k);
std::vector cur(k);
std::vector temp1;
std::vector temp2;
int len1 = nums1.size();
int len2 = nums2.size();
int max_len = std::min(len1, k);
int min_len = std::max(k - len2, 0);
//枚举位数
for (int i = min_len; i <= max_len; ++i) {
max_num(temp1, nums1, i);
max_num(temp2, nums2, k - i);
combine(cur, temp1, temp2);
//vector原来可以直接比较
if (cur > res) {
res.swap(cur);
}
}
return res;
}
};
327. Count of Range Sum
题意:给定一个数组,还有一个区间[lower,upper],问这个数组中,有多少个子数组(连续的),他们的和在这个区间中。
题解:设原数组为a[1,...,n],我们求前缀和s[0,,...,n],s[0] = 0,s[i] = sum a[1,...i],那么问题变成,s中有多少个对,i>j使得s[i] - s[j]在这个区间中。进一步,我么可以先算出所有可能的对数,然后排除掉那些不在这个区间中的。然后问题就和493题差不多了。解法一样。只不过同样要注意溢出问题。
class Solution {
public:
int getans(int low,int up,int left, int right, vector& nums){
if(right - left <= 1) return 0;
int mid = (right + left) / 2;
int ans = 0;
ans = getans(low,up,left, mid, nums);
ans += getans(low,up,mid, right, nums);
int ind1 = mid, ind2 = right - 1;
int tmp1 = 0,tmp2 = 0;
for(int i = left; i < mid; ++i){
//cout<<"i= "< nums[ind1]){
// cout<<"i= "<= left; --i){
//cout<<"inin"<= mid && nums[i] + up < nums[ind2]){
tmp2 += (i - left + 1);
--ind2;
}
// cou
//if(c++ <= 1000 &&c%100 == 0) cout< tmp;
for(int i = left; i < right; ++i){
if(indL >= mid) tmp.push_back(nums[indR++]);
else if(indR >= right) tmp.push_back(nums[indL++]);
else if(nums[indL] < nums[indR]) tmp.push_back(nums[indL++]);
else tmp.push_back(nums[indR++]);
}
for(int i = left; i < right; ++i){
nums[i] = tmp[i - left];
}
return ans;
}
int countRangeSum(vector& nums, int lower, int upper) {
int ans = 0;
if(nums.size() == 0) return 0;
vector newNums(nums.size());
newNums[0] = nums[0];
for(int i = 1;i < newNums.size() ;++i) newNums[i] = newNums[i - 1] + nums[i];
//for(int i = 0; i < newNums.size(); ++i)cout<= lower && newNums[i] <= upper;
// cout<
329. Longest Increasing Path in a Matrix
题意:给定一个整数矩阵,求最长上升路径长度。路径只能往上下左右四个方向走。
题解:很容易想到,如果我们从最大的树开始dp,设dp[i][j]为以i,j为起始点的最长路长度。
那么最大的dp[i][j]即可。所以我们用bfs就可以保证从最大那个(局部最大)数开始了。
另外一种是dfs,设dp[i][j]为以i,j为起始点的最长路长度。如果周围有比它大的,那么先计算它的dp值。然后记忆搜索即可。如下是dsf版本,不难改成bfs版本。
class Solution {
public:
vector > dp;
int n,m;
vector> matrix;
int dfs(int x,int y){
if(dp[x][y]) return dp[x][y];
vector> directions = {{-1, 0}, {1, 0}, {0, 1}, {0, -1}};
for (auto &dir : directions) {
int newx = x + dir[0], newy = y + dir[1];
if (newx < 0 || newx >= n || newy < 0 || newy >= m) continue;
if (matrix[newx][newy] <= matrix[x][y]) continue;
dp[x][y] = max(dp[x][y], dfs(newx, newy));
}
return ++dp[x][y];
}
int longestIncreasingPath(vector>& mat) {
matrix = mat;
if(matrix .size() == 0 || matrix[0].size() == 0) return 0;
n = matrix.size(); m = matrix[0].size();
dp = vector >(n,vector(m,0));
int ans = 0;
for(int i = 0;i < n; ++i){
for(int j = 0;j < m; ++j){
ans = max(ans,dfs(i,j));
}
}
return ans;
}
};
330. Patching Array
题意:给定一个序列,和一个数n,问需要至少补充多少个数才能使得1到n可表示成这个序列中一些元素的和
题解:贪心。每次从小到大,遇到不能表示的就加入肯定是最优的。
class Solution {
public:
int minPatches(vector& nums, int n) {
int cnt=0,i=0;
long long maxNum=0;
while (maxNum < n){
if (i < nums.size() && nums[i] <= maxNum + 1)
maxNum += nums[i++];
else{
maxNum += maxNum + 1; ++cnt;
}
}
return cnt;
}
};
332. Reconstruct Itinerary
题意:给定图的边(以字符串的形式),求里面以"JFK"开始,且通过所有边的字典序最小的路径(题目保证肯定存在),即最小字典序欧拉路径
题解:首先建图,然后,从"JFK"开始,DFS,贪心,按字典序小的边先走。如果走不通,则到达终点(因为只有终点入度比出度大一)。而进来的边,就是最终到达的边。因为如果一个点,存在一个出边没有遍历,要么点已经访问过,那么通过这条边,肯定会再回到这个点,要么这个点没被访问过。如果是第一种情形,那么实际上只是加了一个环,不会影响最后的边。而第二种情形,它肯定被包含在某个环里面。然后,删掉这个边,在剩下的图里继续找这样的点和边,一直找到全部路径上的边和点。
大神的代码就是简洁
class Solution {
public:
vector findItinerary(vector> tickets) {
unordered_map> graph;
vector itinerary;
if (tickets.size() == 0){
return itinerary;
}
for (pair eachTicket : tickets){
graph[eachTicket.first].insert(eachTicket.second);
}
stack dfs;
dfs.push("JFK");
while (!dfs.empty()){
string topAirport = dfs.top();
if (graph[topAirport].empty()){
itinerary.push_back(topAirport);
dfs.pop();
}
else {
dfs.push(*(graph[topAirport].begin()));
graph[topAirport].erase(graph[topAirport].begin());
}
}
reverse(itinerary.begin(), itinerary.end());
return itinerary;
}
};
335. Self Crossing
题意:给定一个正整数数组x,表示的是从原点开始,先向北走x[0],然后向西走x[1],然后向南走x[2],然后向东走x[3],以此类推,每次都逆时针旋转再走。问x路径有没有交叉(有没有相交点)
题解:首先注意到,如果要产生交叉,一定是有一条边x[i]不比x[i - 2]长,因为如果都有x[i] > x[i - 2]那么肯定是螺旋式向外走圈。
我们只需要考虑最近6个点就可以了,因为如果当前节点产生交点,那么最近6个点(5条线段)肯定有交叉。
现在我们就只考虑5个或者6个点的情形(4个点不可能相交)。
5个点的情况非常简单就是 x[i - 3] == x[i - 1] && x[i - 4] + x[i] >= x[i - 2]。
6个点会有几种情况,
第一种,最后一条边穿过x[i - 3],这时候有x[i] >= x[i - 2]
第二种,边重合,这时候有x[i - 2] > x[i - 4] && x[i - 1] <= x[i - 3] && x[i - 5] + x[i - 1] >= x[i - 3] && x[i] + x[i - 4] >= x[i - 2]
class Solution {
public:
bool isSelfCrossing(vector& x) {
if(x.size() < 4) return false;
bool contraction = false;
for(int i = 2;i < x.size(); ++i){
if(contraction){
if((x[i] >= x[i - 2]) ||
(i >= 4 && x[i - 3] == x[i - 1] && x[i - 4] + x[i] >= x[i - 2]) ||
(i >= 5 && x[i - 2] > x[i - 4] && x[i - 1] <= x[i - 3] && x[i - 5] + x[i - 1] >= x[i - 3] && x[i] + x[i - 4] >= x[i - 2])) return true;
continue;
}
if(x[i] <= x[i - 2]) contraction = true;
}
return false;
}
};
336. Palindrome Pairs
题意:给定一些串,求出所有连接能构成回文串的对。
题解:用map或trie保存翻转后的结果。然后对每个串,分别枚举前翻转和后翻转的位置,查找串即可。注意空串的存在。懒得写了。
class Solution {
public:
vector> palindromePairs(vector& words) {
unordered_map dict;
vector> ans;
for(int i = 0; i < words.size(); i++) {
string key = words[i];
reverse(key.begin(), key.end());
dict[key] = i;
}
if(dict.find("")!=dict.end()){
for(int i = 0; i < words.size(); i++){
if(i == dict[""]) continue;
if(isPalindrome(words[i])) ans.push_back({dict[""], i});
}
}
for(int i = 0; i < words.size(); ++i) {
for(int j = 0; j < words[i].size(); ++j) {
string left = words[i].substr(0, j);
string right = words[i].substr(j, words[i].size() - j);
if(dict.find(left) != dict.end() && isPalindrome(right) && dict[left] != i) {
ans.push_back({i, dict[left]});
}
if(dict.find(right) != dict.end() && isPalindrome(left) && dict[right] != i) {
ans.push_back({dict[right], i});
}
}
}
return ans;
}
bool isPalindrome(string str){
int i = 0;
int j = str.size() - 1;
while(i < j) {
if(str[i++] != str[j--]) return false;
}
return true;
}
};
352. Data Stream as Disjoint Intervals
题意:给定一个整数数组。将相邻的合并为区间。并且支持插入和查询操作。
题解:插入时,二分查找插入位置,然后合并即可。
/**
* Definition for an interval.
* struct Interval {
* int start;
* int end;
* Interval() : start(0), end(0) {}
* Interval(int s, int e) : start(s), end(e) {}
* };
*/
class SummaryRanges {
private:
vector intervals;
public:
SummaryRanges() {}
static bool cmp(Interval &a,Interval &b){
return a.end + 1 < b.start;
}
struct comp{
bool operator()(const Interval &a,const Interval &b){
return a.end + 1 < b.start;
}
};
void addNum(int val) {
Interval cur(val, val);
vector res;
vector::iterator begin = lower_bound(intervals.begin(),intervals.end(),cur,comp());
vector::iterator end = begin;
while(end != intervals.end()){
Interval tmp = *end;
if(cur.end + 1 >= tmp.start){
cur.start = min(cur.start, tmp.start);
cur.end = max(cur.end, tmp.end);
++end;
}else {
break;
};
}
int pos = begin - intervals.begin();
intervals.erase(begin,end);
intervals.insert(intervals.begin() + pos,cur);
}
vector getIntervals() {
return intervals;
}
};
/**
* Your SummaryRanges object will be instantiated and called as such:
* SummaryRanges obj = new SummaryRanges();
* obj.addNum(val);
* vector param_2 = obj.getIntervals();
*/
354. Russian Doll Envelopes
题意:俄罗斯套娃。有一些箱子(长和宽),一个箱子如果长和宽都严格大于另一个,那么可以套住另一个。问最多可以套多少层。
题解:动态规划。按照箱子大小排序(先按宽度从小到大,如果宽度一样,那么长度从大到小)。问题变成了,最长递增子序列。然后就可以用O(nlogn)解法了。
class Solution {
public:
struct cmp{
bool operator ()(pair&a, pair &b){
if(a.first == b.first) return a.second > b.second;
else return a.first < b.first;
}
};
struct cmp2{
bool operator ()(const pair&a, const pair &b){
if(a.first >= b.first || a.second >= b.second) return false;
return true;
}
};
int maxEnvelopes(vector>& envelopes) {
int n = envelopes.size();
if(n <= 1) return n;
sort(envelopes.begin(),envelopes.end(),cmp());
vector > head(n + 1,make_pair(INT_MAX,INT_MAX));
head[0] = make_pair(-1,-1);
int ans = 0;
for(int i = 0;i < n; ++i){
int pos = lower_bound(head.begin(),head.end(),envelopes[i],cmp2()) - head.begin();
head[pos] = envelopes[i];
ans = max(ans,pos);
}
return ans;
}
};
363. Max Sum of Rectangle No Larger Than K
题意:给定一个数值矩阵,求其中子矩阵最大不超过k的和
题解:先考虑最大子矩阵和问题。
通常有两种动态规划的解法,一种是枚举起始点和矩阵一边的长度,另外一种是枚举起始行和终止行,然后变成一维求解最大子段和。
类似地我们枚举起始行和终止行,然而求的是最大不超过k的子段和,这个可以用二分查找得到。
时间复杂度为O(min(n,m)^2 max(m,n) log(max(n,m)))
class Solution {
public:
void rotate(vector>& matrix,int &n,int &m){
int tmp = n;
n = m;
m = tmp;
vector> matrix2(n,vector(m));
for(int i = 0;i < n; ++i)
for(int j = 0;j < m; ++j) matrix2[i][j] = matrix[j][i];
matrix = matrix2;
}
int maxSumSubmatrix(vector>& matrix, int kk) {
int n = matrix.size();
if(n == 0) return 0;
int m = matrix[0].size();
if(m == 0) return 0;
int res = INT_MIN;
if(n > 2 * m) rotate(matrix,n,m);
for(int i = 0;i < n; ++i){
vector sum(m, 0);
for(int j = i;j < n; ++j){
for(int k = 0;k < m; ++k){
sum[k] += matrix[j][k];
}
set S;
S.insert(0);
int curSum = 0;
for(int k = 0;k < m; ++k){
curSum += sum[k];
set::iterator iter = lower_bound(S.begin(),S.end(),curSum - kk);
if(iter != S.end()) res = max(res,curSum - *iter);
S.insert(curSum);
}
}
}
return res;
}
};
381. Insert Delete GetRandom O(1) - Duplicates allowed
题意:设计一个可以O(1)插入,删除和抽取随机样本的算法(数据结构)。
题解:用Hashmap和vector来做。map将值映射到val的下标(可能有多个,所以是一个vector),vector保存值和在m中val对应vector的位置。
这样。每次插入,只要更新map值对应的vector,而删除,把vector最后位置调换到删除的地方,然后更新对应map。
class RandomizedCollection {
public:
/** Initialize your data structure here. */
RandomizedCollection() {
}
/** Inserts a value to the collection. Returns true if the collection did not already contain the specified element. */
bool insert(int val) {
auto result = m.find(val) == m.end();
m[val].push_back(nums.size());
nums.push_back(pair(val, m[val].size() - 1));
return result;
}
/** Removes a value from the collection. Returns true if the collection contained the specified element. */
bool remove(int val) {
auto result = m.find(val) != m.end();
if(result)
{
auto last = nums.back();
m[last.first][last.second] = m[val].back();
nums[m[val].back()] = last;
m[val].pop_back();
if(m[val].empty()) m.erase(val);
nums.pop_back();
}
return result;
}
/** Get a random element from the collection. */
int getRandom() {
return nums[rand() % nums.size()].first;
}
private:
vector> nums;
unordered_map> m;
};
391. Perfect Rectangle
题意:给定一些长方形(以左下角,右上角形式给出),问它们是否不重叠地拼接成一个长方形。
题解:只要保证,面积一样。且除了四个顶点,其他点出现偶数次即可。
class Solution {
public:
bool isRectangleCover(vector>& rectangles) {
set > S;
if(rectangles.size() <= 1) return true;
int xl,yl,xr,yr;
xl = rectangles[0][0];
yl = rectangles[0][1];
xr = rectangles[0][2];
yr = rectangles[0][3];
long long area = (long long)(xr - xl) * (yr - yl);
S.insert(make_pair(xl,yl));
S.insert(make_pair(xl,yr));
S.insert(make_pair(xr,yl));
S.insert(make_pair(xr,yr));
for(int i = 1;i < rectangles.size(); ++i){
int xlt = rectangles[i][0];
int ylt = rectangles[i][1];
int xrt = rectangles[i][2];
int yrt = rectangles[i][3];
xl = min(xl,xlt);
yl = min(yl,ylt);
xr = max(xr,xrt);
yr = max(yr,yrt);
pair p1(xlt,ylt),p2(xlt,yrt),p3(xrt,ylt),p4(xrt,yrt);
if(!S.insert(p1).second) S.erase(p1);
if(!S.insert(p2).second) S.erase(p2);
if(!S.insert(p3).second) S.erase(p3);
if(!S.insert(p4).second) S.erase(p4);
area += (xrt - xlt) * (yrt - ylt);
cout<<(xrt - xlt) * (yrt - ylt)< p1(xl,yl),p2(xl,yr),p3(xr,yl),p4(xr,yr);
if(S.find(p1) == S.end() || S.find(p2) == S.end() || S.find(p3) == S.end() ||
S.find(p4) == S.end() || S.size() > 4 || area != (long long)(yr - yl) * (xr - xl))
return false;
return true;
}
};
403. Frog Jump
题意:有一些石头的位置以一个数组给出。一只青蛙从0位置开始跳,每次只可以跳之前跳跨越步数的-1,不变或+1的步数。问能否到达终点。
题解:可以用dfs。这里用map和vector记录每个点到达的步数。
class Solution {
public:
bool canCross(vector& stones) {
if(stones.size() <= 1) return true;
if(stones[1] != stones[0] + 1) return false;
if(stones.size() == 2) return stones[1] == stones[0] + 1;
int n = stones.size();
map m;
for(int i = 0;i < n; ++i){
m[stones[i]] = i;
}
vector > dp(n);
dp[0].push_back(0);
vector jumpStep = {-1,0,1};
for(int i = 0;i < n; ++i){
for(int j = 0;j < dp[i].size(); ++j){
int step = dp[i][j];
int pos;
for(int jump : jumpStep){
if(step + jump <= 0) continue;
pos = m[stones[i] + step + jump];
if(find(dp[pos].begin(),dp[pos].end(),step + jump) == dp[pos].end())
dp[pos].push_back(step + jump);
}
}
}
return dp[n - 1].size() > 0;
}
};
407. Trapping Rain Water II
题意:和42一样,只不过改成二维的。
题解:对于一个点,我们要知道它能trap住多少水,只要知道它到外部的最低高度。所以从最外圈开始,每次取一个最低点,向着4面走,寻求每个点到达外部的最低点。我们用优先队列,先把外围加进队列,每次取最低那个,往里更新即可。还可以用最短路算法来求,我们定义两个点i到j的边权,为j的高度,再加一个外部点,高度为0,所有外围点连接到这个点。定义路径的长度为路径权重最大的边。那么只要求出每个点到达最外点的最短路即可。
class Solution {
public:
struct point{
int x,y;
point(){}
point(int a,int b):x(a),y(b){}
};
struct cmp{
bool operator()(const pair & a,const pair &b) {
return a.second > b.second;
}
};
int trapRainWater(vector>& heightMap) {
int n = heightMap.size();
if(n == 0) return 0;
int m = heightMap[0].size();
priority_queue,vector >, cmp> Q;
vector >vis(n,vector(m,false));
for(int j = 0;j < m; ++j) {
Q.push(make_pair(point(0,j),heightMap[0][j]));
Q.push(make_pair(point(n - 1,j),heightMap[n - 1][j]));
vis[0][j] = vis[n - 1][j] = true;
}
for(int i = 1;i < n - 1; ++i){
Q.push(make_pair(point(i,0),heightMap[i][0]));
Q.push(make_pair(point(i,m - 1),heightMap[i][m - 1]));
vis[i][0] = vis[i][m - 1] = true;
}
int res = 0;
while(!Q.empty()){
pair u = Q.top();
Q.pop();
point p = u.first;
int height = u.second;
vector > dirs = {{1,0},{-1,0},{0,1},{0,-1}};
for(auto &dir : dirs){
int x = p.x + dir[0], y = p.y + dir[1];
if(x < 0 || x >= n || y < 0 || y >= m || vis[x][y]) continue;
res += max(0,height - heightMap[x][y]);
Q.push(make_pair(point(x,y),max(height,heightMap[x][y])));
vis[x][y] = true;
}
}
return res;
}
};
410. Split Array Largest Sum
题意:给定一个非负数组,和一个数m,问把这个数组分成最多m个连续块,对每一块求和。求最大和的最小可能。
题解:动态规划可以解。二分答案可以解。二分的思路是,二分最大和,然后check能否分,用贪心进行check即可。
class Solution {
public:
int splitArray(vector& nums, int m) {
int n = nums.size();
if(n == 0) return 0;
if(n == 1) return nums[0];
int tot = 0,maxs = 0;
for(int i = 0;i < n; ++i) tot += nums[i],maxs = max(maxs,nums[i]);
if(m >= n) return maxs;
long long left = maxs, right = tot,mid,ans;
while(left <= right){
mid = (right - left) / 2 + left;
if(check(nums,mid,m)){
ans = mid;
right = mid - 1;
}else left = mid + 1;
}
return ans;
}
int check(vector& nums,long long sum,int m){
int count = 1;
long long cur = 0;
for(int i = 0;i < nums.size(); ++i){
if(cur + nums[i] > sum){
cur = 0;
++count;
}
cur += nums[i];
if(count > m) return false;
}
return true;
}
};
420. Strong Password Checker
题意:给定一个字符串,问至少通过多少次操作能使得它满足以下条件:1,必须有数字,大小写字母。2.不能出现连续三个一样的字符。3.长度在6到20之间。操作有,插入,删除,修改三种。
题解:首先对于小于6的情况,只要使得没有缺失三种字符和长度到达6就可以自然解决问题2,所以没有难度。对于小于等于20的情况,由于不用删除(替换总比删除有效),所以也没有难度。对于大于20的情况就比较麻烦。首先,肯定用不到插入操作,因为替换总比插入有效。所以只有替换和删除两种操作。所以删除的总数我们就确定了,为s.len - 20。显然,我们必须优先删除连续三个或以上的那些字符,另外如果能。假设连续的字符长度为len > 2。
如果len % 3 == 0, 那么删除一个字符,我们可以少一个替换,然后变成len % 3 == 2
如果len % 3 == 1,那么删除两个字符,我们可以少一个替换,然后变成len % 3 == 2
上面这两种情况我们要优先考虑,因为其他的len % 3 == 2要删除3个字符才能少一个替换。
按照上面的思路,计算出需要替换的数目就可以了。
class Solution {
public:
int strongPasswordChecker(string s) {
int missing_type = 3;
bool digit = 0;
bool lower_case = false;
bool upper_case = false;
for(int i = 0;i < s.length(); ++i){
if(s[i] >= 'A' && s[i] <= 'Z') upper_case = true;
else if(s[i] >= 'a' && s[i] <= 'z') lower_case = true;
else if(s[i] >= '0' && s[i] <= '9') digit = true;
}
missing_type = 3 - digit - lower_case - upper_case;
int replacement = 0, del = 0, one = 0, two = 0;
for(int i = 2;i < s.length(); ++i){
if(s[i] == s[i - 1] && s[i] == s[i - 2]){
int len = 3;
while(i + 1 < s.length() && s[i + 1] == s[i]) ++i,++len;
replacement += len / 3;
one += (len % 3 == 0);
two += (len % 3 == 1);
}
}
if(s.length() < 6){
return max(missing_type,(int)(6 - s.length()));
}else if(s.length() <= 20){
return max(missing_type,replacement);
}else{
del = s.length() - 20;
}
replacement -= min(del, one);
replacement -= min(max(del - one, 0), two * 2) / 2;
replacement -= max(del - one - 2 * two, 0) / 3;
return del+ max(missing_type, replacement);
}
};
432. All O`one Data Structure
题意:设计一个数据结构支持四种操作,Inc(key)将key的对应value增1,如果不存在则为1。Dec(key)将key的value减一,如果为0,则删除key。GetMaxKey()返回任意一个value最大的key,如果没有,返回空串。GetMinKey()返回value最小的key。所有操作是O(1)的
题解:用一个list存储每个value的那些key,按value从大到小排列,这样GetMaxKey和GetMinKey就可以由列表头和列表尾得到。由于获取和删除key也要一个常数时间,所以存储可以用hashset。而由于列表不是随机访问的,所以对于每个key,我们还需要记录它在list中的哪个位置,记录可以用hashmap记录。
class AllOne {
unordered_map>>::iterator> key2freq_keys;
//从大到小
list>> freq_keys_list;
public:
/** Initialize your data structure here. */
AllOne() {
}
/** Inserts a new key with value 1. Or increments an existing key by 1. */
void inc(string key) {
int freq = 0;
list>>::iterator data_iter = freq_keys_list.end();
//key存在
if(key2freq_keys.count(key)){
//如果存在,则删除原有的
data_iter = key2freq_keys[key];
//删除key2freq_keys中的key,删除freq_keys_list中对应的集合中的key,如果删除以后该节点保存的key为空,删除节点
//pair> &freq_keys = *data_iter;
freq = data_iter->first;
data_iter->second.erase(key);
if(data_iter->second.empty()){
freq_keys_list.erase(data_iter++);
}
key2freq_keys.erase(key);
}
//插入
//说明freq + 1的节点不存在,需要先创建
if(data_iter == freq_keys_list.begin() || prev(data_iter)->first != freq + 1){
data_iter = freq_keys_list.insert(data_iter, pair>(freq + 1, unordered_set()));
}else{
--data_iter;
}
data_iter->second.insert(key);
key2freq_keys[key] = data_iter;
}
/** Decrements an existing key by 1. If Key's value is 1, remove it from the data structure. */
void dec(string key) {
if(!key2freq_keys.count(key)) return;
list>>::iterator data_iter = key2freq_keys[key];
int freq = data_iter->first;
//删除原有的
data_iter->second.erase(key);
if(data_iter->second.empty()){
freq_keys_list.erase(data_iter++);
}else{
++data_iter;
}
key2freq_keys.erase(key);
if(freq == 1) return;
//插入
if(data_iter == freq_keys_list.end() || data_iter->first != freq - 1 ){
data_iter = freq_keys_list.insert(data_iter, pair>(freq - 1, unordered_set()));
}
data_iter->second.insert(key);
key2freq_keys[key] = data_iter;
}
/** Returns one of the keys with maximal value. */
string getMaxKey() {
if(freq_keys_list.empty()) return "";
return *freq_keys_list.front().second.begin();
}
/** Returns one of the keys with Minimal value. */
string getMinKey() {
if(freq_keys_list.empty()) return "";
return *freq_keys_list.back().second.begin();
}
};
/**
* Your AllOne object will be instantiated and called as such:
* AllOne* obj = new AllOne();
* obj->inc(key);
* obj->dec(key);
* string param_3 = obj->getMaxKey();
* string param_4 = obj->getMinKey();
*/
440. K-th Smallest in Lexicographical Order
题意:给定一个n,求1到n中字典序排第k的数
题解:给定一个数,如果我们能求其字典序排第几,那么我们逐位枚举即可。那么给定一个数我们怎么求其字典序排第几呢,其实很简单,字典序排在其后的,肯定是前缀比它大,或者前缀一样,后面有0。
我们用get_rank函数来实现这个功能。n_d是一个10的幂次,位数和n一样大。首先将num不断乘以10,一直到位数和n一样多,设为m。那么num的字典序就是这个数m的字典序减去num和n之间的字典序差,而他们排序的差由他们差多少个0决定,所以括号中有--rank。
问题现在转变成,求一个位数和n一样的数的字典序。这个是很容易求的。枚举位数,求位数为t的字典序在这个数前的数。只要前缀小于num前缀那些就是。
class Solution {
int get_rank(int num, int n, int n_d){
int rank = 0;
//使num和n同位
while(num < n_d){
--rank;
num *= 10;
}
if(num > n) ++rank;
while(num){
// - (n_d - 1)那些位数不等于当前位数的减掉1到 n_d - 1共n_d - 1个数
rank += min(num, n) + 1 - n_d;
n_d /= 10;
num /= 10;
}
return rank;
}
public:
int findKthNumber(int n, int k) {
int n_digit = 1, d = 1;
int tmp = n;
while(tmp /= 10){
++n_digit;
d *= 10;
}
int num = 0;
for(int i = 0;i < n_digit; ++i){
int j = 0;
if(num == 0) j = 1;
for(; j <= 9; ++j)
if(get_rank(num * 10 + j, n, d) > k) break;
if(j == 0) break;
num = num * 10 + j - 1;
}
return num;
}
};
446. Arithmetic Slices II - Subsequence
题意:给定一个数组,问其中子序列为等差数列的有多少个(三个数或以上)?
题解:设dp[i][j]表示以A[i]为结尾的且差为j的等差数列的个数(包括2个数的)。那么枚举上一个元素A[k],就可以了。由于差的范围很大,所以用unorder_map存储,保证O(1)的平均存取时间。
class Solution {
public:
int numberOfArithmeticSlices(vector& A) {
int n = A.size();
if(n < 3) return 0;
vector> dp(A.size());
int ans = 0;
for(int i = 0;i < n; ++i)
for(int j = 0;j < i; ++j){
if((long)A[i] - (long)A[j] > INT_MAX || (long)A[i] - (long)A[j] < INT_MIN) continue;
int diff = A[i] - A[j];
dp[i][diff] += 1;
if(dp[j].find(diff) != dp[j].end()){
dp[i][diff] += dp[j][diff];
ans += dp[j][diff];
}
}
return ans;
}
};
466. Count The Repetitions
题意:定义一个串S=[s,n]表示S由n个s拼接。s2能由s1获得表示s1删除某些元素后能够得到数
。现给S1=[s1,n1],S2=[s2,n2],求一个最大的m使得S=[S1,m]能由S2获得。
题解:倍增dp。定义dp[i][k]表示从字符串s1的第i位开始匹配2^k个s2串需要的最短长度。
dp[i][k] = dp[ i, k - 1] + dp[i + dp[i][k - 1]) % len1,k - 1];
class Solution {
string str1,str2;
int len1,len2,n1,n2;
long long dfs(vector>& dp,int i,int k){
long long &res = dp[i][k];
if(res) return res;
return res = dfs(dp, i, k - 1) + dfs(dp,(i + dp[i][k - 1]) % len1,k - 1);
}
void init(vector>& dp){
for(int i = 0;i < len1; ++i){
int l1 = i,l2 = 0;
while(l2 < len2){
while(l1 < n1 * len1 && str1[l1 % len1] != str2[l2]) ++l1;
++l1; ++l2;
}
dp[i][0] = l1 - i;
}
}
public:
int getMaxRepetitions(string s1, int n1, string s2, int n2) {
vector> dp(101,vector(31,0));
str1 = s1;
str2 = s2;
len1 = s1.length(), len2 = s2.length();
this->n1 = n1;
this->n2 = n2;
init(dp);
dfs(dp,0,30);
long long ans=0;
long long begin = 0;
for(int k = 29; k >= 0; --k)
if((begin + dp[(begin % len1)][k]) <= n1 * len1){
ans += (1 << k);
begin += dp[(begin % len1)][k];
}
return ans / n2;
}
};
leetcode中有人贴出答案如下,更加优雅。稍微解释下:
rest[i]表示i个s1最多能匹配到的s2的下一个未匹配位置。
rapport[i]表示i个s1能匹配多少个s2
b是产生第一个循环的开始,使用s1的个数
last是下个循环开始,使用s1的个数
interval是一个循环需要的s1个数
idea是找出匹配过程中产生的循环节。因为s2至多用s2.length()个s1就能完全匹配,所以s2.length() + 1个s1肯定能产生循环节。
class Solution {
public:
int getMaxRepetitions(string s1, int n1, string s2, int n2) {
vector rapport(102,-1);
vector rest(102,-1);
int b=-1;int posRest=0;int rap=0;
int last=-1;
rapport[0]=rest[0]=0;//case when n=0
for(int i=1;i<=s2.size()+1;i++){
int j;
for(j=0;j=0)break;
}
int interval=last-b;
if(b>=n1)return rapport[n1]/n2;
return ((n1-b)/interval*(rapport[last]-rapport[b])+rapport[(n1-b)%interval+b])/n2;
}
};
458. Poor Pigs
题意:假设有 n
只水桶,猪饮水中毒后会在 m
分钟内死亡,你需要多少猪(x
)就能在 p
分钟内找出 “有毒” 水桶?这 n
只水桶里有且仅有一只有毒的桶。
题解:信息论解法,
一共有n个桶,哪个桶有有毒药,包含log(n)的信息量。每一只猪可以测试的次数是 floor(minutesToTest / minutesToDie)。
每只猪有t+1种状态,在某次死亡或存活到最后带信息量最多为log(t+1)。假设需要num只猪,则有num*log(t+1)>=log(n)
所以答案就是ceil(log(buckets) / log(minutesToTest / minutesToDie + 1))
class Solution {
public:
int poorPigs(int buckets, int minutesToDie, int minutesToTest) {
return ceil(log(buckets) / log(minutesToTest / minutesToDie + 1));
}
};
460. LFU Cache
题意:设计一个数据结构,满足以下的需求( Least Frequently Used (LFU) )。容量为capacity。get(key)返回对应value(不存在返回-1)。put(key,value)插入key,value对,如果容量已满,删除最近最少使用次数的key和对应的数据,如果多个次数一样多,删除最后使用时间最早的那个。
题解:可以对不同频次使用不同的list存储数据,每个list维护一次先后顺序。由于频次次数可能很大,不能预先用数组存,所以可以用一个hashmap来存。hashmap将频次映射到一个list。因为还需要对key索引到val中取,所以还需要一个hashmap保存一下key到哪个list以及list中的哪个指针(我用一个hashmap多层嵌套模板产生了模板嵌套编译错误,暂时改不了,所以我采用了别人的代码,和我的想法基本一致的,它用了两个hashmap,一个用来存频数,一个用来存list中的指针位置)。注意到最小频次如果有新元素插入就是1,没有的话,可以随着每次更新跟着更新即可。
class LFUCache {
int min_count;
int capacity;
//key 的频数
unordered_map key_to_count;
//每个频数对应的一个list(key,val)
unordered_map>*> count_to_queue;
//key对应的list中的指针位置
unordered_map>::iterator> cache;
//更新key的频次,和对应的list
//也就是更新key_to_count中的频次
//更新count_to_queue对应位置(存在则先删除然后在频次+1的位置中插入)
//更新cache
void update(int key, int value) {
int count;
list>::iterator data_it;
//key不存在
if(cache.find(key) == cache.end()) {
count = 1;
min_count = 1;
}
else {
count = key_to_count[key] ;
auto it = cache[key];
auto q = count_to_queue[count];
q->erase(it);
if(q->empty()) {
delete q;
count_to_queue.erase(count);
if(count == min_count)
min_count++;
}
count++;
}
list> *q;
if(count_to_queue.find(count) == count_to_queue.end())
q = new list>;
else
q = count_to_queue[count];
q->push_back({key,value});
data_it = --q->end();
count_to_queue[count] = q;
key_to_count[key] = count;
cache[key] = data_it;
}
//删除频次最低且访问最早的(最低频次的列表头)
void evict() {
auto q = count_to_queue[min_count];
int victim_key = q->front().first;
q->pop_front();
if(q->empty()) {
delete q;
count_to_queue.erase(min_count);
}
cache.erase(victim_key);
key_to_count.erase(victim_key);
}
public:
LFUCache(int cap) {
capacity = cap;
}
int get(int key) {
if(cache.find(key) == cache.end())
return -1;
int value = cache[key]->second;
update(key, value);
return value;
}
void put(int key, int value) {
if(capacity <= 0)
return;
if(cache.find(key) == cache.end() && cache.size() == capacity)
evict();
update(key,value);
}
};
472. Concatenated Words
题意:给出一些字符串,输出那些可以被其他串组成的串
题解:对每个串做动态规划。设dp[i]表示这个串从0到位置i - 1能不能由其他串组成。
class Solution {
public:
vector findAllConcatenatedWordsInADict(vector& words) {
unordered_set s(words.begin(), words.end());
vector res;
for (auto w : words) {
int n = w.size();
vector dp(n + 1,false);
dp[0] = true;
for (int i = 0; i < n; i++) {
if (!dp[i]) continue;
for (int j = i + 1; j <= n; j++) {
if (j - i < n && s.count(w.substr(i, j - i))) dp[j] = true;
}
if (dp[n]) { res.push_back(w); break; }
}
}
return res;
}
};
474. Ones and Zeroes
题意:给定一系列只有0和1的串(一个string数组),m,n表示有m个0n个1.用这么多个1和0可以最多组成这些串中多少个?每个0或1只能用一次
题解:动态规划dp[i][j][k]表示前i个串用j个0k个1最多组成多少个串,转移方程易得。然后用滚动数组就少了一维的内存。
class Solution {
public:
int findMaxForm(vector& strs, int m, int n) {
if(strs.size() == 0) return 0;
int **dp = new int*[m + 1];
for(int i = 0;i <= m; ++i){
dp[i] = new int[n + 1];
// memset(dp[i], (n + 1) * sizeof(int), 0);
for(int j = 0;j <= n; ++j) dp[i][j] = 0;
}
int *oness = new int[strs.size()];
int *zeros = new int[strs.size()];
//memset(oness, strs.size() * sizeof(int), 0);
//memset(zeros, strs.size() * sizeof(int), 0);
for(int i = 0; i < strs.size(); ++i){
oness[i] = zeros[i] = 0;
}
for(int i = 0;i < strs.size(); ++i){
for(int j = 0;j < strs[i].length(); ++j){
if(strs[i][j] == '0')
++zeros[i];
else
++oness[i];
}
}
int ans = 0;
for(int i = 0;i < strs.size(); ++i){
for(int j = m; j >= 0; --j){
if(zeros[i] > j) break;
for(int k = n;k >= 0; --k){
if(oness[i] > k) break;
dp[j][k] = max(dp[j - zeros[i]][k - oness[i]] + 1,dp[j][k]);
ans = max(ans, dp[j][k]);
}
}
}
for(int i = 0; i <= m; ++i)
delete [] dp[i];
delete [] dp;
delete [] oness;
delete [] zeros;
return ans;
}
};
479. Largest Palindrome Product
题意:给定一个n。求两个n位数相乘得到的最大回文数 题解:枚举回文数的一半,然后判断是不是能分解成两个n位数相乘。或者直接打表即可
//可以先找到最大的回文数,再检查能否被分解,如果不能就再找仅次于这个回文数的小回文
或者打表
class Solution {
public:
int largestPalindrome(int n) {
//打表
int ans[] = {9, 987, 123, 597, 677, 1218, 877, 475};
return ans[n - 1];
if(n==1) return 9;
int upper = pow(10, n) - 1;
int lower = pow(10, n - 1);
for (int i = upper; i > lower; --i) {
long long cand = creatPalindrome(i);
for (int j = upper; cand / j < j; --j) {
if (cand % j == 0) return cand % 1337;
}
}
return -1;
}
private:
long long creatPalindrome(int n) {
string lastHalf = to_string(n);
reverse(lastHalf.begin(), lastHalf.end());
return stoll(to_string(n) + lastHalf);
}
};
480. Sliding Window Median
题意:求连续k个数的中值
题解:用multiset记录,因为它删除和插入,原迭代器不会失效,所以只要适当调整中间的迭代器即可。O(nlog(k)
class Solution {
public:
vector medianSlidingWindow(vector& nums, int k) {
multiset window(nums.begin(), nums.begin() + k);
auto mid = next(window.begin(), k / 2);
vector medians;
for(int i = k;i < nums.size() ; ++i){
medians.push_back((double(*mid) + *prev(mid, 1 - k % 2)) / 2);
window.insert(nums[i]);
if (nums[i] < *mid) --mid;
if (nums[i - k] <= *mid) ++mid;
window.erase(window.find(nums[i - k]));
}
medians.push_back((double(*mid) + *prev(mid, 1 - k % 2)) / 2);
return medians;
}
};
483. Smallest Good Base
题意:给一个数,问它能以最小的几进制表示成全是1。
题解:枚举位数,二分进制即可。
class Solution {
public:
string smallestGoodBase(string n) {
unsigned long long num = (unsigned long long)stoll(n);
unsigned long long ans = num - 1,last = num - 1;
if(num == 1uLL) return string("2");
for(int i = 62;i >= 1; --i){
if(1uLL << (i - 1) >= num) continue;
unsigned long long left = 2,right = min(ans,(unsigned long long)(pow(num, 1.0 / i ) + 1));
while(left <= right){
unsigned long long mid = (right - left) / 2 + left;
unsigned long long cur = 1,power = 1;
for(int j = 0;j < i; ++j){
power *= mid;
cur += power;
}
if(cur == num){
ans = min(mid,ans);
break;
}else if(cur < num){
left = mid + 1;
}else{
right = mid - 1;
}
}
}
return to_string(ans);
}
};
493 Reverse Pairs
题意:给定一个 长度不超过5w的int数组。求其中(i,j)满足i < j 且 a[i] > 2 * a[j]的对的数目
题解:显然这个和逆序对差不多。所以容易想到分治的算法,这个比较容易,无需多说。除了采用分治,像这种结构的问题,还可以用树状树枝或者线段树解决。具体的,先增加n个数,分别是原来数字的两倍,然后对数组加索引标记(原来和加两倍的标记同一个),然后从大到小排序(两个合并一起排序)。然后逐一访问。如果遇到是原数字两倍的,则执行查询操作,否则进行插入操作,见下面代码(树状树枝)。
应该注意的是乘以2的时候溢出的问题。
class Solution {
public:
int getans(int left, int right, vector& nums){
if(right - left <= 1) return 0;
size_t mid = (right + left) / 2;
int ans = 0;
ans = getans(left, mid, nums);
ans += getans(mid, right, nums);
size_t ind = mid;
for(size_t i = left; i < mid; ++i){
while(ind < right && nums[i] > 2LL * nums[ind]){
ans += (mid - i) ;
++ind;
}
}
size_t indL = left,indR = mid;
vector tmp;
for(size_t i = left; i < right; ++i){
if(indL >= mid) tmp.push_back(nums[indR++]);
else if(indR >= right) tmp.push_back(nums[indL++]);
else if(nums[indL] < nums[indR]) tmp.push_back(nums[indL++]);
else tmp.push_back(nums[indR++]);
}
for(size_t i = left; i < right; ++i){
nums[i] = tmp[i - left];
}
return ans;
}
int reversePairs(vector& nums) {
return getans(0, nums.size(), nums);
}
};
树状数组的代码
class Solution {
public:
int *tree;
struct Node{
long long value;
int index;
bool dflag;
Node(){};
Node(long long v, int id, bool f){
value = v;
index = id;
dflag = f;
}
bool operator < (const Node &b){
if(this->value == b.value){
if(this->index == b.index) return this->dflag;
return this->index > b.index;
}
else return this->value > b.value;
}
};
Node *node;
Solution(){
tree = new int[200000];
node = new Node[200001];
}
void init(size_t size){
memset(tree,sizeof(tree),0);
//memset(node,sizeof(node),0);
}
int lowbit(int x){
return x&(-x);
}
void add(int k, int n)
{
while(k <= n)
{
tree[k] += 1;
k += lowbit(k);
}
}
int sums(int k)
{
int sum = 0;
while(k)
{
sum += tree[k];
k -= lowbit(k);
}
return sum;
}
int reversePairs(vector& nums) {
if(nums.size() <= 1) return 0;
init(nums.size());
int total = 0;
int ans = 0;
for(size_t i = 0; i < nums.size(); ++i){
ans -= nums[i] < 0;
node[total++] = Node(nums[i], i + 1, 0);
node[total++] = Node(2LL * nums[i], i + 1, 1);
}
sort(node,node + total);
for(int i = 0; i < total; ++i){
if(node[i].dflag) ans += sums(node[i].index);
else add(node[i].index,total);
}
return ans;
}
};