LeetCode高频148错题记录
3. Max Points on a Line 共线点个数3种解法
思路一:思考如何确定一条直线,两点法,确定斜率后带入一点。有三种情况,1. 两点重合,2. 斜率不存在,3. 正常算,依次以每个点为过直线的点,map映射斜率个数。
思路二:后两种情况合并,用(dy/d, dx/d)表示,其中d=gcd(dx, dy),这样避免了除法的精度损失
思路三:暴力O(n^3)的解法,枚举任意一条直线,判断三点共线三角形面积法(1/2*ABxAC==0)叉积为零(行列式为0)
4. O(nlogn)链表排序
思路一:归并排序(注意要做截断处理前一半的末尾赋值为NULL,奇数偶数都考虑可以避免找中间结点的错误,if(two&&two->next))
思路二:快速排序
1 class Solution { 2 public: 3 ListNode* sortList(ListNode* head) { 4 quicksort(head,NULL); 5 return head; 6 } 7 void quicksort(ListNode* head, ListNode* end) 8 { 9 if(head!=end) 10 { 11 ListNode* partion = partition(head,end); 12 quicksort(head, partion); 13 quicksort(partion->next,end); 14 } 15 } 16 ListNode* partition(ListNode* head, ListNode* end) 17 { 18 ListNode* slow = head; 19 ListNode* fast = head->next; 20 while(fast!=end) 21 { 22 if(fast->val<head->val) 23 { 24 slow = slow->next; 25 swap(fast->val,slow->val); 26 } 27 fast = fast->next; 28 } 29 swap(slow->val,head->val); 30 return slow; 31 } 32 };
6 后序遍历非递归
思路一:标记变量法,判断是否为遍历完右子树返回。
要点:标记变量mark,初始化为NULL。后序从右子树返回则mark = root,从左子树返回的,还要重新入栈,注意两个循环的条件都是栈不为空。
思路二: 判断法
入栈顺序根节点,右儿子,左儿子。这样出栈顺序就是后序的顺序,判断条件一定要注意pre!=NULL一定不能忘记写,如果忘记了,pre==NULL时有一个子树为空也满足条件就错了。
if((cur->left==NULL&&cur->right==NULL)||(pre!=NULL&&(cur->left==pre||cur->right==pre)))
一种情况如果左右子树为空,那么直接记录答案并更新pre;另一种情况,如果当前pre不为空并且表示从任意一个子树返回,那么也是正确的后序返回顺序。为什么从左子树返回也是正确的,因为按照左右中的顺序出栈,那么到当前点,pre==cur->left,表明右子树为空。
思路三:左右子树入栈顺序改变的先序遍历+reverse
栈来操作,先:中左右->后:左右中,先反转:右左中,将左右子树入栈顺序调换即可
8. reorder-list.
L: L0→L1→…→Ln-1→Ln, 变成这样:L0→Ln →L1→Ln-1→L2→Ln-2→… 思路,快慢指针找中点,找到后尾串反转(双指针法,或头插[第二个放前面,第三个放前面..]),两个链表合并。
11. word-break-ii:
枚举所有情况的题,多半是dfs,但如果不用记忆数组做减少重复计算的优化,那么递归方法跟brute force没有区别,所以要避免重复计算。或用dp搞。
1 vector<string> wordBreak(string s, vector<string>& wordDict) { 2 unordered_map<string, vector<string>> m; 3 return helper(s,wordDict,m); 4 } 5 vector<string> helper(string s, vector<string> & dict, unordered_map<string, vector<string>>& m) 6 { 7 if(m.count(s))return m[s]; //记忆化搜索 8 if(s.empty())return {""}; //递归出口 9 vector<string> res; 10 for(string word:dict) 11 { 12 if(s.substr(0,word.size())!=word)continue; 13 vector<string> remember = helper(s.substr(word.size()),dict,m); 14 for(string str:remember) 15 { 16 res.push_back(word + (str.empty()?"":" ")+str); 17 } 18 } 19 return m[s]=res; 20 }
single-number-ii
思路:转自菜鸟葫芦娃
Single Number的本质,就是用一个数记录每个bit出现的次数,如果一个bit出现两次就归0,这种运算采用二进制底下的位操作^是很自然的。Single Number II中,如果能定义三进制底下的某种位操作,也可以达到相同的效果,Single Number II中想要记录每个bit出现的次数,一个数搞不定就加两个数,用ones来记录只出现过一次的bits,用twos来记录只出现过两次的bits,ones&twos实际上就记录了出现过三次的bits,这时候我们来模拟进行出现3次就抵消为0的操作,抹去ones和twos中都为1的bits
1 int singleNumber(int A[], int n) { 2 int ones = 0; // 记录只出现一次的bits 3 int twos = 0; // 记录只出现二次的bits 4 int threes; 5 for(int i=0;i<n;i++) 6 { 7 int t=A[i]; 8 twos |= ones&t; // 要在更新ones之前更新twos 9 ones ^=t; 10 threes = ones&twos;// ones和twos中都为1即出现3次 11 ones &= ~threes;//抹去出现3次的bits 12 twos &= ~threes; 13 } 14 return ones; 15 }
Integer break
思路一:O(n^2)的dp,注意拆分的时候,可以不拆分包含自己
1 dp[2]=1; 2 dp[3]=2; 3 for(int i=4;i<=n;i++) 4 { 5 for(int j=1;j<i;j++) 6 { 7 dp[i] = max(max(dp[j],j)*max(dp[i-j],i-j),dp[i]);// 注意这里 8 } 9 } 10 return dp[n];
思路二:O(n)的dp,和台阶题类似,在n >3的情况下,处理一个数要么拆分成2,要么拆分成3,(4的话相当于2个2 , 拆成1的话乘积太小了)
1 dp[2] = 2 2 dp[3] = 3 3 for(int i=4;i<=n;i++) 4 dp[i] = max(dp[i - 2] * 2, dp[i - 3] * 3) 5 return dp[n]
思路三:
拆成3的比拆成2的乘积大。 比如6的时候 2*2*2 < 3*3
希望能尽可能的拆成3,然后才是2.
所以,如果
- n % 3 == 0: 那么全部拆成3
- n % 3 == 1: 2个2剩下的为3 4*3^(x-1) > 1*3^x
- n % 3 == 2: 1个2剩下的为3
16 candy
要求每个小朋友至少分一个,rating高的比邻居分的多
思路:分两步,从左到右,从右到左。从右到左时注意,如果此时dp[i]>dp[i+1]则continue,没必要加了,别无脑加1!
18 clone-graph
类比任意指针的链表深拷贝
思路:dfs或bfs遍历复制,用map记录以及visited处理
1 /** 2 * Definition for undirected graph. 3 * struct UndirectedGraphNode { 4 * int label; 5 * vector<UndirectedGraphNode *> neighbors; 6 * UndirectedGraphNode(int x) : label(x) {}; 7 * }; 8 */ 9 class Solution { 10 public: 11 UndirectedGraphNode *cloneGraph(UndirectedGraphNode *node) { 12 unordered_map<int, UndirectedGraphNode *> umap; 13 return clone(node,umap); 14 } 15 UndirectedGraphNode * clone(UndirectedGraphNode* node,unordered_map<int, UndirectedGraphNode *> & umap) 16 { 17 if(!node)return node; 18 if(umap.count(node->label))return umap[node->label]; 19 UndirectedGraphNode* newNode = new UndirectedGraphNode(node->label); 20 umap[node->label]=newNode; 21 for(int i=0;i<node->neighbors.size();i++) 22 { 23 newNode->neighbors.push_back(clone(node->neighbors[i],umap)); 24 } 25 return newNode; 26 } 27 };
25 word ladder
一次只能变一个字母,且中间状态必须在dict中存在,可能有很多中间状态,求最小转换次数,最小的搜索方式就是bfs
思路:从第一个字母开始改,26个字母分别试,用一个map记录是否访问过以及到这个状态所需的次数。
1 class Solution { 2 public: 3 int ladderLength(string start, string end, unordered_set<string> &dict) { 4 unordered_map<string,int> pathCnt{{{start,1}}}; 5 queue<string> q{{start}}; 6 while(q.size()) 7 { 8 string word = q.front();q.pop(); 9 for(int i=0;i<word.size();i++) 10 { 11 string newWord = word; 12 for(char ch = 'a';ch<='z';ch++) 13 { 14 newWord[i] = ch; 15 if(newWord==end)return pathCnt[word]+1; 16 if(dict.count(newWord)&&!pathCnt.count(newWord)) 17 { 18 q.push(newWord); 19 pathCnt[newWord]=pathCnt[word]+1; 20 } 21 } 22 } 23 } 24 return 0; 25 } 26 };
*27 binary-tree-maximum-path-sum
这题之前做过,现在又不会了orz。类似一维数组最大和问题,换到了二叉树上,起点终点在任意结点,可能有负数。
考察递归和树的dfs很好的一道题。
4 / \ 11 13 / \ 7 2
试一个例子自己找一下递归的解法
对于每个结点来说,我们要知道经过其左子结点的path之和大还是经过右子节点的path之和大。那么递归函数返回值就可以定义为以当前结点为根结点,到叶节点的最大路径之和,然后全局路径最大值放在参数中,用引用res来表示。
1. 用res记录答案
2. 递归函数始终返回以当前结点为终点的最大路径和
每次递归中,左子结点为终点的最大path和加上以右子结点为终点的最大path和,再加上当前结点值就组成了一条完整的路径。
1 class Solution { 2 public: 3 int maxPathSum(TreeNode *root) { 4 int res = INT_MIN; 5 helper(root, res); 6 return res; 7 } 8 int helper(TreeNode *root, int & res) 9 { 10 if(root==NULL)return 0; 11 int left = max(0,helper(root->left,res)); 12 int right = max(0,helper(root->right,res)); 13 res = max(res,left+right+root->val); 14 return max(left,right)+root->val; 15 } 16 };
Maximum Subarray 最大子数组
思路一:dp O(n) dp[i] = max(dp[i-1], 0)+a[i]
思路二: 分治,每次把数组一分为二,分别找出左边和右边的最大子数组之和,然后还要从中间开始向左右分别扫描,求出的最大值分别和左右两边得出的最大值比较取最大的
1 class Solution { 2 public: 3 int maxSubArray(int A[], int n) { 4 if (n==0) return 0; 5 return helper(A, 0, n - 1); 6 } 7 int helper(int num[],int l,int r) 8 { 9 if(l>=r)return num[l]; 10 int mid = (l+r)/2; 11 int left = helper(num,l,mid-1); 12 int right = helper(num,mid+1,r); 13 int mmax = num[mid], t = mmax; 14 for (int i = mid - 1; i >= l; --i) { 15 t += num[i]; 16 mmax = max(mmax, t); 17 } 18 t = mmax; 19 for (int i = mid + 1; i <= r; ++i) { 20 t += num[i]; 21 mmax = max(mmax, t); 22 } 23 return max(max(left,right),mmax); 24 } 25 };
sqrtx
开根号,返回int
二分法:
注意判出条件有两个。
1 int sqrt(int x) { 2 if(x < 0) 3 return -1; 4 long l = 0, r = x; 5 while(l<=r) 6 { 7 long mid = l+(r-l)/2; 8 if(mid*mid==x || (mid * mid < x && (mid + 1) * (mid + 1) > x))return mid; 9 if(mid*mid<x)l=mid+1; 10 else r = mid-1; 11 } 12 return 0; 13 }
牛顿法:
转自Matrix67
随便设一个近似值x,然后不断令x等于x和a/x的平均数,迭代个六七次后x的值就已经相当精确了。
仅仅是不断用(x,f(x))的切线来逼近方程x^2-a=0的根。根号a实际上就是x^2-a=0的一个正实根,这个函数的导数是2x。也就是说,函数上任一点(x,f(x))处的切线斜率是2x。那么,x-f(x)/(2x)就是一个比x更接近的近似值。代入f(x)=x^2-a得到x-(x^2-a)/(2x),也就是(x+a/x)/2。
1 int sqrt(int x) { 2 if(x < 0) 3 return -1; 4 long res = x; 5 while(res * res > x) 6 res = ((res + x / res) >> 1); 7 return res; 8 }
Permutation Sequence
求第k个全排列,直接dfs求超时
有个O(n)的好方法,从左到右一个一个求出来。因为只要求1
个,所以可以按照全排列的规则,一个个数的求出每个位置的数字,而不需要将所有的全排列字符串列出来。
对于n
个字符组成的字符串{1,2,3,...,n}
,取第k
个排列时,首先可以求出第k
个排列字符串中的第一个数,即(k-1)/(n-1个数的排列个数)
下面是递归和非递归的两种写法
1 class Solution { 2 public: 3 string res=""; 4 string nums = "123456789"; 5 string getPermutation(int n, int k) { 6 string s; 7 for(int i=0;i<n;i++) 8 s += nums[i]; 9 solve(s,k); 10 return res; 11 } 12 void solve(string& s, int k) 13 { 14 if(s==""||k==0)return; 15 int len = s.size(); 16 int cnt=1; // 计算 (n-1)个数的排列个数cnt 17 for(int i=1;i<len;i++) 18 { 19 cnt*=len-i; 20 } 21 int pos = (k-1)/cnt; 22 res+=s[pos]; 23 k-=cnt*pos; 24 s=s.erase(pos,1); 25 solve(s,k); 26 } 27 };
1 string getPermutation(int n, int k) { 2 string s; 3 string res=""; 4 string nums = "123456789"; 5 for(int i=0;i<n;i++)s += nums[i]; 6 for(int i=0;i<n;i++) 7 { 8 int cnt=1; 9 int len=s.size()-1; 10 while(len>1) 11 { 12 cnt*=len; 13 len--; 14 } 15 int pos = (k-1)/cnt; 16 k-=cnt*pos; 17 res+=s[pos]; 18 s.erase(pos,1); 19 } 20 return res; 21 }
pascals-triangle-ii
只用O(k)空间返回第k个杨辉三角数组
类比01背包,容量倒循环写法省空间
1 class Solution {//合理规划dp转移方向降低一个维度 2 public: 3 vector<int> getRow(int rowIndex) { 4 vector<int> dp(rowIndex+1,0); 5 dp[0]=1; 6 for(int i=1;i<=rowIndex;i++) 7 for(int j=i;j>0;j--) 8 { 9 dp[j]=dp[j]+dp[j-1]; 10 } 11 return dp; 12 } 13 };
populating-next-right-pointers-in-each-node
将完全二叉树改写成next指针指向右边结点的形式,这题之前做过,不用多余空间又忘了怎么搞了orz。应该考虑连续两层的信息。只考虑当前层肯定就断了,不要一直卡在这里。
1 void connect(TreeLinkNode *root) { 2 if(!root)return; 3 while(root->left) 4 { 5 TreeLinkNode* p=root; 6 while(p) 7 { 8 p->left->next = p->right; 9 if(p->next) 10 p->right->next = p->next->left; 11 else 12 p->right->next = NULL; 13 p=p->next; 14 } 15 root = root->left; 16 } 17 }
populating-next-right-pointers-in-each-node II
凉凉,上一题变一下形又凉了,这题树变为任意二叉树,要灵活运用dummy结点处理,这样从起始到结束都是统一的形式,方便循环处理。当找第一个结点的情况与找后序存在的结点情况相似时,考虑dummy伪结点的使用
1 void connect(TreeLinkNode *root) { 2 if(!root)return; 3 while(root) 4 { 5 TreeLinkNode* dummy = new TreeLinkNode(-1); // 每一层的头结点 6 TreeLinkNode* cur = dummy; 7 while(root) 8 { 9 if(root->left) 10 { 11 cur->next = root->left; 12 cur = cur->next; 13 } 14 if(root->right) 15 { 16 cur->next = root->right; 17 cur = cur->next; 18 } 19 root=root->next; 20 } 21 root = dummy->next; 22 } 23 }
distinct-subsequences
dp :当前位置选或不选
1 int numDistinct(string S, string T) { 2 if(S.size()==0||T.size()==0)return 0; 3 vector<int> dp(T.size()+1,0); 4 dp[0]=1;//dummy 5 int len = T.size(); 6 for(int i=1;i<=S.size();i++) 7 { 8 for(int j=min(i,len);j>0;j--) 9 if(S[i-1]==T[j-1]) 10 dp[j]=dp[j-1]+dp[j]; 11 } 12 return dp[len]; 13 }
binary-tree-level-order-traversal-ii
倒序的层序遍历
1 bfs层序遍历后reverse,虽然能做但这个方法太low不符合命题者出题意图
2 查出max depth后,dfs从第一层max_depth到最后一层1,用一个数组存储
3 递归版bfs,很巧,不直接存到result中,先进行下一层递归,存最后一层然后一层一层递归上来,再把当前层的结果保存到result中 。
binary-tree-zigzag-level-order-traversal
1. 这题用bfs+size+tag-reverse可以搞,但比较low
2. 可以双栈搞。注意第二个栈的入栈顺序,先右再左
rotate-image
先关于主对角线对称,然后关于中间行对称。
* merge-k-sorted-lists
1. merge sort 分治做
2. 优先队列最小堆做
分布式常考,解法见blog
分治写法
1 /** 2 * Definition for singly-linked list. 3 * struct ListNode { 4 * int val; 5 * ListNode *next; 6 * ListNode(int x) : val(x), next(NULL) {} 7 * }; 8 */ 9 class Solution { 10 public: 11 ListNode *mergeKLists(vector<ListNode *> &lists) { 12 if (lists.size() == 0) return NULL; 13 return merge(lists,0,lists.size()-1); 14 } 15 ListNode* merge(vector<ListNode *> &lists, int l,int r) 16 { 17 if(l<r) { 18 int mid = (l+r) / 2; 19 ListNode* left = merge(lists, l,mid); 20 ListNode* right = merge(lists, mid+1,r); 21 return mergeTwoLists(left,right); 22 } 23 else return lists[l]; 24 } 25 ListNode *mergeTwoLists(ListNode *l1, ListNode *l2) { 26 ListNode *head = new ListNode(-1); 27 ListNode *cur = head; 28 while (l1 && l2) { 29 if (l1->val < l2->val) { 30 cur->next = l1; 31 l1 = l1->next; 32 } else { 33 cur->next = l2; 34 l2 = l2->next; 35 } 36 cur = cur->next; 37 } 38 if (l1) cur->next = l1; 39 if (l2) cur->next = l2; 40 return head->next; 41 } 42 };
最小堆写法:
首先把k个链表的首元素都加入最小堆中,它们会自动排好序。然后我们每次取出最小的那个元素加入我们最终结果的链表中,然后把取出元素的下一个元素再加入堆中,下次仍从堆中取出最小的元素做相同的操作,以此类推,直到堆中没有元素了,此时k个链表也合并为了一个链表,返回首节点
1 struct cmp { 2 bool operator () (ListNode *a, ListNode *b) { 3 return a->val > b->val; 4 } 5 }; 6 7 class Solution { 8 public: 9 ListNode *mergeKLists(vector<ListNode *> &lists) { 10 priority_queue<ListNode*, vector<ListNode*>, cmp> q;//最小堆 11 for (int i = 0; i < lists.size(); ++i) { 12 if (lists[i]) q.push(lists[i]); 13 } 14 ListNode *head = NULL, *pre = NULL, *tmp = NULL; 15 while (!q.empty()) { 16 tmp = q.top(); 17 q.pop(); 18 if (!pre) head = tmp; 19 else pre->next = tmp; 20 pre = tmp; 21 if (tmp->next) q.push(tmp->next); 22 } 23 return head; 24 } 25 };
remove-duplicates-from-sorted-array-ii
给一个排好序的数组,最多允许2个重复元素。注意要和筛选后的数组比较,不是和原数组比较
1 int removeDuplicates(int A[], int n) { 2 3 if(n<3)return n; 4 int index=2; 5 for(int i=2;i<n;i++) 6 { 7 if(A[i]!=A[index-2]) 8 A[index++]=A[i]; 9 } 10 return index; 11 }
subsets
递增的全排列,要考虑初始数组不是顺序的情况,dp来做看代码,追加法写的很好,最后按元素数排序输出
1 vector<vector<int> > subsets(vector<int> &S) { 2 vector<vector<int>> result; 3 vector<vector<int>> res; 4 vector<int> v; 5 result.push_back(v); 6 res.push_back(v); 7 for(int i=0;i<S.size();i++){ 8 int n = result.size(); 9 for(int j=0;j<n;j++){ 10 result.push_back(result[j]); 11 result[j].push_back(S[i]); 12 sort(result[j].begin(), result[j].end());// 原数组不是有序数组 13 } 14 15 } 16 sort(result.begin(), result.end()); 17 for(int j=1;j<=S.size();j++) 18 for(int k=0;k<result.size();k++) 19 if(result[k].size() == j) 20 res.push_back(result[k]); 21 return res; 22 }
combinations
sb了这题,看清样例是啥再做行吗,又tm上来就无脑dfs,人家是有序无重复的结果
1 vector<vector<int> > combine(int n, int k) 2 { 3 vector<vector<int>> res; 4 vector<int> tmp; 5 dfs(n,k,1,res,tmp); 6 return res; 7 } 8 void dfs(int n,int k,int start,vector<vector<int>> &res,vector<int> tmp) 9 { 10 if(tmp.size() == k) 11 { 12 res.push_back(tmp); 13 return; 14 } 15 for(int i=start;i<=n;i++) 16 { 17 tmp.push_back(i); 18 dfs(n,k,i+1,res,tmp); 19 tmp.pop_back(); 20 } 21 }
set-matrix-zeroes
使用常数空间,如果矩阵某个元素为0,则其所在行和列都为0
方法1. 主要防止覆盖问题,可以先用-1处理所在行和列元素,最后还原为0。
方法2. 用第一行和第一列作为统计标识,首先要判断第一行和第一列是否需要清零,然后遍历内部矩阵并记录,再根据记录清零,最后看是否需要清零第一行第一列。
Simplify Path
字符串好题
思路一:针对每一个需求分别处理
1 string simplifyPath(string path) { 2 int pos; 3 while((pos = path.find("//"))!=-1) //去掉// 4 { 5 path.erase(pos,1); 6 } 7 while((pos = path.find("/./"))!=-1) //去掉/./ 8 { 9 path.erase(pos,2); 10 } 11 while((pos = path.find("/../"))!=-1)//去掉/../ 12 { 13 path.erase(pos,3); 14 if(pos==0)continue; 15 int p = path.rfind("/",pos-1); 16 path.erase(p,pos-p); 17 } 18 if(path.size()>2&&path.substr(path.size()-3,3)=="/.."){//去掉/.. 19 path.erase(path.size()-2,2); 20 if(int(path.size())-2>=0){ 21 int p = path.rfind("/",path.size()-2); 22 path.erase(p,path.size()-1-p); 23 } 24 } 25 if(path.size()>1&&path.substr(path.size()-2,2)=="/.")path.erase(path.size()-1,1); //去掉/. 26 if(path.size()>1&&path[path.size()-1]=='/')path.erase(path.size()-1,1);//处理结尾 27 return path; 28 }
思路二:遍历一遍,遍历过程中综合考虑各种因素
1 class Solution { 2 public: 3 string simplifyPath(string path) { 4 vector<string> v; 5 int i = 0; 6 while (i < path.size()) { 7 while (path[i] == '/' && i < path.size()) ++i; 8 if (i == path.size()) break; 9 int start = i; 10 while (path[i] != '/' && i < path.size()) ++i; 11 int end = i - 1; 12 string s = path.substr(start, end - start + 1); 13 if (s == "..") { 14 if (!v.empty()) v.pop_back(); 15 } else if (s != ".") { 16 v.push_back(s); 17 } 18 } 19 if (v.empty()) return "/"; 20 string res; 21 for (int i = 0; i < v.size(); ++i) { 22 res += '/' + v[i]; 23 } 24 return res; 25 } 26 };
%是带有符号的,这点要注意
spiral-matrix-ii
螺旋数组正确写法(JAVA)
1 public int[][] generateMatrix(int n) { 2 int[][] res = new int[n][n]; 3 if (n < 1) 4 return res; 5 int index = 1, rowStart = 0, rowEnd = n - 1, colStart = 0, colEnd = n - 1; 6 while (index <= n * n) { 7 for (int i = colStart; i <= colEnd; i++) { 8 res[rowStart][i] = index++; 9 } 10 for (int i = rowStart + 1; i <= rowEnd; i++) { 11 res[i][colEnd] = index++; 12 } 13 for (int i = colEnd - 1; i >= colStart; i--) { 14 res[rowEnd][i] = index++; 15 } 16 for (int i = rowEnd - 1; i > rowStart; i--) { 17 res[i][colStart] = index++; 18 } 19 20 rowStart += 1; 21 rowEnd -= 1; 22 colStart += 1; 23 colEnd -= 1; 24 25 } 26 27 return res; 28 }
spiral-matrix
1 while(l<=r&&up<=down) 2 { 3 for(int i=l;i<=r;i++) 4 ans.push_back(matrix[up][i]); 5 for(int i=up+1;i<=down;i++) 6 ans.push_back(matrix[i][r]); 7 for(int i=r-1;up!=down&&i>=l;i--) //由于矩阵是任意的,注意往返不能重复 8 ans.push_back(matrix[down][i]); 9 for(int i=down-1;l!=r&&i>up;i--)//注意往返不能重复 10 ans.push_back(matrix[i][l]); 11 l++,r--,up++,down--; 12 }
search-for-a-range
两次二分夹逼法,注意等号的选择
1 int l1=0,r1=n-1,l2=0,r2=n-1; 2 while(l1 <= r1) 3 { 4 int mid = l1+((r1-l1)>>1); 5 if(A[mid] < target) 6 l1 = mid + 1; 7 else 8 r1 = mid - 1; 9 } 10 while(l2 <= r2) 11 { 12 int mid = l2+((r2-l2)>>1); 13 if(A[mid] > target) 14 r2 = mid - 1; 15 else 16 l2 = mid + 1; 17 } 18 vector<int> result(2,-1); 19 if(l1 <= r2) 20 { 21 result[0] = l1; 22 result[1] = r2; 23 } 24 return result; 25 }
first-missing-positive
思路:将数值和位置形成映射,使得A[i]=i+1
1 int firstMissingPositive(int A[], int n) { 2 for(int i=0;i<n;) 3 { 4 if((A[i]<=0||A[i]>n)&&i<n) 5 { 6 i++; 7 } 8 else if(A[A[i]-1]!=A[i])// 注意不能直接写A[i]!=i+1 因为[1,1]可能死循环 9 { 10 swap(A[i],A[A[i]-1]); 11 } 12 else i++; 13 14 } 15 int i; 16 for(i=0;i<n;i++) 17 if(A[i]!=i+1)break; 18 return i+1; 19 }
search-in-rotated-sorted-array
旋转排序数组找target
思路:二分后,一定按顺序的找,注意等号
1 int search(int A[], int n, int target) { 2 int l=0,r=n-1; 3 while(l<=r) 4 { 5 int mid = (l+r)/2; 6 if(A[mid]==target)return mid; 7 if(A[l]<=A[mid])//等号必须在这里,eg:[3,1] 1 8 { 9 if(A[l]<=target&&target<A[mid]) 10 r=mid-1; 11 else l=mid+1; 12 } 13 else 14 { 15 if(A[mid]<target&&target<=A[r]) 16 l=mid+1; 17 else r=mid-1; 18 } 19 } 20 return -1; 21 }
unique-binary-search-trees
分阶段,先看根节点,可以1到n,当根节点确定时找状态转移,因为是bst,所以左子树为i-1个结点,右子树为n-i个结点,两者乘积
1 int dp[1000]={0}; 2 int numTrees(int n) { 3 dp[1]=1; 4 dp[0]=1; 5 for(int i=2;i<=n;i++) 6 { 7 for(int j=1;j<=i;j++) 8 dp[i]+=dp[j-1]*dp[i-j]; 9 } 10 return dp[n]; 11 }
unique-binary-search-trees II
1 vector<TreeNode *> generateTrees(int n) { 2 return dfs(1,n); 3 } 4 vector<TreeNode *> dfs(int l,int r){ 5 vector<TreeNode*> ans; 6 if(l>r) {ans.push_back(NULL);return ans;} 7 for(int i=l;i<=r;i++){ 8 vector<TreeNode*> vecl=dfs(l,i-1),vecr=dfs(i+1,r); 9 for(auto p:vecl) 10 for(auto q:vecr){ 11 TreeNode *root=new TreeNode(i); 12 root->left=p; 13 root->right=q; 14 ans.push_back(root); 15 } 16 } 17 return ans; 18 }
minimum-window-substring
尺取法O(n), 在S串中遍历一次,找到包含T串中所有元素的最小子串
思路:因为无序所以hash,map记录元素及对应个数,保留T串整体信息
l=0,r从头向后遍历,直到子串中元素满足map的记录,用count==T.size()判断,记录子串,l右移直到越过第一个T中的元素使子串不满足map条件,此时确定l边界,r右移确定右边界
1 class Solution { 2 public: 3 string minWindow(string S, string T) { 4 string res; 5 map<char,int> t,s; 6 for(auto c:T) 7 t[c]++; 8 int count=0,l=0; 9 for(int r=0;r<S.length();r++) 10 { 11 if(t[S[r]]!=0) 12 { 13 s[S[r]]++; 14 if(s[S[r]]<=t[S[r]]) 15 count++; 16 while(count==T.size()) 17 { 18 if(res.empty()||res.length()>r-l+1) 19 res=S.substr(l,r-l+1); 20 if(t[S[l]]) 21 { 22 s[S[l]]--; 23 if(s[S[l]]<t[S[l]]) 24 count--; 25 } 26 l++; 27 } 28 } 29 } 30 return res; 31 } 32 };
longest-common-prefix:对所有string排序后比较首尾即可
roman-to-integer:当前一个罗马数字小于后一个时是特例,要减去2倍的前一个数字值
regular-expression-matching
方法一:递归求解
先来判断p是否为空,若为空则根据s的为空的情况返回结果。当p的第二个字符为*号时,由于*号前面的字符的个数可以任意,可以为0,那么先用递归来调用为0的情况,就是直接把这两个字符去掉再比较,或者当s不为空,且第一个字符和p的第一个字符相同时,我们再对去掉首字符的s和p调用递归,注意p不能去掉首字符,因为*号前面的字符可以有无限个;如果第二个字符不为*号,那么就的比较第一个字符,然后对后面的字符串递归
1 class Solution { 2 public: 3 bool isMatch(string s, string p) { 4 if (p.empty()) return s.empty(); 5 if (p.size() > 1 && p[1] == '*') { 6 return isMatch(s, p.substr(2)) || (!s.empty() && (s[0] == p[0] || p[0] == '.') && isMatch(s.substr(1), p)); //注意这里 7 } else { 8 return !s.empty() && (s[0] == p[0] || p[0] == '.') && isMatch(s.substr(1), p.substr(1)); 9 } 10 } 11 };
方法二:dp
我们也可以用DP来解,定义一个二维的DP数组,其中dp[i][j]表示s[0,i)和p[0,j)是否match,然后有下面三种情况:
1. P[i][j] = P[i - 1][j - 1], if p[j - 1] != '*' && (s[i - 1] == p[j - 1] || p[j - 1] == '.');
2. P[i][j] = P[i][j - 2], if p[j - 1] == '*' and the pattern repeats for 0 times;
3. P[i][j] = P[i - 1][j] && (s[i - 1] == p[j - 2] || p[j - 2] == '.'), if p[j - 1] == '*' and the pattern repeats for at least 1 times.
1 class Solution { 2 public: 3 bool isMatch(string s, string p) { 4 int m = s.size(), n = p.size(); 5 vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false)); 6 dp[0][0] = true; 7 for (int i = 0; i <= m; ++i) { 8 for (int j = 1; j <= n; ++j) { 9 if (j > 1 && p[j - 1] == '*') { 10 dp[i][j] = dp[i][j - 2] || (i > 0 && (s[i - 1] == p[j - 2] || p[j - 2] == '.') && dp[i - 1][j]); 11 } else { 12 dp[i][j] = i > 0 && dp[i - 1][j - 1] && (s[i - 1] == p[j - 1] || p[j - 1] == '.'); 13 } 14 } 15 } 16 return dp[m][n]; 17 } 18 };
divide-two-integers:倍增法用的好,减少循环次数
题意:不能用乘法,除法,取余操作,实现整数除法
1 int divide(int dividend, int divisor) { 2 if(divisor == 0) 3 return INT_MAX; 4 5 long long result=0,flag=0; 6 if((dividend<0)^(divisor<0)) 7 flag=1; 8 9 long long a = abs(dividend), b = abs(divisor); 10 while(a >= b) 11 { 12 long long k = 1, t = b; 13 while(a >= t) 14 { 15 a -= t; 16 result += k; 17 k <<= 1; 18 t += t; 19 } 20 } 21 return flag?-result:result; 22 }
longest-substring-without-repeating-characters
双指针维护区间,尺取法枚举可能解,这里的词典只记录字母是否出现,并不记录位置。
1 int lengthOfLongestSubstring(string s) { 2 int len = s.size(); 3 int dic[128]={0}; 4 int ans=0,p=0; 5 for(int q=0;q<len;q++) 6 { 7 while(dic[s[q]]) 8 { 9 dic[s[p]]--; 10 p++; 11 } 12 ans=max(ans,q-p+1); 13 dic[s[q]]++; 14 } 15 return ans; 16 }
longest-palindromic-substring
二维dp 0:不回文,1:回文。注意奇数偶数的初始情况。
1 string longestPalindrome(string s) { 2 int len = s.length(),ans; 3 int dp[len+1][len+1]; 4 memset(dp,0,sizeof(dp)); 5 int rec[len+1]; 6 for(int i = 0;i < len;i++) dp[i][i] = 1,ans = 1,rec[1] = 0; 7 for(int i = 0;i < len-1;i++) if(s[i] == s[i+1]) ans = 2,rec[2] = i,dp[i][i+1] = 1; 8 for(int i = 3;i <= len;i++){ 9 for(int j = 0;j <= len-i;j++){ 10 if(s[j] == s[j+i-1]) { 11 dp[j][i+j-1] = dp[j+1][i+j-2]; 12 if(dp[j][i+j-1] == 1) {ans = max(ans,i),rec[i] = j;} 13 } 14 } 15 } 16 return s.substr(rec[ans],ans); 17 }
median-of-two-sorted-arrays
分治,注意递归的出口,一个是k=1,另一个是边界
1 double findMedianSortedArrays(int A[], int m, int B[], int n) { 2 if((m+n)%2==0) 3 return (help(A,0,m,B,0,n,(m+n)/2)+help(A,0,m,B,0,n,(m+n)/2+1))/2; 4 else 5 return help(A,0,m,B,0,n,(m+n)/2+1); 6 } 7 double help(int A[], int aStart, int m, int B[], int bStart, int n, int k) 8 { 9 if(aStart >= m) 10 return B[bStart+k-1]; 11 if(bStart >= n) 12 return A[aStart+k-1]; 13 if(k==1) 14 return min(A[aStart],B[bStart]); 15 int aMin = 0x7fffffff, bMin = 0x7fffffff; 16 if(aStart + k / 2 - 1 < m) 17 aMin = A[aStart + k/2 -1]; 18 if(bStart + k / 2 - 1 < n) 19 bMin = B[bStart + k / 2 - 1]; 20 if(aMin<bMin) 21 return help(A,aStart+k/2,m,B,bStart,n,k-k/2); 22 else 23 return help(A,aStart,m,B,bStart+k/2,n,k-k/2); 24 }
3sum
选出数组中相加为0的3个数,要求非递减,不重复
思路:fix一个数转换为2sum+set会超时
先排序,固定一个数,确定后两个数的和。在后面的数组中两头向中间找,注意剪枝与去重的操作
1 vector<vector<int> > threeSum(vector<int> &num) { 2 vector<vector<int> > ans; 3 sort(num.begin(),num.end()); 4 if(num.empty()||num.front()>0||num.back()<0)return {}; 5 for(int i=0;i<num.size()-2;i++) 6 { 7 if(num[i]>0)break; 8 if(i>0&&num[i]==num[i-1])continue; 9 int target = 0 - num[i]; 10 int j=i+1,k=num.size()-1; 11 while(j<k) 12 { 13 if(num[j]+num[k]==target) 14 { 15 ans.push_back({num[i],num[j],num[k]}); 16 while(j<k&&num[j]==num[j+1])j++; 17 while(j<k&&num[k]==num[k-1])k--; 18 j++,k--; 19 } 20 else if(num[j]+num[k]<target) 21 j++; 22 else 23 k--; 24 } 25 } 26 return ans; 27 }
reverse-nodes-in-k-group
这题考察链表反转,头插的双指针写法
1 ListNode *reverseKGroup(ListNode *head, int k) { 2 if(head == NULL || head->next == NULL || k < 2) return head; 3 ListNode* dummy = new ListNode(0); 4 dummy->next = head; 5 ListNode* pre = dummy, *cur = head, *temp; 6 int len = 0; 7 while (head != NULL) { 8 len++ ; 9 head = head->next; 10 } 11 for (int i = 0; i < len / k; i ++ ) { 12 for (int j = 1; j < k; j ++ ) { 13 temp = cur->next; 14 cur->next = temp->next; 15 temp->next = pre->next; 16 pre->next = temp; 17 } 18 pre = cur; 19 cur = cur->next; 20 } 21 return dummy->next; 22 }
generate-parentheses
产生括号的全排列
思路:全排列,容易想到dfs,由于括号包含左括号和右括号两部分,所以一方面不用考虑左括号,让他不断生成就好,对于右括号,设置两种情况,分别是生成和不生成右括号。
1 vector<string> generateParenthesis(int n) { 2 vector<string> s; 3 dfs(0,0,"",s,n); 4 return s; 5 } 6 void dfs(int left, int right, string tmp, vector<string> & s, int n) 7 { 8 if(left==n&&right==n) 9 { 10 s.push_back(tmp); 11 return; 12 } 13 if(left<n) 14 dfs(left+1,right,tmp+'(',s,n); 15 if(left>right&&right<n) 16 dfs(left,right+1,tmp+')',s,n); 17 }
substring-with-concatenation-of-all-words
这道题空间换时间的思路特别,不同word concate的组合非常多,枚举非常不明智,那么就hash好了。接下来就是暴力匹配,内部循环words.size()次,用另一个hash map记录匹配到的word个数。
O(n)方法,尺取法,每次尺取一个word长度,还是两个hashmap
1 vector<int> findSubstring(string S, vector<string>& L) { 2 if (S.empty() || L.empty())return {}; 3 vector<int> res; 4 int n = S.size(), cnt = L.size(), len = L[0].size(); 5 unordered_map<string, int> m1; 6 for (string w : L) ++m1[w]; 7 for (int i = 0; i < len; i++) 8 { 9 int left = i, count = 0; 10 unordered_map<string, int> m2; 11 for (int j = i; j <= n - len; j += len) 12 { 13 string t = S.substr(j,len); 14 if (m1.count(t)) 15 { 16 ++m2[t]; 17 if (m2[t] <= m1[t]) 18 ++count; 19 else 20 { 21 while (m2[t] > m1[t]) 22 { 23 string tmp = S.substr(left, len); 24 m2[tmp]--; 25 if (m2[tmp] < m1[tmp])count--; 26 left += len; 27 } 28 } 29 if (count == cnt) 30 { 31 res.push_back(left); 32 m2[S.substr(left, len)]--; 33 --count; 34 left += len; 35 } 36 } 37 else 38 { 39 m2.clear(); 40 count = 0; 41 left = j + len; 42 } 43 } 44 } 45 sort(res.begin(),res.end()); 46 return res; 47 }
longest-valid-parentheses
找出最长的合法括号子串,输出长度
常规做法,栈中存左括号,栈空时记录起始节点位置
1 int longestValidParentheses(string s) { 2 stack<int> st; 3 int ans=0,t=-1; 4 if(s.size()==0)return 0; 5 for(int i=0;i<s.size();i++) 6 { 7 if(s[i]=='(')st.push(i); 8 else 9 { 10 if(st.empty())t=i; 11 else 12 { 13 st.pop(); 14 if(st.empty()) 15 ans = max(ans,i-t); 16 else 17 ans = max(ans,i-st.top()); 18 } 19 } 20 } 21 return ans; 22 }
Largest Rectangle in Histogram
1 class Solution { 2 public: 3 int largestRectangleArea(vector<int>& heights) { 4 int res = 0; 5 stack<int> st; 6 heights.push_back(0); 7 for (int i = 0; i < heights.size(); ++i) { 8 while (!st.empty() && heights[st.top()] >= heights[i]) { 9 int cur = st.top(); st.pop(); 10 res = max(res, heights[cur] * (st.empty() ? i : (i - 1 - st.top())));// 注意这里是st.top()而不是cur,两者之间有可行的宽度,前一次出栈了 11 } 12 st.push(i); 13 } 14 return res; 15 } 16 };
Longest Consecutive Sequence 求最长连续序列
O(n)时间复杂度
方法(1) hash set求解,将序列存入set,对每个数,从左右两个方向展开搜索hash匹配,然后删除这个数,避免重复搜索
方法(2) hash map求解,map初始为空,如果map[x]有值则表示已访问,continue(这里强调用count判断,如果用默认map映射判断,会自动生成一个为0的映射),只需判断map[x-1], map[x+1]即可,更新ma[x-left], map[x+right]的值。
1 class Solution { 2 public: 3 int longestConsecutive(vector<int>& nums) { 4 int res = 0; 5 unordered_map<int, int> m; 6 for (int num : nums) { 7 if (m.count(num)) continue; 8 int left = m.count(num - 1) ? m[num - 1] : 0; 9 int right = m.count(num + 1) ? m[num + 1] : 0; 10 int sum = left + right + 1; 11 m[num] = sum; 12 res = max(res, sum); 13 m[num - left] = sum; 14 m[num + right] = sum; 15 } 16 return res; 17 } 18 };
Course schedule(拓扑排序)
方法一:bfs+入度
方法二:dfs+访问标记(标记循环) 0表示还未访问过,1表示已经访问了(为了剪枝),-1 表示有冲突(一次dfs中判断是否有环)
1 class Solution { 2 public: 3 bool canFinish(int numCourses, vector<vector<int>>& prerequisites) { 4 vector<vector<int>> graph(numCourses, vector<int>()); 5 vector<int> visit(numCourses); 6 for (auto a : prerequisites) { 7 graph[a[1]].push_back(a[0]); 8 } 9 for (int i = 0; i < numCourses; ++i) { 10 if (!canFinishDFS(graph, visit, i)) return false; 11 } 12 return true; 13 } 14 bool canFinishDFS(vector<vector<int>>& graph, vector<int>& visit, int i) { 15 if (visit[i] == -1) return false; 16 if (visit[i] == 1) return true; 17 visit[i] = -1; 18 for (auto a : graph[i]) { 19 if (!canFinishDFS(graph, visit, a)) return false; 20 } 21 visit[i] = 1; 22 return true; 23 } 24 };
Sliding Window Maximum
1 最大堆 pair<value, position> 2 双端队列,维护按序单调递减
1 class Solution { 2 public: 3 vector<int> maxSlidingWindow(vector<int>& nums, int k) { 4 vector<int> res; 5 deque<int> q; 6 for (int i = 0; i < nums.size(); ++i) { 7 if (!q.empty() && q.front() == i - k) q.pop_front(); // 删除左边元素 8 while (!q.empty() && nums[q.back()] < nums[i]) q.pop_back();// 保持队列单调递减 9 q.push_back(i); 10 if (i >= k - 1) res.push_back(nums[q.front()]); 11 } 12 return res; 13 } 14 };
Meeting Rooms II
计算线段最大重叠个数,对于线段,可以特殊处理,起始点+1,终止点-1,这样遍历的过程可以模拟实线段
1 class Solution { 2 public: 3 int minMeetingRooms(vector<Interval>& intervals) { 4 map<int, int> m; 5 for (auto a : intervals) { 6 ++m[a.start]; 7 --m[a.end]; 8 } 9 int rooms = 0, res = 0; 10 for (auto it : m) { 11 res = max(res, rooms += it.second); 12 } 13 return res; 14 } 15 };
LRU cache
链表 和 map 实现 LRU 内存换页
get 和 put, get 函数是通过输入 key 来获得 value,如果成功获得后, (key, value) 升至缓存器中最常用的位置(顶部),如果 key 不存在,则返回 -1。put 函数是插入一对新的 (key, value),如果原缓存器中有该 key,则需要先删除掉原有的,将新的插入到缓存器的顶部。如果不存在,则直接插入到顶部。若加入新的值后缓存器超过了容量,则需要删掉一个最不常用底部的值。
1 class LRUCache { 2 public: 3 LRUCache(int capacity) { 4 size = capacity; 5 } 6 7 int get(int key) { 8 auto it = hash.find(key); 9 if(it == hash.end()) return -1; 10 cache.splice(cache.begin(), cache, it->second); 11 return it->second->second; 12 } 13 14 void put(int key, int value) { 15 auto it = hash.find(key); 16 if(it != hash.end()){ 17 it->second->second = value; 18 return cache.splice(cache.begin(), cache, it->second); 19 } 20 cache.insert(cache.begin(), make_pair(key, value)); 21 hash[key] = cache.begin(); 22 if(cache.size() > size){ 23 hash.erase(cache.back().first); 24 cache.pop_back(); 25 } 26 } 27 private: 28 unordered_map<int, list<pair<int, int>>::iterator> hash; 29 list<pair<int, int>> cache; 30 int size; 31 }; 32 33 /** 34 * Your LRUCache object will be instantiated and called as such: 35 * LRUCache obj = new LRUCache(capacity); 36 * int param_1 = obj.get(key); 37 * obj.put(key,value); 38 */
python
1 class Node: 2 def __init__(self, k, v): 3 self.key = k 4 self.val = v 5 self.prev = None 6 self.next = None 7 8 class LRUCache: 9 def __init__(self, capacity): 10 self.capacity = capacity 11 self.dic = dict() 12 self.head = Node(0, 0) 13 self.tail = Node(0, 0) 14 self.head.next = self.tail 15 self.tail.prev = self.head 16 17 def get(self, key): 18 if key in self.dic: 19 n = self.dic[key] 20 self._remove(n) 21 self._add(n) 22 return n.val 23 return -1 24 25 def set(self, key, value): 26 if key in self.dic: 27 self._remove(self.dic[key]) 28 n = Node(key, value) 29 self._add(n) 30 self.dic[key] = n 31 if len(self.dic) > self.capacity: 32 n = self.head.next 33 self._remove(n) 34 del self.dic[n.key] 35 36 def _remove(self, node): 37 p = node.prev 38 n = node.next 39 p.next = n 40 n.prev = p 41 42 def _add(self, node): 43 p = self.tail.prev 44 p.next = node 45 self.tail.prev = node 46 node.prev = p 47 node.next = self.tail
Coin Change
DP有两种写法,一种为自底向上(dp迭代),另一种为自顶向下(dfs+记忆数组),两者等价
1 class Solution { 2 public: 3 int coinChange(vector<int>& coins, int amount) { 4 vector<int> memo(amount+1,INT_MAX); 5 memo[0]=0; 6 return dfs(coins,amount,memo); 7 } 8 int dfs(vector<int>& coins, int target, vector<int>& memo){ 9 if(target<0)return -1; 10 if(memo[target]!=INT_MAX)return memo[target]; 11 for(auto coin : coins){ 12 int tmp = dfs(coins, target - coin, memo); 13 if(tmp>=0) memo[target] = min(memo[target], tmp+1); 14 } 15 return memo[target] = (memo[target] == INT_MAX) ? -1 : memo[target]; 16 } 17 };
Validate IP Address 验证IP地址
注意getline的用法何stoi的用法很省事
getline(is, t, '.')
https://www.cnblogs.com/grandyang/p/6185339.html
Combination Sum II 组合之和之二
注意sort后
if (i > start && num[i] == num[i - 1]) continue;
https://www.cnblogs.com/grandyang/p/4419386.html
两个有序数组中位数
public double findMedianSortedArrays(int[] nums1, int[] nums2) { int n = nums1.length; int m = nums2.length; int left = (n + m + 1) / 2; int right = (n + m + 2) / 2; //将偶数和奇数的情况合并,如果是奇数,会求两次同样的 k 。 return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, left) + getKth(nums1, 0, n - 1, nums2, 0, m - 1, right)) * 0.5; } private int getKth(int[] nums1, int start1, int end1, int[] nums2, int start2, int end2, int k) { int len1 = end1 - start1 + 1; int len2 = end2 - start2 + 1; //让 len1 的长度小于 len2,这样就能保证如果有数组空了,一定是 len1 if (len1 > len2) return getKth(nums2, start2, end2, nums1, start1, end1, k); if (len1 == 0) return nums2[start2 + k - 1]; if (k == 1) return Math.min(nums1[start1], nums2[start2]); int i = start1 + Math.min(len1, k / 2) - 1; int j = start2 + Math.min(len2, k / 2) - 1; if (nums1[i] > nums2[j]) { return getKth(nums1, start1, end1, nums2, j + 1, end2, k - (j - start2 + 1)); } else { return getKth(nums1, i + 1, end1, nums2, start2, end2, k - (i - start1 + 1)); } }