About the way how to use Leetcode wisely for preparing the interview and the solutions to some Leetcode problems’. 怎样为准备面试机智聪明的刷题(高效刷题)以及一些题目的题解。 同 github 链接: https://github.com/jiangzhouwang/Leetcode-How-What
给定一个数字数组和一个目标数字 target,在数组中寻找2个下标点,使得指向的数字的和为 target 值
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> solution;
for (int i = 0; i < nums.size()-1; ++i){
for (int j = i+1; j < nums.size(); ++j){
if (nums[i] + nums[j] == target){
solution.push_back(i);
solution.push_back(j);
return solution; // 这一行可以不用写,因为第二个 for 循环是从 i+1开始,必然不会重复加入
}
}
}
return solution;
}
};
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> solution;
map<int, int> imap;
for (int i = 0; i < nums.size(); ++i){
imap.insert(pair<int, int>(nums[i], i));
}
for (int i = 0; i < nums.size();++i){
int diff = target-nums[i];
map<int,int>::iterator ite = imap.find(diff);
if (ite != imap.end() && ite->second!=i){
solution.push_back(i);
solution.push_back(ite->second);
return solution; // 不能少,否则会重复添加,或者直接这里 return
}
}
return solution;
}
};
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> solution;
map<int, int> imap;
for (int i = 0; i < nums.size(); ++i){
int diff = target - nums[i];
map<int,int>::iterator iter = imap.find(diff);
if (iter != imap.end()){
solution.push_back(iter->second);
solution.push_back(i);
return solution;
}
imap.insert(pair<int,int>(nums[i],i));
}
return solution;
}
};
给定两个表示倒序十位数字的list,如(2->4->3)和(5->6->4),代表342 和 465 两个数字,返回他们两个相加后的 list,即(7->0->8) 表示 807 数字。 给定语言中的前面有 NodeList 的定义。 题目思路清晰,坑点在于考虑全,两个 List 不同长度比较容易考虑到,可能少考虑的两种情况一种是 存在为空的 List,另一种是 (9->9) + (1) 要返回 (0->0->1),这里进位到最后要保留。
/**
* 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* ans=NULL;
ListNode** tmpnode = &ans;
int carry=0;
ListNode *p=l1,*q=l2;
int add1=0,add2=0,sum=0;
while(p!=NULL || q!=NULL || carry!=0){
if (p!=NULL){
add1 = p->val;
p = p->next;
}else
add1 = 0;
if (q!=NULL){
add2 = q->val;
q = q->next;
}else
add2 = 0;
sum = add1+add2+carry;
(*tmpnode) = new ListNode(sum%10);
carry = sum/10;
tmpnode = &((*tmpnode)->next);
}
return ans;
}
};
题意:寻找一个 string 中最长的内部不出现重复字符的子串的长度。如 “abcabcbb” 返回 3,“bbbb”返回 1。
思路:用一个数组每次初始全为0表示每个字符都没有出现,然后两层循环外层是循环给定string每个字符表示开始,内层循环从这个字符往后寻找还没有出现的字符,统计内层的长度来每次更新。
思考:我想到子串可能是空的,但是没注意此题有个大坑点,就是这个 string 居然可以是一个空格,我惊了,也就是 " ", 返回 1。 本来我想用 set 判断是否出现重复字符,后来发现不行,回顾下来应该也是把空格放进去出问题了,因为我设置的是 set,所以改用了数组,一开始是 alpha[s[temp]-‘A’]=1 来把出现的字符设置为 1,但是也是由于空格类字符的缘故,就出现报错说 index是负的多少,超过了数组的范围,所以改用直接int 得到 ascill 码。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
if (s.size() == 0)
return 0;
int beginindexofsubstring=0; // 存储子串的开始下标
int maxnumofsubstring=0;
while (beginindexofsubstring < s.size()){
int temp = beginindexofsubstring;
int alpha[270] = {0}; // 初始所有字母均没有
int cnt=0;
int ascii= s[temp];
while(temp<s.size() && alpha[ascii] == 0){
alpha[ascii] = 1;
temp++;
ascii= s[temp];
cnt++;
}
maxnumofsubstring = max(maxnumofsubstring, cnt);
beginindexofsubstring = beginindexofsubstring+1;
}
return maxnumofsubstring;
}
};
给定两个本身已经排序的数列,求得两个数列合并后的中位数。 定义奇数个数的数列中位数为最中间数,偶数个的为中间两个数字的算数平均数。 且题目要求算法复杂度不超过 O(log(m+n))。 解:直接两个合并后用 sort,再返回中位数即可。不过太取巧了
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
for (int i = 0; i< nums1.size(); ++i){
nums2.push_back(nums1[i]);
}
int length=nums2.size();
sort(nums2.begin(), nums2.begin()+length);
if (nums2.size() & 1 ==1){ //奇数
return nums2[length/2];
}else{
return (nums2[length/2]+nums2[length/2-1])*1.0/2;
}
}
};
题意:给定一个小写字母组成的字符串,求其最长的回文子串是什么。例子 "babad"返回 bab, aba 之一即可。
解答:因为题目说只有 1000 大小,所以暴力可解。我是参考了dicuss 中的,得到了滑动窗口的解法。即 index 从头到尾滑动,以 index 作为子串的中心,先设定头尾两个指针同 index,接着判断 尾+1是否与 尾指针指向的 字符相同,相同则尾指针++,之后判断 头尾相加是否相同,是则头减尾加。
class Solution {
public:
string longestPalindrome(string s) {
// slidding window solution
int maxlen=0,index=0,start=0,end=0,ansstart=0;
while(index < s.size()){
start=index;
end=index;
while(end < s.size() && s[end]==s[end+1])
end++;
index = end+1;
while(start-1>=0 && end+1<s.size() && s[start-1]==s[end+1]){
start--;
end++;
}
if (end-start+1 > maxlen){
ansstart=start;
maxlen=end-start+1;
}
}
string anss;
return s.substr(ansstart,maxlen);
}
};
翻转数字,给定一个 int 型数字把它整体左右翻转并返回还为int型,注明如果超出 int 的范围则返回 0。 解:为了处理超过int范围怎么弄,故用 long / long long 来处理 //4 ms, faster than 69.82%; 8.2 MB, less than 95.24%
class Solution {
public:
int reverse(int x) {
long long ans = 0;
while (x != 0){
ans = ans*10+x%10;
x /= 10;
}
if (ans > INT_MAX || ans < INT_MIN)
return 0;
return ans;
}
};
题意:实现字符串中从左起第一个有效数字字符串到整型数字的转变。 若是空串或全是空格则返回 0,若左起第一个非空格字符非有效数字字符串如" w123"则返回 0。 解:本题题意简单,但是我做的比较费力,我是先把所有该返回 0 的都返回,然后再开始计算。实际并不需要的。 解法一是我做出来的模拟,但是很复杂,其实不必要。
class Solution {
public:
int myAtoi(string str) {
if (str.size() == 0) return 0;
int index = 0;
while (index < str.size() && str[index] == ' ')
index++; // 到第一个不是空白的位置
if (index == str.size()) // 直接到了尾,即 string 全是空白 ' '
return 0;
if (index == str.size()-1 && !('0'<=str[index] && str[index]<='9')) // index 指向最后一个位置,且还不是数字时, 返回 0
return 0;
if ((str[index] == '-' || str[index] == '+') && !('0'<=str[index+1]&&str[index+1]<='9')) //index指向+/-号,且后一位不是数字时,返回 0
return 0;
if (!(('0'<=str[index]&&str[index]<='9')||(str[index]=='-')||(str[index]=='+'))) //index 指向的是非数字,非正负号时,返回 0
return 0;
//接下来index 指向的位置就只能是数字 或 + - 数字要注意范围
int posiornega = 1;
if (str[index] == '-'){
posiornega = -1;
index++;
}else if (str[index] == '+'){
posiornega = 1;
index++;
}
// 这里需要处理 -0000123 => 123 0023=>23 +0023=>23 注意 -000 +000 0000 都返回 0
while (index < str.size() && str[index]=='0')
index++;
if (index == str.size())
return 0;
//到这里把前导零的给处理了, 开始正式处理数字
long long num = 0;
while (index < str.size() && ('0'<=str[index]&&str[index]<='9')){
num = num*10+ str[index]-'0';
++index;
if (num*posiornega>=INT_MAX) return INT_MAX;
if (num*posiornega<=INT_MIN) return INT_MIN;
}
return num * posiornega;
}
};
int myAtoi(string str) {
int ret = 0, sign = 1, i = str.find_first_not_of(' '), base = INT_MAX / 10;
if (str[i] == '+' || str[i] == '-') sign = str[i++] == '+' ?: -1;
while (isdigit(str[i])) {
if (ret > base || (ret == base && str[i] - '0' > 7))
return sign > 0 ? INT_MAX : INT_MIN;
ret = 10 * ret + (str[i++] - '0');
}
return sign * ret;
}
题意:给定两个 string s 和 p,其中 s 纯由小写字母组成,p 中有点 . 和星 * 符号,其中点可以代替1个任意字母,星则可以把星号前一个字符进行任意次重复,题意判断 s 和 p 是否匹配,返回 true 或 false。如 “ab” 和 .* 匹配,因为 * 可以把 . 再重复一次。 “aab” 和 c*a*b 可以匹配。题目没说的情况是两个字符串可以为空。 难题
解法:这道题首先要明白是 dp 问题,然后关键在于看清楚最优子结构。使用 dp[i][j] 来表示 s 字符串从开头到 s[i] 的子串和 p 字符串从开头到 p[j]的子串是否匹配,注意加上头行和头列,dp[i][j]实际是 s[i-1]和p[j-1]比较,注意初始化头行和头列时 p 可以使用 * 来匹配空串。则若i-1,j-1此时指向的2个字符相同或j-1指向点号,则当前匹配成功,dp[i][j]结果为dp[i-1][j-1]的值,即 if(s[i-1] == p[j-1] || p[j-1] ==’.’) dp[i][j]=dp[i-1][j-1] ; 若j-1指向星号,由于星号可以让星号前一个位置出现任意次,那么若出现0次即前面这个符号就直接没了,此时 dp[i][j]=dp[i][j-2],在此基础上,若星号表示出现1次则相当于此星号j-1指向位置为空,就需要判断i-1,j-2各指向字符相同或j-2指向为点号情况下,前一个结果或运算上dp[i-1][j],这里一行写出来即为 dp[i][j] = dp[i][j-2] | (dp[i-1][j] && (s[i-1] == p[j-2] || p[j-2] == ‘.’))。 这里给出 aab 和 .*a*b 的dp 数组,1 为 True, 0 为 false:
\ | 空 | . | * | a | * | b |
---|---|---|---|---|---|---|
空 | 1 | 0 | 1 | 0 | 1 | 0 |
a | 0 | 1 | 1 | 1 | 1 | 0 |
a | 0 | 0 | 0 | 1 | 1 | 0 |
b | 0 | 0 | 0 | 0 | 0 | 1 |
思考:建议遇到这种题就拿纸笔画一下二维的横纵列,多试几组不同数据。 另外在做这个题发现自己的知识漏洞,见下面总结
!逻辑非 && 逻辑与 || 逻辑或 , 顺序是 非与或。 &按位与, | 按位或。 设 a=0xaa, b=0x55 则 a&&b=true, a||b=true, a&b=0x0,a|b=0xff。 “&&”跟“|”没有关系,在用“||”的地方一般也可以用|代替,但是用“|”的地方不能用“||”代替。但是注意,运算符顺序是 算数运算>移位运算>位运算>逻辑运算。 1 << 3 + 2 & 7等价于 (1 << (3 + 2))&7,所以这道题中一个式子有多个进行运算,就不能把 | 和 || 替代,除非加括号。
题意:题目给定一个非负数数列,保证至少有两个数字。数字代表高度,任取两个数字,较小的数字表示的高度乘以两个数字下标距离表示一个蓄水池的横截面面积,求这个数列中能得到的最大面积是多少。例子:[1,8,6,2,5,4,8,3,7] 返回 49。
解:第一个反应是暴力,两次循环,每次更新最大的面积。 看了Discuss才知道两指针法,即一个指向头一个指向尾,计算一次面积后更新最大面积,并使得数字小的那个指针往对面走一个位置,直到两个指针指向同样位置结束
class Solution {
public:
int maxArea(vector<int>& height) {
int front=0,back=height.size()-1,maxamout=0;
while(front != back){
maxamout = max(maxamout, (back-front)*min(height[front],height[back]));
if (height[front] < height[back])
++front;
else
--back;
}
return maxamout;
}
};
罗马数字转10进制数字。给定罗马数字转换规则和一个字符串,求转换后的 10 进制数字。 Easy 题目,纯模拟。
class Solution {
public:
int romanToInt(string s) {
int alpha[26] = {0};
alpha['I'-'A']=1; alpha['V'-'A']=5; alpha['X'-'A']=10; alpha['L'-'A']=50;
alpha['C'-'A']=100; alpha['D'-'A']=500; alpha['M'-'A']=1000;
int sum = 0;
int i = 0;
while (i < s.size()){
if (s[i] == 'I'){
if (i+1<s.size() && (s[i+1] == 'V' || s[i+1] == 'X')){
if (s[i+1] == 'V')
sum += 4;
else
sum += 9;
i+=2;
}else{
sum += 1;
i++;
}
}else if (s[i] == 'X'){
if (i+1<s.size() && (s[i+1]=='L'||s[i+1]=='C')){
if (s[i+1]=='L')
sum+=40;
else
sum+=90;
i+=2;
}else{
sum+=10;
i++;
}
}else if (s[i] == 'C'){
if (i+1<s.size() && (s[i+1]=='D'||s[i+1]=='M')){
if (s[i+1] == 'D')
sum+=400;
else
sum+=900;
i+=2;
}else{
sum += 100;
i++;
}
}else{
sum += alpha[s[i]-'A'];
i++;
}
}
return sum;
}
};
寻找给定几个字符串的相同的前缀子字符串。 Easy 题目,模拟,相当于模拟每列字符是否相同,所以两层循环,外层是第一个字符串的字符,内层是对其同列的对应位置其他字符串的字符比较。
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
string ans;
if (strs.size() == 0) return ans;
for (int j = 0; j < strs[0].size(); ++j){
char ch = strs[0][j];
bool flag = true;
for (int i = 1; i < strs.size(); ++i){
if (j >= strs[i].size() || strs[i][j] != ch){
flag = false;
break;
}
}
if (flag == true) ans+=ch;
else break;
}
return ans;
}
};
题目:给定一个数字序列,输出所有 3 个数字之和为 0 的数字组合。如 [-1, 0, 1, 2, -1, -4],target=0,输出为 [[-1,0,1],[-1,-1,2]]。注意不能重复输出。
解析:是第一题2 Sum 的扩展。两层循环可解。2Sum 是一层循环可解。3Sum 就是先sort 后,外层要循环每个出现的不重复数字,里层循环是剩下的右边序列中是否存在两个值之和为 0-外层循环的数字。 用两个指针一前一后往中间走。
我这里错的:1,没有考虑数字序列可能不足 3个数字要返回空序列 2,防止 [-2,0,0,2,2] 输出两次 [-2,0,2]所以要在里层相同后 ++front 和 --back 后还要判断继续 ++ 和 --。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans;
if (nums.size()<3)
return ans;
sort(nums.begin(),nums.end());
for (int indexbegin = 0; indexbegin < nums.size()-2; ++indexbegin){
if (indexbegin>0 && nums[indexbegin]==nums[indexbegin-1])
continue;
if (nums[indexbegin]+nums[indexbegin+1]+nums[indexbegin+2]>0)
break;
if (nums[indexbegin]+nums[nums.size()-2]+nums[nums.size()-1]<0)
continue;
int front = indexbegin+1, back = nums.size()-1;
int tmp = 0-nums[indexbegin];
while (front < back){
if (nums[front]+nums[back] == tmp){
ans.push_back({nums[indexbegin],nums[front],nums[back]});
++front;
while(nums[front]==nums[front-1] && front<back) //必须要有,防止 [-2,0,0,2,2] 输出两次 [-2,0,2]
++front;
--back;
while(nums[back]==nums[back+1]&&front<back)
--back;
}else if (nums[front]+nums[back] < tmp){
front++;
}else{
back--;
}
}
}
return ans;
}
};
题目:手机9宫格输入键盘上 2~9 每个数字是对应好几个字母的,问给定一个数字序列,输出所有可能的对应字母组合,不要求按特定顺序返回。如输入 “23” 输出 [“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”] 。
思路:DFS,当此时 index 大于长度则返回上层,当此时 index 等于长度则添加构建好的 string,否则 对此时对应的所有字符 在此层加入到构建 string 中并+1 层往下 DFS 。这里我把定义函数写在了最后面 }; 后面是不行的,要写在里面。其次是要注意定义函数的 vector & ans,这里要有 &表示是要改变它的。
class Solution {
public:
vector<string> letterCombinations(string digits) {
vector<string> ans;
if (digits.size()==0)
return ans;
map<char,string> mapchar = {{'2',"abc"},{'3',"def"},{'4',"ghi"},{'5',"jkl"},{'6',"mno"},{'7',"pqrs"},{'8',"tuv"},{'9',"wxyz"}};
string tmp="";
int ii=5;
dfs(ans,digits,0,tmp,mapchar);
return ans;
}
void dfs(vector<string> &ans, string digits, int index, string inputstring, map<char,string> mapchar){
if (index == digits.size()){
return;
}
string stemp = mapchar[digits[index]];
for (int i = 0; i < stemp.size(); ++i){
if (index == digits.size()-1)
ans.push_back(inputstring+stemp[i]);
else
dfs(ans,digits,index+1,inputstring+stemp[i],mapchar);
}
}
};
给定一个链表和数字n,求删除倒数第 n 个数字后的链表。思路:用2个指针指向的位置隔着 n个空,这样一遍循环靠尾的指针到头时,靠头指针指向的就是要删除的,而为了删除它,所以还需要第 3 个指针指向它的前面。另外需要注意判断删除的正好是第一个位置时情况。
/**
* 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*p=head,*q=head,*before=head;
if (!head)
return head;
while(n>1){
q = q->next;
n--;
}
int cnt=1;
while(q->next != NULL){
q = q->next;
p = p->next;
if (cnt!=1)
before = before->next;
else
++cnt;
}
if (cnt!=1)
before->next = p->next;
else
head = head->next;
return head;
}
};
题目判断一个由 {}()[] 6 种字符组成的序列是否是合法的。“合法的定义是 开型括号必须由相同类型闭括号关闭 且 开型括号的关闭顺序必须按照一定顺序” 。 补充几个 false 的例子: “)))” “((((” “(]” “)(” 。本题是学数据结构的栈时的经典例题,简单。但是我这里测试时出问题,是在栈为空的时候还取 brackets.top(),这种会报错。
class Solution {
public:
bool isValid(string s) {
stack<char> brackets;
for (int i = 0; i < s.size(); ++i){
if (s[i] == '(' || s[i]=='[' || s[i]=='{'){
brackets.push(s[i]);
}else{
if (s[i] == ')' && !brackets.empty() && brackets.top()=='(')
brackets.pop();
else if (s[i] == ']' && !brackets.empty() && brackets.top()=='[')
brackets.pop();
else if (s[i] == '}' && !brackets.empty() && brackets.top()=='{')
brackets.pop();
else
brackets.push(s[i]);
}
}
return brackets.empty();
}
};
题目给定两个已排序好的链表,返回两个链表合并后的排序链表。题目分类是 Easy。 思路比较简单:在两个链均表不空下,把两个中较小的节点插入新链表中,若之一为空则插入另一个的指向位置。链表的操作还是要熟悉。比如 ListNode ans(0); ListNode *tmp=&ans; 最后返回时是 return ans.next; 而不是 return ans->next,因为 ans 是一个节点而不是一个指针。
/**
* 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) {
ListNode ans(0);
ListNode *tmp=&ans;
while(l1 != NULL || l2 != NULL){
if (l1 == NULL){
tmp->next = l2;
break;
}
if (l2 == NULL){
tmp->next = l1;
break;
}
if (l1->val < l2->val){
tmp->next = l1;
l1 = l1->next;
}else{
tmp->next = l2;
l2 = l2->next;
}
tmp = tmp->next;
}
return ans.next;
}
};
给定数字 n,生成 n 对 “(“和”)“组成的所有有效括号。如 n= 3, [”((()))”,"(()())","(())()","()(())","()()()"] ; n=2下 ["(())","()()"] 。 有 DP 解法和递归解法。DP 解法是 vector< vector > dp(n+1, vector()); dp[0] = “”; dp[i]=’(’+ dp[k]+’)’+dp[i-1-k],k=0…i-I 递归解法是传递 2 个参数 l 和 r,在 l 大于 0 时添加 ’ ( ’ 并 --l ,再 l < r 时传递’ ) ’ 并 --r,在 l 和 r 都为 0 时添加此时的 string
class Solution {
public:
vector<string> generateParenthesis(int n) {
vector<string> ans;
dfs(n,n,"",ans);
return ans;
}
void dfs(int l, int r, string path, vector<string> &ans){
if (l==0 && r==0){
ans.push_back(path);
return;
}
if (l>0)
dfs(l-1,r,path+"(",ans);
if (l<r)
dfs(l,r-1,path+")",ans);
}
};
给定一个已经排好序且存入 vector
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
int size = lists.size();
if (size == 0)
return NULL;
if (size == 1)
return lists[0];
while (size>1){
for (int i = 0; i < size/2; ++i)
lists[i] = merge2Lists(lists[i], lists[size-i-1]);
size = (size+1)/2;
}
return lists[0];
}
ListNode* merge2Lists(ListNode *list1,ListNode *list2){
if (list1==NULL)
return list2;
if (list2==NULL)
return list1;
ListNode *l,*ltmp;
if (list1->val <= list2->val){
ltmp = new ListNode(list1->val);
list1 = list1->next;
}else{
ltmp = new ListNode(list2->val);
list2 = list2->next;
}
l = ltmp;
while (list1!=NULL && list2!=NULL){
if (list2==NULL || list1->val<=list2->val){
ltmp->next = new ListNode(list1->val);
ltmp = ltmp->next;
list1 = list1->next;
}
if (list1==NULL || list2->val<=list1->val){
ltmp->next = new ListNode(list2->val);
ltmp = ltmp->next;
list2 = list2->next;
}
}
if (list1!=NULL)
ltmp->next = list1;
else
ltmp->next = list2;
return l;
}
};
在从小到大排好序的序列中去掉重复数字,并同时返回不重复数字有几个。 感觉题目一开始没有说清,还是靠下面解释和 run 时候的样例比较才明白,实际中去掉重复数字是把原序列的不重复的放到序列前面去。那两个指针就可以。从左端开始,两个指针,一个走得快,一个在开始位置,走得快的和慢的比数字大小,相同则快的往前走,不同则复制到慢的那里。 简单题目。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if (nums.size() == 0) return 0;
if (nums.size() == 1) return 1;
int left = 0, right = 1;
while (right < nums.size()){
while (right < nums.size() && nums[right] == nums[left])
right++;
if (right == nums.size())
break;
left++;
nums[left] = nums[right];
right++;
}
return left+1;
}
};
实现在一个字符串中搜索另一个字符串首次出现的下标位置函数。两层循环暴力 4 ms, faster than 91.53%
class Solution {
public:
int strStr(string haystack, string needle) {
int haylen = haystack.size(), needlen = needle.size();
if (needlen == 0) return 0;
if (needlen > haylen) return -1;
int ans = 0,i=0;
for (; i <= haylen-needlen; ++i){
int j = 0;
for (; j < needlen && haystack[i+j] == needle[j]; ++j);
if (j == needlen)
break;
}
return i == haylen-needlen+1?-1:i;
}
};
不用除法操作、取余操作、乘法操作,求两个整数相除结果,已保证被除数不为 0,如果结果超过 int 范围,则返回 INT_MAX。 解:我想到可能直接减会 TLE,然后想的是每次减的数字是之前被减数加上自己的值,即 17/3 , 17-3, 14-6, 8-3。但是没有这么做,看了两个题解,解法一是同样的,不过用了位运算左移代替了自加本身。 第二种方法则是用了 log()函数,想法是 a/b = e^(ln(a)) / e^(ln(b)) = e^(ln(a)-ln(b))
class Solution {
public:
int divide(int dividend, int divisor) {
if (dividend == INT_MIN && divisor == -1) return INT_MAX;
long absdivid = abs((long)dividend), absdivisi = abs((long)divisor), ans= 0;
int sign = (dividend>0 ^ divisor>0)?-1:1;
while (absdivid >= absdivisi){
long tmp = absdivisi, mod=1;
while (tmp<<1 <= absdivid){
tmp <<= 1;
mod<<=1;
}
absdivid -= tmp;
ans+=mod;
}
return ans*sign;
}
};
给定一个数字序列,求这个序列的下一个排列组合。即字典序的下一个排列组合,如果它本身已经是最大的,那就返回最小的排列组合。 [1,2,3]->[1,3,2] ,[3,2,1]->[1,2,3]。 解析:从后往前找到第一个正向看是数字升序的下标位置,它就是要往后调的,需要跟它互换调整的是后面所有大于它的数字中最小的那个,这个下标位置之后所有数字再排序即可。两次提交都 WA 是没有正确写出“找到所有大于它的数字中最小的那个数字”,改正即 AC
class Solution {
public:
void nextPermutation(vector<int>& nums) {
bool ischanged = false;
int minabovei=0,pos=0;
for (int i = nums.size()-2; i >= 0; --i){
if (nums[i]>=nums[i+1])
continue;
minabovei = nums[i+1];
pos=i+1;
for (int index = i+2; index < nums.size(); ++index){
if (minabovei > nums[index] && nums[index] > nums[i]){
minabovei = nums[index];
pos = index;
}
}
swap(nums[pos],nums[i]);
sort(nums.begin()+i+1,nums.end());
ischanged = true;
break;
}
if (!ischanged)
sort(nums.begin(),nums.end());
}
};
给定一个只由’(‘和’)‘组成的序列,求子序列中有效合法的括号对最长长度多少。如输入 “)()())”,返回 4。看了他人的提交,有 DP 和 栈 的做法。DP做法是dp[i]存储从 0 到 i 子序列最长有效合法括号对长度,所以 if (s[i-1]==’)’) dp[i] = 2+dp[i-dp[i]]+(s[i-2]==’)’?dp[i-1]:0); 注意这里 dp 是增加了头 dp[0],所以dp[i] 对应的是 s[i-1]。 栈的做法是维护栈存储数字,数字表示意义为子串中一段序列的长度,具体根据不同可以是 一串’(‘或一串’)'的长度,或一段合法括号序列的长度。栈为空时就 push 进数字,不为空则用此时下标位置减去栈顶数字更新最大值 ans,最后输出 ans。
class Solution {
public:
int longestValidParentheses(string s) {
int cnt = 0;
int dp[s.size()+1] = {0};
for (int i = 1; i <= s.size(); ++i){
if (s[i-1] == '(')
cnt++;
else{
if (cnt > 0){
--cnt;
dp[i] = 2 + (s[i-2]==')'?dp[i-1]:0); //加上前面一位若有‘)’的 dp 值
dp[i] += dp[i-dp[i]]; // 加上前面有效的括号对值
}
}
}
return *max_element(dp,dp+s.size()+1);
}
};
class Solution {
public:
int longestValidParentheses(string s) {
stack<int> istack;
istack.push(0); // 这里不能少,否则")()())",会因为第一个')'而 runtime error,在于栈为空时取栈顶
int ans=0;
for (int i = 0; i < s.size(); ++i){
if (s[i] == '('){
istack.push(i+1);
}else{
istack.pop();
if (!istack.empty()){
ans = max(ans, i+1-istack.top());
}else{
istack.push(i+1);
}
}
}
return ans;
}
};
给定一个数字序列,原本是从小到大排好序的,现在可能从某个位置旋转了,比如 [4,5,6,7,0,1,2],现在给定一个数字,求这个数字是否在数字序列中,要求时间复杂度必须在 O(logn)。 二分法,不过二分的时候就要判断是否旋转了。在一个旋转的序列中,一定存在第二段最右边数字必然小于第一段最左数字,所以每次二分时用中间值nums[mid]和nums[r]比较,若大于,则必然旋转了且旋转的位置在此时 mid 的右边,这个时候拿要比较的值判断是否在右边如果是,则 l = mid+1,不是则 r = mid-1,判断条件是 比最右数字小于等于或比中间数字大; 前面二分那里若小于,则必然没有旋转,那判断值是否在右半段,条件是小于等于最右,大于中间,是则 l = mid+1,否则 r= mid-1
class Solution {
public:
int search(vector<int>& nums, int target) {
int l=0,r=nums.size()-1;
if (nums.size() == 0)
return -1;
while (l <= r){
int mid = (l+r)/2;
if (nums[mid] == target)
return mid;
if (nums[mid] > nums[r]){
if (target>nums[mid] || target <= nums[r]){
l = mid+1;
}else {
r = mid-1;
}
}else{
if (target<=nums[r] && target > nums[mid])
l = mid+1;
else
r = mid-1;
}
}
return -1;
}
};
给定一个正序排好序的数字序列和一个数字 target,求这个数字在序列中开始和最后分别两个下标位置,如果没有就返回 [-1,-1]。题目要求时间复杂度为 O(logn)。 用二分,求小于等于目标值的最大值的位置和 大于等于目标值的最小值的位置。
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int start=0,end=nums.size()-1;
vector<int> ans;
int mid,leftans=-1,rightans=-1;
while(start <= end){
mid = (start+end)/2;
if (nums[mid] > target){
end = mid-1;
}else if (nums[mid] < target){
start = mid+1;
}else{
leftans = mid;
end = mid-1;
}
}
start=0,end=nums.size()-1;
while (start <= end){
mid = (start+end)/2;
if (nums[mid] > target){
end = mid-1;
}else if (nums[mid] < target){
start = mid+1;
}else{
rightans = mid;
start = mid+1;
}
}
ans.push_back(leftans);
ans.push_back(rightans);
return ans;
}
};
判断一个 99 的数独格子是否“有效”,有效即每行出现的在 1~9 之间且每个只出现一次,每列也是,99 分为的 9 个 3*3 的各自也是如此,只判断有效,不用解。 模拟即可, 8 ms, faster than 98.71% 。 9.3 MB, less than 100.00%
class Solution {
public:
bool isValidSudoku(vector<vector<char>>& board) {
int num[11] = {0};
bool isokay = true;
for (int i = 0; i < 9; ++i){ // 横向
memset(num,0,sizeof(num));
for (int j = 0; j < 9; ++j){
if(board[i][j] == '.')
continue;
if (num[board[i][j]-'0'] != 0){
return false;
}else
num[board[i][j]-'0']++;
}
}
for (int j = 0; j < 9; ++j){
memset(num,0,sizeof(num));
for (int i = 0; i < 9; ++i){
if(board[i][j] == '.')
continue;
if (num[board[i][j]-'0'] != 0){
return false;
}else
num[board[i][j]-'0']++;
}
}
for (int ii = 0; ii <= 6; ii+=3){
for (int jj = 0; jj <= 6; jj+=3){
memset(num,0,sizeof(num));
for (int i = ii; i < ii+3; ++i){
for (int j = jj; j < jj+3; ++j){
if (board[i][j] == '.') continue;
if (num[board[i][j]-'0'] != 0)
return false;
else
num[board[i][j]-'0']++;
}
}
}
}
return true;
}
};
读序列,假设一开始是 “1” 那么读它的时候是 1 个 1,故组成 11,而对 11读它是 2个1故是 21,而对 21读它是 1 个 2 和1 个 1,故组成 1211。接下来是 111221,以此类推。 那么给定 n 求第 n 个输出什么。 模拟就可以了
class Solution {
public:
string countAndSay(int n) {
string s = "1";
if (n == 0 || n == 1) return "1";
int count = 1;
while (count < n){
int repeat=1;
int i = 1;
string pbstring = "";
while (i < s.size()){
while(i < s.size() && s[i] == s[i-1]){
repeat++;
i++;
}
pbstring = pbstring+ to_string(repeat) +s[i-1];
repeat = 1;
i++;
}
if (i == s.size()){ // 加上最后的尾巴
pbstring = pbstring + "1"+s[i-1];
}
s = pbstring;
count++;
}
return s;
}
};
给定一个正数的数字序列和一个正数 target,求所有的总数字和为 target 的子序列情况。序列中的数字可以不止一次使用,原数列中数字不重复。 如 [2,3,6,7] ,target=7, 返回 [[7], [2,2,3]]。 解析:递归/DFS/回溯/DP。 我自己想法是排序后从后往前,重复target-这个数字直到差值小于这个数字,看是否在数列中。但这是有问题的,即前面例子的[2,2,3]是得不到的。如果改为每次减完都判断是否在序列中则可能重复了。 所以改为了 DFS 方法,把 target-candidate[i]往下DFS, DFS 中是对从当前index位置开始到最后的进行 DFS。
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
vector<vector<int>> ans;
vector<int> bepushedvector;
dfs(ans,bepushedvector,candidates,target,0);
return ans;
}
void dfs(vector<vector<int>> &ans, vector<int> &bepushedvector, vector<int> candidates, int target, int index){
if (target < 0)
return;
if (target == 0){
ans.push_back(bepushedvector);
return;
}
for (int i = index; i < candidates.size(); ++i){
bepushedvector.push_back(candidates[i]);
dfs(ans, bepushedvector, candidates, target-candidates[i],i);
bepushedvector.pop_back();
}
}
};
给定一个未排序的整数数字序列,找到最小的缺失的正整数。 如 [1,2,0]->3 ; [3,4,-1,1]->2; [7,8,9,11,12]->1。 题目要求使用 O(n) 时间, constant 空间。我的方法是做了 sort,再找,还用到了 set,其实并不符合要求。 看了 Discuss 想明白的正解,把每个在[0,len]的数字num放在对应下标位置num-1上,之后再一遍循环即可,这里虽然写了两层循环的,但执行起来是 O(n)时间的。
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
sort(nums.begin(),nums.end());
if (nums.size() == 0 || nums[nums.size()-1]<=0)
return 1;
int minnum = nums[0], maxnum = nums[nums.size()-1];
if (nums[0]>1)
return 1;
int index = 1;
set<int> iset;
for (int i = 0; i < nums.size(); ++i)
iset.insert(nums[i]);
while (index < nums[nums.size()-1]){
if (iset.find(index)==iset.end())
return index;
index++;
}
return index+1;
}
};
class Solution
{
public:
int firstMissingPositive(vector<int>& A)
{
for (int i = 0; i < A.size(); ++i){
while (A[i]>0 && A[i]<=A.size() && A[A[i]-1]!=A[i])
swap(A[i], A[A[i]-1]);
}
for (int i = 0; i < A.size(); ++i)
if (A[i] != i+1)
return i+1;
return A.size()+1;
}
};
给定一个数字序列表示相应高度的板,问能蓄积多少水。和 11 题类似,11 题问的是两个板最大能截出多大面积。这题思路和那道一样都是两个指针各自从前后往中间靠拢。用 lower 记录每次前后位置中矮的那个值, level 记录当前最高水平面,所以是 max(level,lower),然后 ans += level-lower 即为存储水容量
class Solution {
public:
int trap(vector<int>& height) {
int ans = 0;
int lower=0,l=0,r=height.size()-1,level=0;
while (l < r){
lower = height[height[l]<height[r]?l++:r--];
level = max(level,lower);
ans += level-lower;
}
return ans;
}
};
给定一个由小写字母组成的字符串s 和一个由小写字母、"?"、"*" 组成的字符串p。 '?'只可以匹配一个字母,"*"可以匹配任意单个或连续字符也可不出现。 则求问 s 和 p 是否可以匹配成一样的。 要树立意识,跟2个字符串匹配,某种模式相关的很多都可以用 dp,设置一个二维数组dp[i,j]表示[0,i)和[0,j\)然后写出来结果,多写几组,看规律就能找到转移方程,有时甚至可以压缩到一维的空间上去。 这道题的我用 DP 做的,我是 dp[0][0]表示的s[0]与 p[0],所以特殊处理二维 dp 数组横着行和竖着列,如果多加1竖列和1横行,会简单些。 // 32 ms, faster than 72.37% 10.2 MB, less than 50.00%
class Solution {
public:
bool isMatch(string s, string p) {
if (s.size()==0 && p.size()==0) return true; //两字符串同时空
if (p.size()==0) return false; // 待处理 p 为空 && s 不为空
if (s.size()==0){ //s 为空且待处理 p 不为空
for (int i = 0; i < p.size(); ++i)
if (p[i] != '*')
return false;
return true;
}
bool dp[p.size()][s.size()];
memset(dp,0,sizeof(dp));
for (int j = 0; j < s.size(); ++j){ // 处理第一行
if (p[0] == '*'){
dp[0][j] = true;
}else{
if (s[j] == p[0] || p[0] == '?'){
if (j == 0){
dp[0][j] = true;
}else{
dp[0][j] = false;
}
}else{
dp[0][j] = false;
}
}
}
int countvalidchar = (p[0]=='*'?0:1);
for (int i = 1; i < p.size(); ++i){ //处理第一列
if (p[i] == '*'){
dp[i][0] = dp[i-1][0];
}else{
countvalidchar++;
if (s[0] == p[i] || p[i] == '?'){
if (p[i-1] == '*' && countvalidchar==1)
dp[i][0] = dp[i-1][0];
else
dp[i][0] = false;
}else{
dp[i][0] = false;
}
}
}
for (int i = 1; i < p.size(); ++i){
for (int j = 1; j < s.size(); ++j){
if (p[i] == s[j] || p[i] == '?')
dp[i][j] = dp[i-1][j-1];
else if (p[i] == '*'){
dp[i][j] = dp[i][j-1]||dp[i-1][j];
}else
dp[i][j] = false;
}
}
return dp[p.size()-1][s.size()-1];
}
};
给定一个不重复数字的序列,求其所有的排列组合。如[1,2,3] 返回 [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,2,1],[3,1,2]]。 方法:回溯/递归,这里我没有想到,回溯法是基于深度搜索的尝试。思路是深搜时输入参数 index 表示指向不重复数字序列的下标位置,然后从 index 到结尾进行循环,其中操作就是把 nums[index]和循环到的交换,再往 index+1下面递归,为了生成所有排列组会,在这个递归结束后的下一行需要再交换回来,这样才能使循环到下一个数字时前面没有变动过。
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> ans;
backtracking(ans,nums,0);
return ans;
}
void backtracking(vector<vector<int>>&ans, vector<int>&nums, int index){
if (index == nums.size())
ans.push_back(nums);
for (int i = index; i < nums.size(); ++i){
swap(nums[i], nums[index]);
backtracking(ans,nums,index+1);
swap(nums[i], nums[index]);
}
}
};
给定一个 N*N 的数字表,把所有数字顺时针整体转置90 度。题目要求不能新建 2 维数组赋值来做。 解:找规律,可以看到任意(i,j),取代它的数字是逆时针方向的位置为 (n - 1 - j, i) ,所以可以通过这个公式连着找到和 (i,j)相关的 3 个 index,因此直接互换它们 4 个。所以思路是从最外层到里层,对没处理过的 (i,j) 直接轮换其相关位置。
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int size = matrix[0].size();
for (int i = 0; i < size/2; ++i){
for (int j = i; j < size-1-i; ++j){
// the counterclockwise element position that replace (i,j) is (n - 1 - j, i)
int tmp = matrix[i][j];
matrix[i][j] = matrix[size-1-j][i];
matrix[size-1-j][i] = matrix[size-1-i][size-1-j];
matrix[size-1-i][size-1-j] = matrix[j][size-1-i];
matrix[j][size-1-i] = tmp;
}
}
}
};
题意: 给定含多个 string 的 vector,这些 string 中可能有的是 同字母异序词 [词的字母顺序变换后组成的新词],所有同字母异或词组成一个 vector,返回所有的这些 vector。 如输入 [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”],输出[[“bat”],[“ate”,“eat”,“tea”],[“nat”,“tan”]],输出顺序无关。
思路: 这里需要用到 hash,把同字母异或词 hash到一起,因此也就需要map
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
vector<vector<string>> ans;
map<string, vector<string>> anagrams2diffstring;
for (int i = 0; i < strs.size(); ++i){
string stmp = strs[i], s=strs[i];
sort(stmp.begin(), stmp.end());
anagrams2diffstring[stmp].push_back(s);
}
for (auto i:anagrams2diffstring){
vector<string> vectmp = i.second;
sort(vectmp.begin(), vectmp.end());
ans.push_back(vectmp);
}
return ans;
}
};
实现 x 的 n 次方。 这里注意的是 n 的范围有正负,x 有正负。直接算会 TLE 方法 可参考 29,不用除法、乘法而实现除法。基本相同 4 ms, faster than 60.71% / 8.4 MB, less than 62.50%
class Solution {
public:
double myPow(double x, int n) {
if (n == 0) return 1;
long long powernum = abs((long long)n);
double ans = 1;
int count = 0;
while (powernum>0){
long long powertmp = 1;
double numtmp=x;
while (powertmp<<1 <= powernum){
powertmp <<= 1;
numtmp *= numtmp;
}
powernum -= powertmp;
ans *= numtmp;
}
if (n<0) return 1.0/ans;
return ans;
}
};
给定数字序列,寻找最长连续子序列使其和最大。DP 可解, dp[i]表示的是从前面某个数字起到下标为 i 的数字的连续总和值,转移方程为 dp[i] = max(nums[i]+dp[i-1], nums[i])。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int ans[nums.size()+1];
ans[0] = nums[0];
int maxnum = nums[0];
for (int i = 1; i < nums.size(); ++i){
ans[i] = max(ans[i-1]+nums[i], nums[i]);
if (maxnum < ans[i])
maxnum = ans[i];
}
return maxnum;
}
};
把一个数字组成的二维数组中所有数字按照从外到内的顺时针顺序输出。像是贪吃蛇一样。 中等题目,只在于判断已经遍历过以及是否超过范围要仔细写。 坑点:题目中的 mn 数字是 1~mn 的,我就开了一个一维数组下标直接表示数字存储其是否遍历过,结果提交 WA 才发现原来二维数组中并不一定存储 1~m*n 的数字。所以用了二维数组同大小判断相应位置是否遍历过。 4 ms, faster than 60.49% / 8.5 MB, less than 100.00%
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> ans;
if (matrix.size() == 0 || matrix[0].size()==0) return ans;
int dx[] = {0,+1,0,-1}; // 右下左上
int dy[] = {+1,0,-1,0};
int row = matrix.size(), col = matrix[0].size();
bool isvisited[row][col];
memset(isvisited, false, sizeof(isvisited));
int xx = 0, yy = 0, dir=0, count=0;
while (isvisited[xx][yy]==false){
isvisited[xx][yy]=true;
++count;
ans.push_back(matrix[xx][yy]); // 没有遍历过,这个时候把它加进去
if ((row<=xx+dx[dir] || xx+dx[dir]<0) || (col<=yy+dy[dir] || yy+dy[dir]<0) || (isvisited[xx+dx[dir]][yy+dy[dir]] == true)){
//如果超出了范围 或者 已经遍历过了 那么 dir 应该加 1,并更新xx 和 yy
dir = (dir+1)%4;
}
xx += dx[dir]; yy += dy[dir];
if (count == row*col)
break;
}
return ans;
}
};
给定一个非负数数字序列,每个数字代表从当前位置往后能跳多少步,问从第一个格子能跳到最后格子么。[3,2,1,0,4] ->false。解析:贪心可解。用一个变量记录最远能到达的下标位置。如果其 >= n-1,则 true,因此每一步都要更新这个值。
class Solution {
public:
bool canJump(vector<int>& nums) {
int maxlen = nums[0];
for (int i = 1; i < nums.size(); ++i){
if (maxlen < i)
return false;
if (maxlen >= nums.size()-1)
return true;
maxlen = max(maxlen,i+nums[i]);
}
return true;
}
};
合并间隔。给定很多间隔组成的集合,把所有间隔中有重叠的地方都合并。[[1,3],[2,6],[8,10],[15,18]]->[[1,6],[8,10],[15,18]]。 思路是先把给定的间隔排序保证后一个间隔的起始点不小于前一个间隔起始点。从第二个开始,若它和前面间隔有重叠则改变它的前后值成新的值,若不重叠则把前一个间隔加入结果 ans 中,循环结束后再把最后一个间隔加入。
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
if (intervals.size() <= 1)
return intervals;
sort(intervals.begin(), intervals.end());
vector<vector<int>> ans;
for (int i = 1; i < intervals.size(); ++i){
if (intervals[i][0] > intervals[i-1][1]){
ans.push_back(intervals[i-1]);
continue;
}
intervals[i][0] = intervals[i-1][0];
intervals[i][1] = max(intervals[i-1][1], intervals[i][1]);
}
ans.push_back(intervals[intervals.size()-1]);
return ans;
}
};
给定一个nm 格子,小人在左上角,只能向右或向下走,问走到右下角有多少种可能。解析:我一开始想到的是排列组合,但是后来没有想通没有计算出,于是列了nm 的格子填数发现是 DP。于是按照 DP 做了,做完之后回看数字发现可以乘积并除数字,然后凑出了组合数字 C(m+n-2,n-1)。凑出来之后我就想明白了,m+n-2是 (m-1)+(n-1)代表的是下箭头数量和上箭头数量的和,意思就是从总共箭头数量的格子中选 n-1个格子出来放一种箭头。
class Solution {
public:
int uniquePaths(int m, int n) {
int dp[n][m] = {{0}};
for (int i = 0; i < n; ++i){
for (int j = 0; j < m; ++j){
if (i==0 || j == 0){
dp[i][j] = 1;
continue;
}
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[n-1][m-1];
}
};
class Solution {
public:
int uniquePaths(int m, int n) {
int N = m+n-2, D = n-1;
long long ans = 1;
for (int i = 1; i <= D; ++i){
ans = ans*(N-D+i)/i;
}
return ans;
}
};
给定一个含有非负数字的二维数组,求左上角点到右下角点的所有路径中,总和数字最小的路径的和是多少。 解法:典型的DP问题。设dp二维数组存储到这个点时最小的和是多少。则转移公式 dp[i][j] = min(dp[i-1][j],dp[i][j-1])+nums[i][j]。我的解法中直接在原来nums上处理了,并未开新的空间。
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int row = grid.size(), col = grid[0].size();
for (int i = 0; i < row; ++i){
for (int j = 0; j < col; ++j){
if (i==0 && j==0)
continue;
if (i == 0 && j > 0){
grid[i][j] = grid[i][j-1]+grid[i][j];
continue;
}
if (j == 0 && i > 0){
grid[i][j] = grid[i-1][j] + grid[i][j];
continue;
}
grid[i][j] = min(grid[i-1][j],grid[i][j-1])+grid[i][j];
}
}
return grid[row-1][col-1];
}
};
给定一个数字数组,表示一个非负数数字,使其加一,再返回。 类似于 String 的数字+1,还简单了,只是非负数 0 ms, faster than 100.00% , 8.6 MB, less than 77.05%
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
int num[digits.size()+1] = {0};
int carry=0,sum=0;
vector<int> ans;
num[digits.size()] = (1+digits[digits.size()-1])%10;
carry = (1+digits[digits.size()-1])/10;
for (int i = digits.size()-2; i >= 0; --i){
num[i+1]=(carry+digits[i])%10;
carry=(carry+digits[i])/10;
}
if (carry == 1)
ans.push_back(1);
for (int i = 1; i <= digits.size(); ++i)
ans.push_back(num[i]);
return ans;
}
};
给定一个 非负数,实现求 sqrt()函数,返回平方根向下去整的整数。 //20 ms, faster than 14.91%, 8.2 MB, less than 98.25%
class Solution {
public:
int mySqrt(int x) {
long long ans = 0;
while (ans*ans <= x){
ans++;
}
return ans-1;
}
};
上台阶问题。每次可以选择上1个台阶或2个台阶。问到达第N级台阶总共有多少种情况。 解:典型的DP问题。用dp数组记录到达这个台阶时总共可能的情况数。则 dp[1]=1,dp[2]=2; 转移公式dp[i]=dp[i-1]+dp[i-2]。意思是到达前一个台阶的数直接一步迈过来+到达2个前的台阶数直接2步迈上来。这个公式实际也是裴波那切公式。为了求这个值,还可以用到矩阵的乘积,可以降低到时间复杂度 O(log)。同理DP的还有贴瓷砖问题。
class Solution {
public:
int climbStairs(int n) {
int dp[n+1] = {0};
dp[1] = 1;
if (n > 1)
dp[2] = 2;
for (int i = 3; i <= n; ++i)
dp[i] = dp[i-1]+dp[i-2];
return dp[n];
}
};
求一个英文词到另一个词的最小编辑距离,3 种操作:对一个词插入一个字母/删除一个字母/替换一个。解析:典型的 DP 问题,用 dp[i][j]记录A词的下标为 i 的字母和B词的下标为 j 的字母的最小编辑距离。 转移公式是 word1[i] == word2[j]时 dp[i][j] = dp[i-1][j-1] (因为这两个字母相同,所以直接等于之前的即可) word1[i] != word2[j] 时 dp[i][j] = min(min(dp[i-1][j], dp[i][j-1]), dp[i-1][j-1])+1。 然后记得 i == 0 || j == 0时初始化下即可。
class Solution {
public:
int minDistance(string word1, string word2) {
int dp[word1.size()+1][word2.size()+1] = {{0}};
for (int i = 0; i <= word1.size(); ++i){
for (int j = 0; j <= word2.size(); ++j){
if (i == 0){
dp[i][j] = j;
continue;
}
if (j == 0){
dp[i][j] = i;
continue;
}
if (word1[i-1] == word2[j-1]){
dp[i][j] = dp[i-1][j-1];
}
else
dp[i][j] = min(min(dp[i-1][j-1],dp[i-1][j]),dp[i][j-1])+1;
}
}
return dp[word1.size()][word2.size()];
}
};
把二维数字数组中所有为 0 的所在横轴和纵轴所有元素都变为 0。要求是尽量压缩空间,甚至到常数级。 解法:常数级做法就是把二维数组的第一行和第一列作为标记列,这样当出现 0 时就把其所在的行头和列头置为 0,然后再循环处理出现的行头和列头的 0。不过这里要特别注意 matrix[0][0]。因为它的下标决定了它既是第一列的列头也是第一行的行头,所以用两个变量记录对于这个特殊的位置是,横向处理还是纵向处理。 44 ms, faster than 94.06% 11.5 MB, less than 70.37%
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int firstnumzerobroadwise = 0, firstnumzerovertical=0; //表示对matrix[0][0]是横向还是纵向
for (int i = 0; i < matrix.size(); ++i){
for (int j = 0; j < matrix[0].size(); ++j){
if (matrix[i][j] == 0){
if (i == 0) firstnumzerobroadwise = 1;
if (j == 0) firstnumzerovertical = 1;
matrix[0][j] = 0;
matrix[i][0] = 0;
}
}
}
for (int i = 1; i < matrix.size(); ++i) //判断第一列中除 matrix[0][0]外
if (matrix[i][0] == 0)
for (int j = 0; j < matrix[i].size(); ++j) // 走横向
matrix[i][j] = 0;
for (int j = 1; j < matrix[0].size(); ++j) // 判断第一行中除 matrix[0][0]外
if (matrix[0][j] == 0)
for (int i = 0; i < matrix.size(); ++i) // 走纵向
matrix[i][j] = 0;
if (firstnumzerobroadwise == 1) // 如果 matrix[0][0]==0为走横向的
for (int j = 0; j < matrix[0].size(); ++j)
matrix[0][j] = 0;
if (firstnumzerovertical == 1) // 如果 matrix[0][0]==0为走纵向的
for (int i = 1; i < matrix.size(); ++i)
matrix[i][0] = 0;
}
};
给定一个只由0,1,2组成的数字序列分别表示红色,白色,蓝色。要求这个序列最后成为所有相同颜色紧邻,且序列颜色顺序是红色白色蓝色。题目要求一次循环且只使用constant space原地进行处理。 解:荷兰旗问题。是由Dijkstra提出,由它祖国荷兰国旗颜色命名。解法是3个指针,前front指向最左边颜色的最右边界,后back指向最右边颜色的最左边界,和当前指针current指向当前位置初始指向最左,通过判断 current 指针指向的数字的值来判断该如何交换并 front 或 back 谁该怎么动。
class Solution {
public:
void sortColors(vector<int>& nums) {
int front = 0, current = 0, back = nums.size()-1;
while(current <= back){
if (nums[current] == 0){
swap(nums[front],nums[current]);
++front;
++current;
}else if (nums[current] == 2){
swap(nums[current], nums[back]);
--back;
}else{
++current;
}
}
}
};
给定两串字母组成的字符数组。求第二个 t 在第一个 s 中国出现的最小窗口是什么。最小窗口定义为一个连续子串,而 t 每一个字符均在窗口中出现过,且只能出现 1 次。题目要求是 O(n)时间复杂度。如"ADOBECODEBANC" 和 “ABC” 返回 “BANC”。我的解法一开始是外层循环从 s 头到尾,找到一个在时,再从这个位置往后找。 但这样时间复杂度大于 O(n),也 TLE 了。看了别人提交之后改成了两个指针分别指向滑动窗口的前后low和high,high指向从 0 往后,若发现指向的字符属于要找的,就放入 hashmap,同步更新长度,直到长度到了 t 长度时,这是 hashmap 肯定是大于或等于 t 长度,那么再更新 low 和 high 指向的位置,还要更新要求的最小窗口长度。 这就相当于一个不定长的滑动窗口在往右滑动中会先变大(因为要一直确认所有 t 中需要的字符都出现)再变小(更新窗口两端指针位置)。这样就是 O(n)复杂度了。
class Solution {
public:
string minWindow(string s, string t) {
string ans;
if (s.size()==0 || t.size()==0)
return ans;
map<char,int> hashmap;
map<char,int> window;
for (int i = 0; i < t.size(); ++i)
hashmap[t[i]]++;
int minlength = 0x3f3f3f3f;
int lettercount = 0;
for (int low = 0, high = 0; high < s.size(); ++high){
char ch = s[high];
if (hashmap.find(ch) != hashmap.end()){
window[ch]++;
if (window[ch] <= hashmap[ch])
lettercount++;
}
if (lettercount >= t.size()){
while (hashmap.find(s[low]) == hashmap.end() || window[s[low]] > hashmap[s[low]]){
window[s[low]]--;
low++;
}
if (high-low+1<minlength){
minlength = high-low+1;
ans = s.substr(low,minlength);
}
}
}
return ans;
}
};
给定一个不重复数字的集合,返回所有的子集情况。 输入[1,2,3]返回 [[],[1],[1,2],[1,2,3],[1,3],[2],[2,3],[3]] 。解析:方法有 递归回溯/ 循环迭代/位运算操作,第三种没有实现。
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> ans;
vector<int> tmpsub;
subset(nums,tmpsub,ans,0);
return ans;
}
void subset(vector<int>&nums, vector<int> &tmpsub, vector<vector<int>>&ans, int i){
ans.push_back(tmpsub);
for (int j = i; j < nums.size(); ++j){
tmpsub.push_back(nums[j]);
subset(nums,tmpsub,ans,j+1);
tmpsub.pop_back();
}
}
};
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> ans;
vector<int> tmpsub;
ans.push_back(tmpsub);
for (int i = 0; i < nums.size(); ++i){
int size = ans.size();
for (int j = 0; j < size; ++j){
tmpsub = ans[j];
tmpsub.push_back(nums[i]);
ans.push_back(tmpsub);
}
}
return ans;
}
};
给一个 二维字符数组和一个字符串,判断这个字符串是否可以存在于字符数组中,存在的定义为可以通过紧邻的字符一一对应上。 解:DFS 问题。从对上的第一个字符开始往四周进行 DFS 直到全部都对应上即为 True。 写的时候用了两种,一种是用 isvisit 二维数组来记录是否通过这个点以免出现遍历早已经过的点,另一种是不引入 isvisit 二维数组而是直接在原来二维字符数组中把经过的点改为 ‘#’,标记意义为已经通过这个点。 但两种方法都 MLE 了,后来两种中都加了两行就 AC 了,表示是已经遍历过此点或此时 flag 为 true 则返回。
class Solution {
public:
bool exist(vector<vector<char>>& board, string word) {
vector<vector<int>> isvisit(board.size()+2, vector<int>(board[0].size()+2,0));
bool flag = false;
for(int i = 0; i < board.size(); ++i){
for (int j = 0; j < board[0].size(); ++j){
if (board[i][j] == word[0]){ //如果第一个点和 word[0]相同且没有被遍历过就从word[0]开始dfs
dfs(i,j,0,board,word,isvisit,flag);
if (flag == true){
return true;
}
}
}
}
return false;
}
void dfs(int i, int j, int index, vector<vector<char>>& board, string word, vector<vector<int>>& isvisit, bool &flag){
if (isvisit[i][j] == '1' || flag) //这两行去掉就 MLE 了
return;
if (index == word.size()-1){
flag = true;
return;
}
isvisit[i+1][j+1] = 1; // 表示已经经过了这个点
if (i-1>=0 && board[i-1][j] == word[index+1] && isvisit[i][j+1] == 0)
dfs(i-1, j, index+1, board, word, isvisit, flag);
if (i<board.size()-1 && board[i+1][j] == word[index+1] && isvisit[i+2][j+1] == 0)
dfs(i+1, j, index+1, board, word, isvisit, flag);
if (j-1>=0 && board[i][j-1] == word[index+1] && isvisit[i+1][j] == 0)
dfs(i, j-1, index+1, board, word, isvisit, flag);
if (j<board[0].size()-1 && board[i][j+1] == word[index+1] && isvisit[i+1][j+2] == 0)
dfs(i, j+1, index+1, board, word, isvisit, flag);
isvisit[i+1][j+1] = 0;
return;
}
};
给定一个非负数的数字序列,每个数字表示其高度的长条,每个长条宽为1,求最大的覆盖最多的 条形部分的长方形的面积是多少。如 [2,1,5,6,2,3] 输出为 10。 我想到的方法是 DP,dp[i][j]表示从下标 i 到下标 j 部分能覆盖的最大面积,初始化是主对角线上值为 heights[i],转移中是斜向的,由主对角线斜向到右上角转移。Minnum[i][j]记录的是从 i 到 j 的最小高度,用来做计算用。 转移方程是:dp[i][j] = max(max(dp[i,j-1],dp[i+1][j]), minnum[i][j]*(i 到 j 的宽度));最终 Runtime Error 了,没过的样例是 [0,1,2,3,……,19999]。自己测试 [0,1,2,3,4,……,10]可以,感觉应该是 0 的原因,使得下标操作中可能过了数组的边界,但是没找到问题所在。还有就是这种方法并不是 O(n),而是 O(n^2)。 另一种则是使用栈,存储升序的高度,当不能存储进时来计算更新最大值。
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
if (heights.size() == 0)
return 0;
int size = heights.size();
int dp[size+1][size+1] = {{0}};
int minnum[size+1][size+1] = {{0}}; // 用来做乘操作
for (int interval = 0; interval < size; ++interval) {
for (int i = 0; i+interval < size; ++i){
if (interval == 0){
dp[i][i+interval] = heights[i];
minnum[i][i] = heights[i];
continue;
}
minnum[i][i+interval] = min(minnum[i][i+interval-1],minnum[i+1][i+interval]);
//dp[i][i+interval] = max(minnum[i][i+interval]*(interval+1),maxnum[i][i+interval]);
dp[i][i+interval] = max(max(minnum[i][i+interval]*(interval+1),dp[i][i+interval-1]), dp[i+1][i+interval]);
}
}
return dp[0][size-1];
}
};
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int ans=0;
int size = heights.size();
if (size == 0)
return ans;
stack<int> ascendingnumbers;
for (int i = 0; i <= size;){
int currentheight;
if (i == size)
currentheight = 0;
else
currentheight = heights[i];
if (ascendingnumbers.empty() || currentheight >= heights[ascendingnumbers.top()]){
ascendingnumbers.push(i);
++i;
}else{
int tmp = ascendingnumbers.top();
ascendingnumbers.pop();
int length = (ascendingnumbers.empty()? i : i-ascendingnumbers.top()-1);
ans = max(ans,length*heights[tmp]);
}
}
return ans;
}
};
给定一个由字符’0’或’1’组成的 2 维数组,只框住字符’1’的所有可能的矩形框中,最大的那个面积是多少。BFS,函数中从这个点往左上方进行计算。这样就能保证外层循环中把所有的都算上了。
class Solution {
public:
int maximalRectangle(vector<vector<char>>& matrix) {
if (matrix.size() == 0) return 0;
int row = matrix.size(), col = matrix[0].size(), maxans = 0;
for (int i = 0; i < row; ++i)
for (int j = 0; j < col; ++j)
if (matrix[i][j] == '1')
maxans = max(maxans, BFS(matrix,i,j));
return maxans;
}
int BFS(vector<vector<char>> &matrix, int ii, int jj){
int row = ii-1, maxarea = 0;
while (row >= 0 && matrix[row][jj] == '1')
row--;
for (int j = jj; j >= 0 && matrix[ii][j] == '1'; --j){
for (int i = row+1; i < ii; ++i)
if (matrix[i][j] == '0'){
row = max(row,i);
}
maxarea = max(maxarea, (jj-j+1)*(ii-row));
}
return maxarea;
}
};
给定两个从小到大排序的数字数组,数组 1 中有足够的空余位,把数组 2 合并到数组 1 中,且总体顺序从小到大。 正常解法是 2 个指针都指向两个尾端,然后比较后放较大的到该放的数组 1 的尾部。 还可以作弊做法用 sort。 两个指针方法:2 个指针 0 ms, faster than 100.00% 8.8 MB, less than 69.57%
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int index1=m-1,index2=n-1,tail=m+n-1;
while (index1>=0 && index2>=0){
if (nums1[index1]>=nums2[index2]){
nums1[tail] = nums1[index1];
tail--;
index1--;
}else{
nums1[tail] = nums2[index2];
tail--;
index2--;
}
}
while (index2>=0)
nums1[tail--] = nums2[index2--];
}
};
求一个给定正整数字符串能够组成多少种字母排列组合情况。 数字到字母的转化模式是 1-A, 2-B,……26-Z。 则 226 有 3 种,即2/26,22/6,2/2/6。 本题一看就可以类比爬台阶问题,都是当前位置取决于前一个位置+再往前推一个位置。即为 DP 问题。若 dp[i]表示序列到对应 i 位的种数,s[i]表示原序列 string 的第 i 位。则 dp[i]就是在 s[i-1]基础上多加一个数字的情况+s[i-2]基础上加上 s[i-1]s[i] 组成的 2 位数情况。但是注意有数字 0 的影响,它的影响在于若 s[i]位为 0,则就是 s[i-1]+'0’而 0 没有对应的字母故这种情况就不能组成了,还有就是 s[i-1]s[i]组成的 2 位数是否在[0,26]的范围,在内才能有值。故转移方程为 dp[i] = dp[i-1] + ((10<=num&&num<=26)?dp[i-2]:0) 。 我的代码写的不够优雅简单,可以更简短,把同样情况的合并。 4 ms, faster than 67.02% 8.6 MB, less than 58.82%
class Solution {
public:
int numDecodings(string s) {
int dp[s.size()+1] = {0};
if (s.size() == 0) return 0;
if (s.size() == 1){
if (s[0] == '0') return 0;
else return 1;
}
if (s[0] != '0')
dp[0] = 1;
if (s[1] != '0'){
if (s[0] == '0')
dp[1] = 0;
else
dp[1]=1;
}
string stint = "";
stint += s[0];
stint += s[1];
int num = stoi(stint); //把 s[0]s[1]组成的两位数字转成 int 型;
if (10<=num && num<=26 && dp[0]!='0')
dp[1]++;
for (int i = 2; i < s.size(); ++i){
if (s[i] == '0'){
if (s[i-1] == '1' || s[i-1] == '2')
dp[i] = dp[i-2];
else
dp[i] = 0;
}
else{
string stmp = "";
stmp += s[i-1];
stmp += s[i];
num = stoi(stmp);
dp[i] = dp[i-1] + ((10<=num&&num<=26)?dp[i-2]:0);
}
}
return dp[s.size()-1];
}
};
给定一个二叉树,求其中序遍历序列。 解析:递归方法是非常简单了。题目还问了用非递归,用的stack方法
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> ans;
inordersearch(root,ans);
return ans;
}
void inordersearch(TreeNode *root, vector<int> &ans){
if (root == NULL)
return;
inordersearch(root->left,ans);
cout << root->val;
ans.push_back(root->val);
inordersearch(root->right,ans);
}
};
给定数字 n,求 [1,2,3……,n] 这些数字能够构建多少种二分搜索树。 如输入 3 输出 5。这个是卡特兰数的一个应用。卡特兰数的另一个应用见前面 22 题,括号匹配的种数。
卡特兰数是一种组合数:1, 2, 5, 14, 42, 132, 429, 1430, …… 其满足递推式: h(0) =1, h(1)=1, 则 h(n) = h(0)*h(n-1)+h(1)*h(n-2)+…+h(n-1)h(0) (n>=2)。 另一种通项公式:h(n)=c(2n,n)-c(2n,n+1)(n=0,1,2,…)。 进一步化解:h(n)=C(2n,n)/(n+1)。
class Solution {
public:
int numTrees(int n) {
vector<long long>dp;
dp.push_back(1); //dp[0]=1
dp.push_back(1); //dp[1]=1
long long tmp;
// h(n)= h(0)*h(n-1)+h(1)*h(n-2) + … + h(n-1)h(0) (n>=2)
for (int i = 2; i<= n; ++i){
tmp = 0;
for (int j = 0; j < i; ++j){
tmp += dp[j]*dp[i-j-1];
}
dp.push_back(tmp);
}
return dp[n];
}
};
class Solution {
public:
int numTrees(int n) {
long long ans=1;
for (int i = 1; i<= n; ++i){
ans = ans*(2*n-(i-1))/i;
}
return ans/(n+1);
}
};
卡特兰数的一些应用:http://lanqi.org/skills/10939/
- 进出栈问题:一个足够大的栈的进栈序列为1,2,3,⋯,n时有多少个不同的出栈序列?(出栈入栈问题有许多的变种,比如n个人拿5元、n个人拿10元买物品,物品5元,老板没零钱。问有几种排队方式。熟悉栈的同学很容易就能把这个问题转换为栈。值得注意的是,由于每个拿5元的人排队的次序不是固定的,所以最后求得的答案要n!。拿10元的人同理,所以还要n!。所以这种变种的最后答案为h(n)*n!*n!)
- 二叉树构成问题:有n个结点,问总共能构成几种不同的二叉树。(这里说的是无序树)这道题出现在2015年腾讯实习生的在线笔试题中。
- 凸多边形的三角形划分。一个凸的n边形,用直线连接他的两个顶点使之分成多个三角形,每条直线不能相交,问一共有多少种划分方案。
- 有n+1个叶子的满二叉树的个数?
- 在n*n的格子中,只在下三角行走,每次横或竖走一格,有多少中走法?其实向右走相当于进栈,向上走相当于出栈,本质就是n个数出栈次序的问题,所以答案就是卡特兰数。
- 将一个凸n+2边形区域分成三角形区域的方法数?(答案是 n 的卡特兰数) 求 凸 n+2 边形用其 n−1 条对角线分割为互不重叠的三角形的分法总数
- 圆内连弦。圆周上有 2n 个点,以这些点为端点连互不相交的 n 条弦,求不同的连法总数。
- n 个数连乘,不同的乘法顺序总和有多少? 答案是看做有 n-1个括号,求 n-1个括号有多少种组合,即 n-1 的卡特兰数
给定一个树,判断其是否是二叉搜索树。二叉搜索树定义为 左侧子树所有结点值均小于根节点值,右侧子树所有结点值均大于根节点值,且左子树和右子树均为二叉搜索树。 解:二叉搜索树的定义就是递归定义的。所以可以用递归求解。注意千万不能用左孩子结点比根小且右孩子结点比根大来判断。 两种方法:1,左子树的最右节点比根小且右子树的最左结点比根大,递归求解。 2,中序遍历之后的结果是一个升序的数字序列即可。递归求解。 以下三种第一个是解法 1,第二和三为解法 2 的思路
class Solution {
public:
bool isValidBST(TreeNode* root) {
int min,max;
return isBST(root, min, max);
}
bool isBST(TreeNode* root, int &minp, int &maxp){
int lmin,lmax,rmin,rmax;
if (root == NULL)
return true;
lmin = lmax = root->val;
if (root->left){
if (isBST(root->left,lmin,lmax) == false)
return false;
if (root->val <= lmax)
return false;
}
rmin = rmax = root->val;
if (root->right){
if (isBST(root->right,rmin,rmax) == false)
return false;
if (root->val >= rmin)
return false;
}
minp = lmin;
maxp = rmax;
return true;
}
};
class Solution {
public:
bool isValidBST(TreeNode* root) {
TreeNode *prev = NULL;
return isBST(root, prev);
}
bool isBST(TreeNode* root, TreeNode*& prev){
if (root){
if (!isBST(root->left,prev))
return false;
if (prev != NULL && root->val <= prev->val)
return false;
prev = root;
return isBST(root->right, prev);
}
return true;
}
};
class Solution {
public:
bool isValidBST(TreeNode* root) {
bool flag = true;
int num = 1, current=0;
inorder(root,num,current,flag);
return flag;
}
void inorder(TreeNode* root, int &num,int ¤t,bool &flag){
if (root == NULL)
return;
inorder(root->left,num,current,flag);
if (num == 1){
current = root->val;
num++;
}else{
if (current < root->val)
current = root->val;
else{
flag = false;
return;
}
num++;
}
inorder(root->right,num,current, flag);
}
};
对称树。 判断一个树是不是关于自己中心线镜像对称的。 解:递归求解和非递归的迭代求解两种方法。递归求解就是写一个递归的函数,参数为 2 个节点,通过判断这两个节点往下递归判断。 非递归可以用栈和队列,思想就是每次拿出两个来判断是否均为空或均不为空时值相同,然后再往栈或队列中添加镜像对称的节点。 还有第3种方法,即中序遍历后的结果数字序列判断这个序列是不是对称的即可。
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (root == NULL)
return true;
return issym(root->left, root->right);
}
bool issym(TreeNode* l, TreeNode *r){
if (!l || !r)
return l == r;
else
return (l->val == r->val && issym(l->left,r->right) && issym(l->right,r->left));
}
};
class Solution {
public:
bool isSymmetric(TreeNode* root) {
queue<TreeNode*> qu;
if (root == NULL)
return true;
qu.push(root->left);
qu.push(root->right);
while (!qu.empty()){
TreeNode *r1 = qu.front();
qu.pop();
TreeNode *r2 = qu.front();
qu.pop();
if (!r1 && !r2)
continue;
if (!r1 || !r2)
return false;
if (r1->val != r2->val)
return false;
qu.push(r1->left);
qu.push(r2->right);
qu.push(r1->right);
qu.push(r2->left);
}
return true;
}
};
二叉树层序遍历。 题目要求是把给定的二叉树按照层序遍历并且把不同层放入不同 vector 中最组成二维数组。 解:层次遍历需要用的 queue 队列,而为了把同层的所有节点收集到,就需要一个标记,这里定义一个 TreeNode *tmp = new TreeNode(0x3f3f3f3f); 其值为一个很大的数,这样在while 循环中再通过一个 while 来判断还不到标记节点时就表示都是同一个层级中的。
思考:看了其他答案,可以直接 qu.push(NULL) ,直接加入 NULL 而不用再定义一个 TreeNode 的。 还有一种是不需要加入一个标记,因为在循环时先求一下队列的长度,这就是要加入的层的节点数,所以一个 for 循环加入这些就直接相当于该下一层了。 还有一种方法 前序遍历中参数增加一个深度 h,递归函数中判断此时深度是否大于 ans.size,是就新建vector 再 push_back 入结果 ans 中,如果不是直接就 ans[h].push_back() 即可。
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> qu;
TreeNode *tmp = new TreeNode(0x3f3f3f3f);
vector<vector<int>> ans;
if (root == NULL) return ans;
qu.push(root), qu.push(tmp);
while (!qu.empty()){
vector<int> tmplevel;
TreeNode *curr = qu.front();
qu.pop();
while (curr->val != 0x3f3f3f3f){ // 如果不是加入的层级标记,则都是这一层的
tmplevel.push_back(curr->val);
if (curr->left) qu.push(curr->left);
if (curr->right) qu.push(curr->right);
curr = qu.front();
qu.pop();
}
if (qu.size() > 0)
qu.push(tmp);
ans.push_back(tmplevel);
}
return ans;
}
};
给定一个二叉树,求层级遍历的zigzag 排序,第一层正着,第二层逆着,第三层又正…… 本来层级排序是用 queue,这个是用 deque,注意是左近还是右进。还有就是左孩子和右孩子谁先进。 8 ms, faster than 43.76% 13.5 MB, less than 79.07%
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
vector<vector<int>> ans;
if (root == NULL) return ans;
int num = 1;
deque<TreeNode*> ique;
ique.push_back(root);
bool forwardoutbackin = true;
while (ique.empty() == false){
int tmpnum=0;
vector<int> inlevelnum;
while (num--){
TreeNode* node;
if (forwardoutbackin == true){
node = ique.front();
ique.pop_front();
}else{
node = ique.back();
ique.pop_back();
}
inlevelnum.push_back(node->val);
if (forwardoutbackin == true){
if (node->left != NULL){
tmpnum++;
ique.push_back(node->left);
}
if (node->right != NULL){
tmpnum++;
ique.push_back(node->right);
}
}else{
if (node->right != NULL){
tmpnum++;
ique.push_front(node->right);
}
if (node->left != NULL){
tmpnum++;
ique.push_front(node->left);
}
}
}
ans.push_back(inlevelnum);
num = tmpnum;
forwardoutbackin = !forwardoutbackin;
}
return ans;
}
};
给定一个树求其深度。只有根设为 1。 解:前中后序遍历都可,层次遍历每层+1 也可。
class Solution {
public:
int maxDepth(TreeNode* root) {
int ans=0;
inorder(root,0,ans);
return ans;
}
void inorder(TreeNode* root, int depth, int &ans){
if (!root) return;
inorder(root->left, depth+1, ans);
if (ans < depth+1)
ans = depth+1;
inorder(root->right, depth+1, ans);
}
};
int maxDepth(TreeNode *root){
return root ? 1+max(maxDepth(root->left), maxDepth(root->right)) :0;
}
从前序和中序遍历中序列中构建树。 基于的方法是: 一段前序序列的第一个值是根,它在中序遍历中的位置决定中序中它左侧为其左子树,其右侧为其右子树。 递归函数求解.
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
return build(preorder, inorder, 0, preorder.size()-1, 0, inorder.size()-1);
}
TreeNode* build(vector<int>& preorder, vector<int>& inorder,int prebegin, int preend, int inbegin, int inend){
if (prebegin > preend)
return NULL;
TreeNode *root = new TreeNode(preorder[prebegin]);
int i;
for (i = inbegin; i <= inend; ++i)
if (preorder[prebegin] == inorder[i])
break;
root->left = build(preorder, inorder, prebegin+1,i-inbegin+prebegin, inbegin, i-1);
root->right = build(preorder, inorder,i-inbegin+prebegin+1, preend, i+1, inend);
return root;
}
};
把一个递增的数字序列转变为平衡二叉树。 递归。 一段序列的最中间值即为根,然后其左半部分递归找左子树的根,右半部分递归找右子树的根。 24 ms, faster than 67.19% 24.9 MB, less than 16.22%
/**
* 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* sortedArrayToBST(vector<int>& nums) {
return arraytobst(nums,0,nums.size()-1);
}
TreeNode* arraytobst(vector<int>& nums, int left, int right){
if (left > right) return NULL;
int mid = (left+right)/2;
TreeNode *ans = new TreeNode(nums[mid]);
ans->left = arraytobst(nums,left,mid-1);
ans->right = arraytobst(nums,mid+1,right);
return ans;
}
};
给定一个树,把它转为只有右孩子的树,从根到最右叶子的顺序为原树的前序遍历节点。 解:方法一:我的做法是非递归的用stack的前序遍历,记录前一个节点 pre,让 pre->right=当前节点。 方法二:递归函数,递归地把左子树添加到右子树上。注意这里写的倒像是后序遍历,操作中需要记录 TreeNode *right = root->right, 因为是把左子树的整体加过来之后的最右节点的右孩子为原来右子树的根。 方法三: 递归+ private记录一个 pre 前一个节点。递归处理左子树和右子树。
class Solution {
public:
void flatten(TreeNode* root) {
if (root == NULL)
return;
stack<TreeNode*> istack;
TreeNode *pre = root;
if (root->right != NULL)
istack.push(root->right);
if (root->left != NULL)
istack.push(root->left);
while(!istack.empty()){
TreeNode *tmp = istack.top();
istack.pop();
pre->left=NULL;
pre->right=tmp;
pre = tmp;
if (tmp->right != NULL)
istack.push(tmp->right);
if (tmp->left != NULL)
istack.push(tmp->left);
}
}
};
void flatten(TreeNode* root) {
if (root == NULL)
return;
flatten(root->left);
flatten(root->right);
if (root->left){
TreeNode *right = root->right;
root->right = root->left;
root->left = NULL;
while (root->right)
root = root->right;
root->right = right;
}
}
class Solution {
private: TreeNode *pre;
public:
void flatten(TreeNode* root) {
if (root == NULL)
return;
if (root->left != NULL){
flatten(root->left);
pre->right = root->right;
root->right = root->left;
pre->left = NULL;
root->left = NULL;
}
pre = root;
flatten(root->right);
}
};
给定一个完全二叉树(所有叶子在同层)。每个节点另有一个指针 next 初始都为 NULL。现在想要让 next 指向其同层右边的节点,每层最右边的 next指向的是 NULL。 题目要求只能使用常数级空间,递归是可以的,隐式空间的堆栈是不算入空间的。 解:这个题没有想出来,我想复杂了,实际上,由于初始所有next都为 NULL,则可以不管每层最右,然后观察情况,既然题目说到了递归,那就往递归想,可以观察到,当某个节点有孩子的时候一定是其左孩子的 next 指向右孩子,而右孩子指向的是本结点 next 的左孩子,为了保证本节点next先有位置,那么递归时要先写处理过程,再递归左孩子,递归右孩子。 20 ms, faster than 80.38% 19.3 MB, less than 100.00%
/*
// Definition for a Node.
class Node {
public:
int val;
Node* left;
Node* right;
Node* next;
Node() : val(0), left(NULL), right(NULL), next(NULL) {}
Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}
Node(int _val, Node* _left, Node* _right, Node* _next)
: val(_val), left(_left), right(_right), next(_next) {}
};
*/
class Solution {
public:
Node* connect(Node* root) {
if (root == NULL || root->left==NULL||root->right==NULL) return root;
if (root->left != NULL){
root->left->next = root->right;
}
if (root->next != NULL)
root->right->next = root->next->left;
connect(root->left);
connect(root->right);
return root;
}
};
实现杨辉三角 。 Easy 题目,注意细节,比如数组的下标 0 ms, faster than 100.00% 8.7 MB, less than 96.30%
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> ans;
for (int i = 1; i <= numRows; ++i){
vector<int> level(i);
if (i == 1){
level[0] = 1;
ans.push_back(level);
}else{
level[0] = 1; level[i-1] = 1;
for (int j = 1; j < i-1; ++j)
level[j] = ans[i-2][j-1]+ans[i-2][j];
ans.push_back(level);
}
}
return ans;
}
};
给定一个数字序列代表每日股价,根据时间序只买入卖出一次,求最多赚多少钱。 解:循环中直接记录最小的股价并更新,同时计算每日股价减去当前最小股价如果能更新则更新,最后就得到结果。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int ans = 0, minprice = 0x3f3f3f3f;
for (int i = 0; i < prices.size(); ++i){
minprice = min(minprice,prices[i]);
ans = max(ans,prices[i]-minprice);
}
return ans;
}
};
给定一个数字序列代表每日股价,根据时间序可买入卖出多次,但每次持股不能超过 1 支,求最多赚多少钱。 这里我没有想到思路,看了solution 才懂了 peak-valley 峰谷计算法。总的价格就是所有的波峰减去其前面最近的波谷的值的和。一遍循环即可。 这里有两种,一种是把波谷认为是最低的那个,另一种是认为 1 2 3 4 5 中每个前一个都是后一个的波谷。 都可解,但是写起来复杂程度不一样,说明还是要多观察,寻找简单做法。
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() == 0 || prices.size() == 1) return 0;
int ans = 0;
int num = prices[0], tmpvalley = prices[0], tmpans=0;
for (int i = 1; i < prices.size(); ++i){
if (prices[i] <= prices[i-1]){ // 如果当前是小于前一个,这个时候来加入总和中
tmpvalley = prices[i];
if (1<i)
ans += tmpans;
tmpans=0;
}else if (prices[i] > prices[i-1]){
tmpans = max(tmpans,prices[i]-tmpvalley);
}
}
if (prices[prices.size()-1] - tmpvalley == tmpans) // 这里控制 1 2 3 4 5 情况
ans+=tmpans;
return ans;
}
};
class Solution:
def maxProfit(self, prices: List[int]) -> int:
ans = 0
for i in range(1, len(prices)):
if prices[i] > prices[i-1]:
ans += prices[i]-prices[i-1]
return ans
给定一个节点为整数数值的二叉树。路径定义为一个节点通过父子节点到另一个节点之间的节点序列,且路径至少含有一个节点。求 路径上节点和 最大的是多少。 思路:后序遍历。 由于路径定义是节点到另一节点,所以返回的是 本结点+max(左子树最大,右子树最大) ,这样返回后上个父结点才能利用当前值继续算下去,但是注意这里还要和 0 比较,因为这个值算出后可能还不如不加上。递归函数中更新最终结果值
class Solution {
private:
int ansmax;
public:
int maxPathSum(TreeNode* root) {
ansmax = INT_MIN;
postorder(root);
return ansmax;
}
int postorder(TreeNode* root){
if (root == NULL) return 0;
int leftsum = postorder(root->left);
int rightsum = postorder(root->right);
ansmax = max(ansmax,root->val+leftsum+rightsum);
return max(0,root->val+max(leftsum,rightsum));
}
};
给定一个字符串判断是否为回文,所有非字母数字都忽略,而同字母的不同大小写算相同。 解:头尾两个指针即可,这里其实要学会使用 s[i].isalnum() s[i].upper() s[i].lower() // 48 ms, faster than 61.44%m 13.2 MB, less than 89.29%
class Solution:
def isPalindrome(self, s: str) -> bool:
head = 0
tail = len(s)-1
while head<tail:
if s[head].isalnum() and s[tail].isalnum():
if s[head].lower() != s[tail].lower():
return False
else:
head += 1
tail -= 1
elif not s[head].isalnum():
head += 1
else:
tail -= 1
return True
给定一个未排序的数字序列。求连续数字元素最长有多长。如输入[100,4,200,1,3,2], 因为有[1,2,3,4]所以返回 4. 题目要求时间复杂度为 O(n)。 考虑就是一次循环,对每个值判断从这个值开始左右连续有多长,更新记录,再往下。那么问题在于“判断这个值开始左右连续有多长”。因此选用 unordered_map 或者 unordered_set,前者做一个原数组值到加上此值最长有多长的映射,因此对每个得到的原数组值, (其数字-1 的数字映射多长) + (其数字+1的数字映射多长)+1,即为连上了左右一整段的数字最长有多长,然后更新记录的最大结果,以及更新映射,这里只用更新这一段两端数值的映射,因为之后得到的数字也只会是考虑 +1,-1,也只可能和现在更新的两端连接上; 后者 set 的做法直接复制所有未重复数字到 set,再对每个值进行 “若-1出现就删去”的连续操作 以及 “若+1出现就删去”的连续操作,更新这一段连续数字的长。
int longestConsecutive(vector<int>& nums) {
unordered_map<int,int> imap;
int maxans=0,countleft,countright,countconsecu;
for (int i = 0; i < nums.size(); ++i){
if (!imap[nums[i]]){
countleft = imap[nums[i]-1];
countright = imap[nums[i]+1];
countconsecu = countleft+countright+1;
imap[nums[i]-countleft] = imap[nums[i]+countright] = imap[nums[i]] = countconsecu;
maxans = max(maxans, countconsecu);
}
}
return maxans;
}
int longestConsecutive(vector<int>& nums) {
if (nums.size() == 0) return 0;
unordered_set<int> iset(nums.begin(), nums.end());
int maxans = 1;
for (int i = 0; i < nums.size(); ++i){
if (iset.find(nums[i]) == iset.end())
continue;
iset.erase(nums[i]);
int leftnum = nums[i]-1, rightnum = nums[i]+1;
while(iset.find(leftnum) != iset.end())
iset.erase(leftnum--);
while(iset.find(rightnum) != iset.end())
iset.erase(rightnum++);
maxans = max(maxans, rightnum-leftnum-1);
}
return maxans;
}
给定一个二维格子区域,包含"O"和"X"两种字符,要求是找到所有被“X”包着的"O",并反转为“X”。 思路,从 4 条边界找“O”来 BFS,这样把遇到的"O"标记为已经遍历过,那么再在中间区域扫过,出现没有遍历过的"O"即是要翻转的。 28 ms, faster than 87.64% 14.4 MB, less than 21.88%
class Solution {
public:
void solve(vector<vector<char>>& board) {
if (board.size()==0 || board[0].size()==0)
return;
int row = board.size(), col = board[0].size();
if (row <= 2 || col <= 2)
return;
vector<vector<bool> > isvisited(row, vector<bool>(col,false));
for (int i = 0; i < row; ++i){
if (board[i][0] == 'O' && isvisited[i][0] == false)
bfs(board,i,0,row,col,isvisited);
if (board[i][col-1] == 'O' && isvisited[i][col-1] == false)
bfs(board,i,col-1,row,col,isvisited);
}
for (int j = 0; j < col; ++j){
if (board[0][j] == 'O' && isvisited[0][j] == false)
bfs(board,0,j,row,col,isvisited);
if (board[row-1][j] == 'O' && isvisited[row-1][j] == false)
bfs(board,row-1,j,row,col,isvisited);
}
for (int i = 1; i < row-1; ++i)
for (int j = 1; j < col-1; ++j)
if (board[i][j] == 'O' && isvisited[i][j] == false)
board[i][j] = 'X';
}
void bfs(vector<vector<char>>& board, int xx, int yy, int row, int col, vector<vector<bool>>& isvisited){
queue<int> ique;
int idx[] = {+1,0,-1,0};
int idy[] = {0,-1,0,+1};
ique.push(xx); ique.push(yy);
while (ique.empty() == false){
xx = ique.front();
ique.pop();
yy = ique.front();
ique.pop();
for (int i = 0; i < 4; ++i){
int tmpx = xx+idx[i], tmpy = yy+idy[i];
if (0<=tmpx && tmpx<row && 0<=tmpy && tmpy<col && isvisited[tmpx][tmpy]==false && board[tmpx][tmpy] == 'O'){
isvisited[tmpx][tmpy] = true;
ique.push(tmpx);
ique.push(tmpy);
}
}
}
}
};
一串由不同加油站组成圆形, 给定两个等长度的非负数数字序列,一个表示加油量,一个表示消耗量,问是否从某个点出发顺时针走,能重回到起点,可以在每个加油站加油。 我直接模拟+贪心做了。先找到能走的起始点,然后顺着模拟从它开始到结束的点为起始点情况,时间复杂度 O(n^2)。
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int begin = 0, ans = 0, len = gas.size();
if (len == 0) return -1;
while (begin < len && gas[begin] < cost[begin])
++begin;
if (begin == len)
return -1;
while (begin < len){
int index = begin, tmpsum = gas[index];
bool flag = false;
while (tmpsum>=cost[index]){
tmpsum -= cost[index];
index = (index+1)%len;
tmpsum += gas[index];
if (index == begin){
flag = true;
break;
}
}
if (index == begin && flag == true)
return begin;
begin++;
}
return -1;
}
};
给定一个非空的数字序列,其中除了一个元素数字仅仅出现 1 次,其他每个元素出现了 2 次。求使用线性时间且不使用另外的空间来求得这个只出现 1 次的数字。 解:这个题目之前看过,我的想法并没有达到线性实际上是先sort排序 O(logn),然后一次循环下加一个数字减一个数字最后即是这个数字了。巧解是用 XOR 异或,因为 A ^A = 0 , 0 ^ B=B, => A^ A ^B = B。所有数字异或一遍即得到结果
int singleNumber(vector<int>& nums) {
if (nums.size() == 0)
return 0;
if (nums.size() == 1)
return nums[0];
sort(nums.begin(), nums.end());
for (int i = 1; i < nums.size(); ++i){
if (i&1){ // 如果 i 是奇数,实际是在 nums 中的偶数位置
nums[i] = nums[i-1]-nums[i];
}else{
nums[i] = nums[i-1]+nums[i];
}
}
return nums[nums.size()-1];
}
int singleNumber(vector<int>& nums) {
if (nums.size() == 0)
return 0;
if (nums.size() == 1)
return nums[0];
for (int i = 1; i < nums.size(); ++i)
nums[i] ^= nums[i-1];
return nums[nums.size()-1];
}
拷贝链表,链表有两个指针,一个是 next,一个是 random,指向的是链表中其他某个节点。 这里要处理 random,用 map
class Solution {
public:
Node* copyRandomList(Node* head) {
unordered_map<Node*, Node*> oldnewnode;
if (head == NULL)
return head;
Node *currold = head,*ans=NULL, *newtail=NULL;
while (currold != NULL){
Node *tmp = new Node(currold->val);
oldnewnode[currold] = tmp;
if (ans == NULL){
ans = tmp;
newtail = tmp;
}else{
newtail->next = tmp;
newtail = newtail->next;
}
currold = currold->next;
}
newtail = ans;
currold = head;
while (newtail){
newtail->random = oldnewnode[currold->random];
newtail = newtail->next;
currold = currold->next;
}
return ans;
}
};
给定一个字符串,以及一个字符串组成的 vector,问是否可由 vector 中一些字符串组成给定字符串。 vector 中的字符串可不止一次使用。 解:DP 方法,设置一维数组,iscontaindp[i]表示字符串中截止到第 i 个字符是否在 vector 中出现过。 外层循环字符串从头至尾,内层循环控制从此时字符的前一个字符到头,若内层循环碰到已经在 vector 中出现过时,判断从此字符后到外层循环字符中是否在 vector 中出现过,若出现则新置为 true,内层循环结束到下面去
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_map<string, int> Dict;
for (int i = 0; i < wordDict.size(); ++i)
Dict[wordDict[i]]++;
bool iscontaindp[s.size()+1] = {0};
iscontaindp[0] = true;
for (int i = 1; i <= s.size(); ++i){
for (int j = i-1; j >= 0; --j){
if (iscontaindp[j] == true){
if (Dict.find(s.substr(j,i-j)) != Dict.end()){
iscontaindp[i] = true;
break;
}
}
}
}
return iscontaindp[s.size()];
}
};
给定一个列表判断其是否存在循环。 方法一: 设置两个指针一个走的快一个走得慢,如果存在撞上的情况必然是存在了循环。注意代码中我的判断条件把 fast->next->next == NULL 先判断,即 if (fast->next->next == NULL || fast->next == NULL) 这样写就过不了的,会在 1->2 这种例子中失败,因为不存在 fast->next->next,说明判断条件中的顺序也是很重要的。 方法二:看其他人题解的根据地址来判断。如果下一个结点指向的地址小于本结点,则存在循环。但由于存在单独一个结点的自循环所以前面判断条件要加“等于”
class Solution {
public:
bool hasCycle(ListNode *head) {
if (head == NULL || head->next == NULL) return false;
ListNode *fast=head->next,*slow=head;
while (fast != slow){
if (fast->next == NULL || fast->next->next == NULL) return false;
//if (fast->next->next == NULL || fast->next == NULL) return false;
fast = fast->next->next;
slow = slow->next;
}
return true;
}
};
class Solution {
public:
bool hasCycle(ListNode *head) {
if (head == NULL || head->next == NULL) return false;
ListNode *now=head;
while (now->next != NULL){
if (now->next <= now)
return true;
now = now->next;
}
return false;
}
};
和前一道题目相似。不过本题需要在链表中存在循环时,找到链尾指向的循环后节点。 解法同上道题,用第二种解法更简单,直接返回即可。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if (head == NULL || head->next == NULL) return NULL;
ListNode *now = head;
while (now->next != NULL){
if (now->next <= now)
return now->next;
now = now->next;
}
return NULL;
}
};
模拟最近最少使用缓存算法。构造时可以设置缓存大小,要求实现 put(key,value) 以及 get(key) 两个函数,并且时间复杂度需要是 O(1)。 解:看了题解才想明白。先用一个链表存储 key,因为要求是 O(1),存起来的就不止是
/**
* 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);
*/
class LRUCache {
unordered_map<int,int> keyvaluepair;
unordered_map<int, list<int>::iterator> keylistiterapair;
int size;
list<int> keylist;
public:
LRUCache(int capacity) {
size = capacity;
}
int get(int key) {
if (keyvaluepair.find(key) == keyvaluepair.end())
return -1;
keylist.erase(keylistiterapair[key]);
keylist.push_front(key);
keylistiterapair[key] = keylist.begin();
return keyvaluepair[key];
}
void put(int key, int value) {
if (keyvaluepair.find(key) != keyvaluepair.end()){ // 如果 key 已经存在则需要删除链表中的 key,并新加入
keylist.erase(keylistiterapair[key]);
}else{ // key 不存在在想要设置 key-value
if (keyvaluepair.size() >= size){ //如果此时容量已经容量满了,需要把最远用的删除,才能加入新的
keyvaluepair.erase(keylist.back());
keylistiterapair.erase(keylist.back());
keylist.pop_back();
}
}
keylist.push_front(key);
keylistiterapair[key] = keylist.begin();
keyvaluepair[key] = value;
}
};
把数字链表进行排序,要求时间复杂度为 O(nlogn)。 对于链表来说不能轻易得到 index,所以相比较快速排序和堆排序,不如使用合并排序,合并是只需要两个链首节点。递归找到找到中间节点,依次分为前后两段。
class Solution {
public:
ListNode* sortList(ListNode* head) {
if (head == NULL || head->next == NULL) return head;
ListNode *fast=head->next, *slow=head;
while (fast != NULL && fast->next != NULL){
fast = fast->next->next;
slow = slow->next;
}
fast = slow->next;
slow->next = NULL;
return merge(sortList(head),sortList(fast));
}
ListNode* merge(ListNode* node1, ListNode *node2){
ListNode *ans = new ListNode(0);
ListNode *curr = ans;
while (node1 != NULL && node2 != NULL){
if (node1->val < node2->val){
curr->next = node1;
node1 = node1->next;
}else{
curr->next = node2;
node2 = node2->next;
}
curr = curr->next;
}
if (node1 != NULL) curr->next = node1;
if (node2 != NULL) curr->next = node2;
return ans->next;
}
};
逆波兰表达式 很经典的栈的应用的题目
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
if len(tokens)==0:
return 0
istack = list()
for i in range(0,len(tokens)):
if tokens[i]=='+' or tokens[i]=='-' or tokens[i]=='*' or tokens[i]=='/':
num1 = istack.pop()
num1 = int(num1)
num2 = istack.pop()
num2 = int(num2)
if tokens[i] == '+':
istack.append(int(num2+num1))
elif tokens[i] == '-':
istack.append(int(num2-num1))
elif tokens[i] == '*':
istack.append(int(num2*num1))
elif tokens[i] == '/':
istack.append(int(num2/num1))
else:
tmpnum = tokens[i]
tmpnum = int(tmpnum)
istack.append(tmpnum)
return istack.pop()
子序列最大乘积 给定一个由整数数字组成的序列,问所有连续子序列中,数字乘积结果最大的那个值是多少。 此题要考虑数字可能是正数、负数和 0,我一开始想的是二维数组 DP 求解,相当于所有情况都求出,不过WA 了,错在一个特别长由1和-1 组成的输入序列。看其他题解明白到其实可以简单做,记录当前最大乘积和最小乘积,因为最小乘积可能是负数,在遇到后面的负数时可以乘出正数,可能就是最大的乘积值了
class Solution {
public:
int maxProduct(vector<int>& nums) {
if (nums.size() == 0) return 0;
int minnum = nums[0], maxnum = nums[0], ans = nums[0];
for (int i = 1; i < nums.size(); ++i){
int maxproduct = max(minnum*nums[i], max(maxnum*nums[i], nums[i]));
int minproduct = min(minnum*nums[i], min(maxnum*nums[i], nums[i]));
ans = max(ans, maxproduct);
maxnum = maxproduct;
minnum = minproduct;
}
return ans;
}
};
要求实现一个栈的类,它能做到push(x), pop(), top() 和常数时间取 getMin() 这四种操作。 这道题就是问在了时间复杂度在常数时间内取最小值上。所以直接设置 2 个 stack,一个正常,另一个记录每次当前最小值即可。
class MinStack {
stack<int> numstack;
stack<int> minstack;
public:
void push(int x) {
if (minstack.empty()){
minstack.push(x);
}else{
if (x >= minstack.top())
minstack.push(minstack.top());
else
minstack.push(x);
}
numstack.push(x);
}
void pop() {
minstack.pop();
numstack.pop();
}
int top() {
if (numstack.empty())
return 0;
return numstack.top();
}
int getMin() {
if (numstack.empty())
return 0;
return minstack.top();
}
};
题意:给两个链表,其中可能存在两个链表从某一个节点开始指向的之后节点都是一样的,找到这个可能存在的两链表相交的交点。题目要求:不能改变原有的两个链表结构,如果没有交点节点返回 NULL,代码时间复杂度为 O(n),使用空间为 O(1)。
3 种思路。思路一:在容器中加入一个链表所有节点,然后加入另个链表各节点,出现重复时就是要返回的节点。可使用 map
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
map<ListNode*,int> imap;
ListNode *tmpa = headA, *tmpb = headB;
while (tmpa != NULL){
imap[tmpa]++;
tmpa = tmpa->next;
}
while (tmpb != NULL){
if (imap[tmpb] == 1)
return tmpb;
tmpb = tmpb->next;
}
return NULL;
}
};
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int counta = 0, countb = 0;
ListNode *tmpa = headA, *tmpb = headB;
while (tmpa != NULL){
counta++;
tmpa = tmpa->next;
}
while (tmpb != NULL){
countb++;
tmpb = tmpb->next;
}
tmpa = headA, tmpb = headB;
if (counta > countb)
while (counta-- > countb)
tmpa = tmpa->next;
else
while (countb-- > counta)
tmpb = tmpb->next;
while (tmpa != tmpb){
tmpa = tmpa->next;
tmpb = tmpb->next;
}
return tmpa;
}
class Solution {
long long num = 0;
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *tmpa = headA, *tmpb = headB;
tmpa = reorder(tmpa);
tmpb = reorder(tmpb);
tmpa = reorder(tmpa);
if (tmpb != headA)
tmpb = reorder(tmpb);
return (ListNode*)num;
}
ListNode *reorder(ListNode *head){
ListNode *ans=NULL, *tmp;
while (head != NULL){
num ^= (long long)head;
tmp = head;
head = head->next;
tmp->next = ans;
ans = tmp;
}
return ans;
}
};
寻找峰值。给定一个数字序列,峰值点定义为比左右大的值,寻找峰值所在的下标位置,假设 nums[-1]=nums[nums.size()]=-∞,如果有多个,返回任意一个即可。题目要求需要是对数时间范围。 这个我没有想到,想到二分但是没有想到怎么二分,还是拿 mid 和left,right 比。后来看别人题解明白了,是拿 mid 和mid的左右比较。若 nums[mid]>nums[mid+1]则必然有峰值出现在 [left,mid]段,然后继续处理左半段即可。所以也会有递归和非递归解法
class Solution:
def findPeakElement(self, nums: List[int]) -> int:
def findpeak(nums:List[int], left:int, right:int):
if left == right:
return left
mid = int((left+right)/2)
if nums[mid] > nums[mid+1]:
return findpeak(nums,left,mid)
else:
return findpeak(nums,mid+1,right)
return findpeak(nums,0,len(nums)-1)
class Solution:
def findPeakElement(self, nums: List[int]) -> int:
left = 0
right = len(nums)-1
while (left < right):
mid = int((left+right)/2)
if nums[mid]>nums[mid+1]:
right = mid
else:
left = mid+1
return left
给定两个整数表示分子分母,保证分母不为0,求分数结果转为String类型,如果有循环节,用括号括起来。 这个题目还是很好的,需要考虑的东西比较多,是要模拟除法,还会涉及到超出范围情况,题目给定的是两个int型,但是负数最大除以-1,是比int的正数最大要大1的。所以一开始需要类型转换后再abs取绝对值。分两部分,小数点前计算好算,而小数点后则需要一位一位的模拟。这里用unordered_map构建小数点后的计算到某一位的数字与整体string返回长度的映射,目的在于如果计算到某个值发现映射中已经出现过则表示已经要循环了。然后就是细节问题。除以和取余操作的不同
class Solution { // 0 ms, faster than 100.00% 8.9 MB, less than 93.33%
public:
string fractionToDecimal(int numerator, int denominator) {
if (numerator == 0)
return "0";
string ans ="";
if (numerator>0 ^ denominator>0)
ans += "-";
long long absnumer = abs(long (numerator)), absdenomi = abs(long(denominator));
ans += to_string(absnumer/absdenomi);
if (absnumer%absdenomi == 0) return ans;
ans += ".";
unordered_map<long long,int> remindertorepeatsize;
long long remind = absnumer%absdenomi;
while (remind!=0){
if (remindertorepeatsize[remind] > 0){
ans.insert(remindertorepeatsize[remind],"(");
ans+=")";
return ans;
}
remindertorepeatsize[remind] = ans.size();
remind *= 10;
ans += to_string(remind/absdenomi);
remind %= absdenomi;
}
return ans;
}
};
给定一个长度为 n 的数字序列,有一个数字出现次数大于 n/2 次,求出这个数字。 解: 解法一是排序后用两个指针指向隔着 n/2+1 的两个位置,然后同时前进,只要指向的数字相同则就是要返回的数字。 但是这个方法可以更加精简,直接 sort 后返回 nums[size/2]即可; 解法二就是 hash,使用一个 map
int majorityElement(vector<int>& nums) {
sort(nums.begin(), nums.end());
int front=nums.size()/2,back=0;
while (front < nums.size()){
if (nums[front] == nums[back]){
return nums[front];
}
front++;
back++;
}
return 0;
}
int majorityElement(vector<int>& nums) {
map<int,int> imap;
for (int i = 0; i < nums.size(); ++i){
imap[nums[i]]++;
if (imap[nums[i]] > nums.size()/2)
return nums[i];
}
return 0;
}
int majorityElement(vector<int>& nums) {
int ans = nums[0],count=1;
for (int i = 1; i < nums.size(); ++i){
if (nums[i] == ans)
count++;
else{
if (count == 0){
ans = nums[i];
count++;
}else if (count > 0){
count--;
}
}
}
return ans;
}
Excel 表格中的列头为 A,B,C……Z 组成的序列,会有对应的第几列,现在给定一个大写字母序列,求对应的序列数字。如 AA 为27 列。 解:题目实际是 26 进制转为10 进制
class Solution {
public:
int titleToNumber(string s) {
// 26进制转为数字
long long ans = 0;
for (int i = s.size()-1; i >= 0; --i){
ans += (s[i]-'A'+1)*pow(26,s.size()-1-i);
}
return ans;
}
};
阶乘尾缀 0。 即给定一个正整数,求其阶乘的尾缀有多少 0。题目要求在对数时间复杂度做出来。 首先把题目转化为另外的容易理解的,也就是因为 0 是由 5 和 2 乘出来的,而 2 的倍数们的数量一定比 5 多倍数们多,所以实际 5 的倍数的数量就决定了有多少尾缀 0。我是列出来很多,看规律是是什么,就发现跟 5 的阶乘有关系。然后就想到了。不过我看其他人也是这么做,但我的时间却不算快的。 4 ms, faster than 57.64% / 8.3 MB, less than 71.43%
class Solution {
public:
int trailingZeroes(int n) {
int ans = 0;
while (n/5>0){
ans += n/5;
n = n/5;
}
return ans;
}
};
给定一组非负数数字,求这组数字能组成的最大正数是多少。 解,这个题曾经在本科的算法求解上有过,是不能够直接连接在一起的,比如 3, 30 转化为Sring再直接比较大小后相连会出现 303,但实际上 330更大一些。所以就是比较string类型下 a+b>b+a才可以。为了达到这个目的,就写一个比较函数再sort下 8 ms, faster than 77.69% 9.1 MB, less than 54.55%
class Solution {
private:
static bool cmp(string &a, string &b){
return a+b>b+a;
}
public:
string largestNumber(vector<int>& nums) {
vector<string> stringnum(nums.size());
for (int i = 0; i < nums.size(); ++i)
stringnum[i] = to_string(nums[i]);
sort(stringnum.begin(),stringnum.end(), cmp);
if (nums.size()==0) return "";
string ans = stringnum[0];
for (int i = 1; i < nums.size(); ++i)
ans += stringnum[i];
return (stringnum[0] == "0"?"0":ans);
}
};
给定一个数字序列,和一个数字 k,求其顺着正向翻转 k 次的结果,翻转一次表示结尾放到开头而其余的顺次往下一个位置。 题目要求 空间复杂度为 O(1)。 解:看了 Hint 提示想到了翻转的方法,根据要翻转 k 的这个值,先把最后 k 个数字整体左右反转,把剩余前面的整体反转,最后把整个数组反转就得到了。3次翻转即可。 12 ms, faster than 99.62% 9.5 MB, less than 86.11%
class Solution {
public:
void rotate(vector<int>& nums, int k) {
if (nums.size() == 0 || nums.size() == 1)
return;
if (k > nums.size())
k %= nums.size();
reverse(nums,nums.size()-k,nums.size()-1);
reverse(nums,0,nums.size()-k-1);
reverse(nums,0,nums.size()-1);
}
void reverse(vector<int>& nums, int left, int right){
while (left <= right){
swap(nums[left],nums[right]);
left++;
right--;
}
}
};
把一个无符号整数逆转所有二进位 0 ms, faster than 100.00% 8.1 MB, less than 87.50%
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
uint32_t ans=0, count = 0;
while (n > 0){
ans = ans<<1;
ans = ans|(n&1);
n = n>>1;
count++;
}
count = 32-count;
while (count--)
ans <<= 1;
return ans;
}
};
数一下一个数字的 2 进制中有多少个 1. Easy 题
class Solution {
public:
int hammingWeight(uint32_t n) {
int ans = 0;
while (n>0){
if (n&1)
ans++;
n >>= 1;
}
return ans;
}
};
给定一串数字序列,每个数字表示当前房屋的价值。要求不能取相邻的数字,可以间隔着取,那么最多能取的价值总和是多少。 解:DP 问题,列一个长串的数字手写一下就会发现,转移公式是 dp[i] = max(dp[i-2],dp[i-3])+nums[i];
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.size() == 0)
return 0;
if (nums.size() == 1)
return nums[0];
if (nums.size() == 2)
return max(nums[0],nums[1]);
int dp[nums.size()+1] = {0};
dp[0] = nums[0];
dp[1] = nums[1];
dp[2] = nums[0]+nums[2];
for (int i = 3; i < nums.size(); ++i){
dp[i] = max(dp[i-2],dp[i-3])+nums[i];
}
return max(dp[nums.size()-1],dp[nums.size()-2]);
}
};
给定一个由字符"1"和"2"组成的 2 维数组,“岛”定义为所有上下左右相邻的"1"组成, 求共有多少个岛。 解:经典 BFS/DFS。 我写的是 BFS,也可以 DFS,会更简单,不需要任何 STL,直接函数中改变为’0’,然后就继续 4 个方向递归即可。
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
int ans = 0;
for (int i = 0; i < grid.size(); ++i)
for (int j = 0; j < grid[0].size(); ++j)
if (grid[i][j] == '1'){
bfs(grid,i,j);
ans++;
}
return ans;
}
void bfs(vector<vector<char>>&grid, int i, int j){
queue<int> ique;
ique.push(i), ique.push(j);
int dx[] = {-1,0,+1,0};
int dy[] = {0,+1,0,-1};
while (!ique.empty()){
int x = ique.front(); ique.pop();
int y = ique.front(); ique.pop();
grid[x][y] = '0';
for (int index = 0; index < 4; ++index){
int tmpx = x+dx[index], tmpy = y+dy[index];
if (tmpx<0 || tmpx>=grid.size())
continue;
if (tmpy<0 || tmpy>=grid[0].size())
continue;
if (grid[tmpx][tmpy] == '1'){
ique.push(tmpx), ique.push(tmpy);
bfs(grid, tmpx, tmpy);
}
}
}
}
};
判断一个正整数是否是高兴数。 高兴数定义为其位数上不同位数字的平方和得到后判断是否为1,如果不为那么继续这个过程再判断。 任何数字一定是出现1或者无限循环下去。 直接模拟即可 0 ms, faster than 100.00% 8.5 MB, less than 57.69%
class Solution {
public:
bool isHappy(int n) {
unordered_map<int, int> numtoexist;
int sum = 0;
while (n > 0){
sum += pow((n%10),2);
n /= 10;
}
n = sum;
while (n != 1){
if (numtoexist.find(n)!=numtoexist.end()){
return false;
}
numtoexist[n]++;
sum = 0;
while (n > 0){
sum += pow((n%10),2);
n /= 10;
}
n = sum;
}
return true;
}
};
计算不大于正整数n的素数有多少个。 正向打素数表,外层从2到sqrt(n),在这个数字i为素数下,内层循环从 i*i起始,到小于n,以 i 为步长,设置为非素数。 时间复杂度 O(n log log n) 520 ms, faster than 61.56% / 24.4 MB, less than 100.00%
class Solution:
def countPrimes(self, n: int) -> int:
if n == 1 or n == 0: return 0
isprime = [1]*n
isprime[0] = 0
isprime[1] = 0
num = int(n**0.5)
ans = 0
for i in range(2,num+1):
if isprime[i]==0:
continue
for j in range(i*i,n,i):
isprime[j] = 0
for i in range(2,n):
if isprime[i]==1:
ans += 1
return ans
给定一个链表,用递归和非递归方法来逆转它。
归:在我写的时候函数中逆序后不知道该怎么做,也是看了其他题解知道了当前节点指向的下一个节点,指回来自己。然后让自己的下个指向为空,我一直想的还以为新建节点并怎么操作。 非递归:非递归的好写,即直接一次循环,头插法即可。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == NULL) return head;
ListNode *ans;
ans = recurse(head);
return ans;
}
ListNode* recurse(ListNode *node){
if (node->next == NULL) return node;
ListNode *tmp = recurse(node->next);
node->next->next = node;
node->next = NULL;
return tmp;
}
};
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == NULL) return head;
ListNode *curr = head, *ans = NULL,*tmp;
while (curr != NULL){
tmp = curr->next;
curr->next = ans;
ans = curr;
curr = tmp;
}
return ans;
}
};
有 n 门课标记为 0 到 n-1,有的课需要先修课如[0,1]表示若修 0 则必须先修 1,给定一个[本课,先修课]的列求是否能够修完所有的课。 解:题目意思就是在一个有向图中判断是否存在环。 两种方法,一种是拓扑排序方法不断删去入度为 0 的点和连接边,判断是否还有点和边; 第二种是标记 3 种状态的 DFS,防止 A->B,A->C->B 被判断有环。 第一种解法中还可以构建一个邻接表构造的图,也可以不构造,以下是不构造,但是时间复杂度会高,因为会重复查找本来需要删掉的边。
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
int degree[numCourses] = {0};
stack<int> degree0;
for (int i = 0; i < prerequisites.size(); ++i){
degree[prerequisites[i][0]]++;
}
for (int i = 0; i < numCourses; ++i){
if (degree[i] == 0)
degree0.push(i); //所有入度为 0 的点入栈
}
int degree0index;
while (!degree0.empty()){
degree0index = degree0.top();
degree0.pop();
for (int i = 0; i < prerequisites.size(); ++i){
if (prerequisites[i][1] == degree0index && degree[prerequisites[i][0]]>0){
degree[prerequisites[i][0]]--;
if (degree[prerequisites[i][0]] == 0){
degree0.push(prerequisites[i][0]);
}
}
}
}
for (int i = 0; i < numCourses; ++i)
if (degree[i] > 0)
return false;
return true;
}
};
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
vector<vector<int>> adj(numCourses,vector<int>());
vector<int> degree(numCourses,0);
for (int i = 0; i < prerequisites.size(); ++i){
adj[prerequisites[i][1]].push_back(prerequisites[i][0]);
degree[prerequisites[i][0]]++;
}
stack<int> istack;
for (int i = 0; i < numCourses; ++i)
if (degree[i] == 0)
istack.push(i);
while (!istack.empty()){
int curr = istack.top();
istack.pop();
numCourses--;
for (int i = 0; i < adj[curr].size(); ++i){
degree[adj[curr][i]]--;
if (degree[adj[curr][i]] == 0)
istack.push(adj[curr][i]);
}
}
if (numCourses)
return false;
return true;
}
};
题目要求实现一个可以做 insert(), search(), startswith() 3 种函数的字典树。 insert(word)是插入字符串 string word, search(word) 是查找是否存在着字符串 word, startswith(prefix) 是查找是否存在着前缀 prefix。 比如字典树中 insert(apple), 那么在查找 search(app)时要返回 fasle,而 startswith(app)时返回 true。 我用了最暴力的 set 把每次插入时的不同前缀插入同时插入到 vector中,这样分成了两种状态用来表示两种查找,AC 了,不过时间上只超过了5%,本题实际考察字典树, 即构建从树根往下是代表 26 个字母的子树,每个子树再往下又是指向了 26 个字母的子树。 这里用 map
class Trie {
struct TrieNode{
map<char, TrieNode*>children;
int end;
TrieNode():end(0){}
};
private:
TrieNode *root;
public:
/** Initialize your data structure here. */
Trie() {
root = new TrieNode();
}
/** Inserts a word into the trie. */
void insert(string word) {
TrieNode *curr = root;
for (int i = 0; i < word.size(); ++i){
if (curr->children.find(word[i]) == curr->children.end())
curr->children[word[i]] = new TrieNode();
curr = curr->children[word[i]];
}
curr->end = 1;
}
/** Returns if the word is in the trie. */
bool search(string word) {
TrieNode *curr = root;
for (int i = 0; i < word.size(); ++i){
if (curr->children.find(word[i]) == curr->children.end())
return false;
curr = curr->children[word[i]];
}
return curr->end==1 ? true: false;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
bool startsWith(string prefix) {
TrieNode *curr = root;
for (int i = 0; i < prefix.size(); ++i){
if (curr->children.find(prefix[i]) == curr->children.end())
return false;
curr = curr->children[prefix[i]];
}
return true;
}
};
/**
* Your Trie object will be instantiated and called as such:
* Trie* obj = new Trie();
* obj->insert(word);
* bool param_2 = obj->search(word);
* bool param_3 = obj->startsWith(prefix);
*/