首页 > 代码库 > leetcode题目思路以及部分解答(一)
leetcode题目思路以及部分解答(一)
为了进好公司这一个多月就得抽时间刷leetcode了。。感觉这个OJ很不严谨。。。好多边界条件都没说清处。。不过还好可以推测。唯一的好处就是不用自己编译调试,可以直接在网上显示出结果。当然,复杂一点的题目为了调试自己构建题目的结构也是很麻烦的。。。所以我发现提交里面错误好多。。。。。再就是在笔记本上会时不时的变卡。。。每次提交都得等个3,4分钟才成功。要不就502错误。。。
我的题目按照通过率来。从通过率最高的题目开始讲解。每题不一定是最优解,都是我想得。仅供参考。
题目标题我都标好了。可以用ctrl+F来查找题目。
全部代码都是用的C++写的。之前一直没时间熟悉STL,这次也好好熟悉了一遍。
所有代码我会尽量用比较高效的方式。
1.Single Number
这一题很简单。。原来看编程之美印象比较深刻,所以直接异或一遍就得出结果了。
2.Maximum Depth of Binary Tree
这个也很简单。直接递归即可。按照把左子树和右子树中偏大的+1返回即可。
3.Same Tree
这个就很简单了。。用递归!先判断是否是空树,然后用&&条件即可。因为是&&,所以把递归式子写在右边有直接断路的可能,减少了递归的次数。效率应该还可以。
4.Reverse Integer
没什么好说的。注意正负号即可。
5.Best Time to Buy and Sell Stock II
这个我想了半天。。画了一下图就明了了。只要是每次上涨点买(斜率变正),下降点(斜率变负)卖了即可。
这是个贪心的题目。每次赚取的都是差价,所以只要线每次只要是最近的一次上升点买,下降点卖了,一定赚得最多。
6.Unique Binary Search Trees
这个也画了半天。第一感觉是在n-1个点的全部图形基础之上插入第n个点。但自己画了4个点的情形发现有的点在中间插入了,这就不好分析了。其实按顺序画的过程也提醒了我。我发现如果把第n个点当作root的话,那么之前n-1个点的图可以全部放在这个点的左边。而且如果把第n-1个点放在root上,那么右边的格局就是定的。左边就是n-2个图的情况。同理把1放在root一样。所以规律也就出来了。因为是BST树,对于第i个点,左边有i-1个点,右边有n-i个点,并且互不干扰。也就是说左边和右边可以递归!由于leetcode没有告诉n最大是多少,不过看这个增长速度,估计500就差不多到达int上限了。下面是AC代码。
class Solution { public: static int table[500]; int numTrees(int n) { if ( table[n] != 0 ) return table[n]; int answer = 0; for (int i = 1 ; i <= n ; ++i){ int left,right; if ( table[i-1] != 0 ) left = table[i-1]; else left = numTrees(i-1),table[i-1] = left; if ( table[n-i] != 0 ) right = table[n-i]; else right = numTrees(n-i),table[n-i] = right; answer += left * right; } return answer; } }; int Solution::table[] = {1,1,2,5};
7.Linked List Cycle
这一题坑了我好久。。。一开始不清楚给的是一个额外的头节点还是本身内的。我就当按头节点在内部的来。直接把头节点的指针和循环指针比较当作判断来写。结果判定结果提交了好多次都错了。。。看了讨论才发现有可能链表是非整体循环的,也就是可能前多少个节点不再循环内。。。。。然后用剑指offer的方法过了,设置一快一慢两个指针不断循环即可。还有就是只有两个节点,并且不循环的情况。并且要注意一个节点循环也算循环= =
class Solution { public: bool hasCycle(ListNode *head) { if( head == NULL || head -> next == NULL ) return false; ListNode *slow = head; ListNode *fast = head; while( slow != NULL && fast != NULL && fast->next != NULL ){ slow = slow -> next; fast = fast -> next -> next; if ( slow == fast ) return true; } return false; } };
8.
Binary Tree Preorder Traversal
这个没什么好说的,就是前序遍历。我用的非递归的。思路就是每次在栈中保存右节点,取左节点。
class Solution { public: vector<int> preorderTraversal(TreeNode *root) { vector<int> answer; stack<TreeNode*> m_stack; if( root == NULL ) return answer; m_stack.push(root); while(!m_stack.empty()){ TreeNode *cur = m_stack.top(); m_stack.pop(); answer.push_back(cur->val); if (cur -> right != NULL ) m_stack.push(cur -> right); if (cur -> left != NULL ) m_stack.push(cur -> left); } return answer; } };
9.
Binary Tree Inorder Traversal
感觉中序遍历要难一些。按照我上面做先序的思路会搞得比较复杂。。做的时间也长些。。所以就看别人的自己试着写,但想写个完全没错的有点困难。。
class Solution { public: vector<int> inorderTraversal(TreeNode *root) { vector<int> answer; stack<TreeNode*> m_stack; TreeNode* cur = root; while(cur || !m_stack.empty()){ while ( cur ){ m_stack.push( cur ); cur = cur -> left; } cur = m_stack.top(); m_stack.pop(); answer.push_back( cur -> val ); cur = cur -> right; } return answer; } };
10.Populating Next Right Pointers in Each Node
我又想复杂了。。。用了两个队列层次遍历。。。其实很简单。。因为是完全二叉树。。如果是{1,#,2,3,4}这种树,那么就不行了。。还是得仔细读题。。。主要思想就是上一层的节点链接好了就可以用于生成下一层节点的链接。当然,这种方法只能链接是相邻的节点。
class Solution { public: void connect(TreeLinkNode *root) { if(root == NULL ) return ; while( root ){ TreeLinkNode *cur = root ; while( cur ){ if( cur -> left && cur -> right){ cur -> left -> next = cur -> right ; if( cur -> next && cur -> next -> left){ cur -> right -> next = cur -> next -> left; } } cur = cur -> next; } root = root -> left; } } };
11.Search Insert Position
这个直接二分查找就可以了。
class Solution { public: int searchInsert(int A[], int n, int target) { if ( A == NULL ) return -1; int start = 0; int end = n-1; while( start < end){ int mid = (start + end) / 2; if( target > A[mid]){ start = mid + 1; }else if ( target < A[mid] ){ end = mid; }else{ return mid; } } if( target <= A[start] ) return start; else return start+1; } };
12.Remove Duplicates from Sorted List
这个也很简单。只用从开头起,把当前节点和下一个节点的值进行比较,相同就删除,不同就更新指针。
class Solution { public: ListNode *deleteDuplicates(ListNode *head) { ListNode * temp = head; while( temp != NULL && temp -> next != NULL ){ if( temp -> val != temp -> next -> val) temp = temp -> next; else { ListNode *temp2 = temp -> next; temp -> next = temp2 -> next; free(temp2); } } return head; } };
13.Climbing Stairs
这个我印象比较深。。原来其他OJ在上做过。开始用DFS递归。。效率太差。。后来发现结果就是斐波那契数列啊。。好像剑指offer上面也说过。
class Solution { public: int climbStairs(int n) { if ( n <= 1) return 1; int a = 1; int b = 1; int answer; for( int i = 2; i <= n ; i++){ answer = a + b; a = b; b = answer; } return answer; } };
14.Roman to Integer
这题题目要求的就是输入一个罗马数字。。然后罗马数字规则参见 http://baike.baidu.com/view/42061.htm?fr=aladdin
从提交的ac代码来看,能用到的就是加减的判断了。。判断是否合法什么的都不用了。。
思路就是根据当前值从后往前判断,因为值小的字母在后面是+,在前面是减。所以可以用已经得到的值来判断这个字母的符号是正还是负。
class Solution { public: int romanToInt(string s) { int answer = 0; for (int i = s.length() - 1; i >= 0; i--) { char c = s[i]; switch (c) { case 'I': answer += ( answer >= 5 ? -1 : 1); break; case 'V': answer += 5; break; case 'X': answer += 10 * ( answer >= 50 ? -1 : 1); break; case 'L': answer += 50; break; case 'C': answer += 100 * ( answer >= 500 ? -1 : 1); break; case 'D': answer += 500; break; case 'M': answer += 1000; break; } } return answer; } };
15.Single Number II
这个之前有个题很类似。所以开始想用异或。但发现不行,即便是循环偶数遍或奇数遍都不行。只能换策略了。
要求的是线性效率,不用额外空间。所以只能用基本运算找规律了。。我直觉告诉我要用位运算。但基本运算好像不太容易直接统计出来。。
所以想了一下,就统计每个位上次数就行了。因为除了那唯一的数,其余每个数的位上的都会出现三次,如果模3后结果为1,那么就可以拼出来结果。
如果细算,这个是线性的,但是系数有点大32n.对于空间来说也稍微用了些空间,但是是常数空间效率。感觉还行。看讨论上也有很多人想出了这个方法,还有一个更高效的方法,喜欢的可以自己去研究了。
class Solution { public: int singleNumber(int A[], int n) { int answer = 0; int bits[32]; for ( int i = 0 ; i < 32 ; i++){ bits[i] = 0; for( int j = 0 ;j < n; j++){ if ( (A[j] >> i) & 1) bits[i]++; } answer |= ( bits[i] % 3) << i; } return answer; } };
16.Maximum Subarray
子数组最大和。这属于经典的动态规划题目。从后往前,依次存入子序列的结尾和当前最大和。可以自己去看编程之美,有好几种方法,包括题目要求的分治法。
class Solution { public: int max(int a,int b){ return a > b ? a : b; } int maxSubArray(int A[], int n) { if ( A == NULL || n == 0 ) return -1; if ( n == 1 ) return A[0]; int all[n]; int start; all[n - 1] = start = A[n - 1]; for (int i = n - 2 ; i >= 0 ; i--){ start = max( A[i], A[i]+start ); all[i] = max( start, all[i+1] ); } return all[0]; } };
17.Integer to Roman
刚开始想直接按十进制位来一个个的顺序输出的,但是我发现9和4不太好输入,但除开这两个数,其余的都可以用一个大数和多个小数凑起来。沿着这个思路,看能不能把9和4分开来表示。其输入的最大值是3999.所以3个M刚好够用,保证不用3个连续的M。对于4来说,可以用IV,对于9来说可以用IX。同理,40和90可以用XL和XC来表示,400和900用CD和CM表示。也就是按顺序来的话,可以用当前等级和小一级的数组合表示4,小两级表示9。每次处理两个字母就可以了。
调试半天,自己写的太复杂。。。参考了下讨论里面的代码(基本一致。。)。
class Solution { public: string intToRoman(int num) { char c[] = {'M','D','C','L','X','V','I'}; int val[] = {1000,500,100,50,10,5,1}; int cur = 0; string answer = ""; while(num){ if( cur % 2 == 0 && num / val[cur] == 4){ answer += c[cur]; answer += c[cur-1]; num -= 4*val[cur]; } else if ( num >= val[cur]){ answer += c[cur]; num -= val[cur]; } else if (cur % 2 == 0 && num / val[cur + 2] == 9){ answer += c[cur + 2]; answer += c[cur]; num -= 9*val[cur + 2]; } else cur++; } return answer; } };这个判断顺序是不能随便改的,否则容易造成错误。本来还想在 ==4和 ==9的地方直接更新指针,但是实际上很容易错。一来是对于49这种数可能cur需要停留,二来是对于 第二个判断来说并不一定每次执行了就更新。
18.Remove Element
这个比较简单。直接设置两个不同步的指针即可。题目也没说清楚数组到底要怎样,我还把最后重复的元素给置0了。。。但是我看讨论里面没有删除也过了。。就复制个讨论里的代码吧。
int removeElement(int A[], int n, int elem) { int begin=0; for(int i=0;i<n;i++) if(A[i]!=elem) A[begin++]=A[i]; return begin; }
19.Merge Two Sorted Lists
一天刷10题有点吃不消。。。而且时间有点晚了。。这么简单的题目做了好久。。。是时候休息一下了。。我的思路是以一条链表为基表,把第二条节点插进去。做完才发现完全可以自己弄一个头指针,然后把两个链表一个个加进来。。反正代码是AC了。。
class Solution { public: ListNode *mergeTwoLists(ListNode *l1, ListNode *l2) { if( l1 == NULL ) return l2; if( l2 == NULL ) return l1; ListNode *fir = l1; ListNode *sec = l2; ListNode *firlast = l1; if( l1->val > l2->val ) { l1 = l2; sec = l2->next; l2->next = fir; firlast = l2; fir = l1->next; } while( fir && sec ){ while( fir && sec && fir->val < sec->val ){ firlast = fir; fir = fir->next; } if( !(fir && sec) ) break; if( fir->val == sec->val ){ ListNode *temp = fir->next; ListNode *temp2 = sec->next; fir->next = sec; sec->next = temp; fir = sec; sec = temp2; }else{ firlast->next = sec; ListNode *temp = sec->next; sec->next = fir; firlast = sec; sec = temp; } } if(sec) firlast->next = sec; return l1; } };
20.N-Queens II
坚持再做一题。。用传统的判断和递归的方式要做很久而且效率很低,所以这次我百度了一下比较有效率的方法。发现原来还可以用位运算做。真是碉堡了。。。。
解题方法中有一个亮点,就是用p &-p来得到p中最右边的一个1的位置。原理就是原码转补码是原码各位取反然后+1得到的。所以刚好能得到最右的节点位置。
我把注释写到代码中吧。
class Solution { public: int answer, limit; int totalNQueens(int n) { answer = 0; limit = (1 << n) - 1; //将n位置一。 dfs(0,0,0); return answer; } void dfs(int h, int r, int l) { //h用来表示当前递归到多少层(棋盘上第几行),r表示从右上角斜下来的限制(即r中为1的地方不能放点) if (h == limit) { //接上,l表示从左上角斜下来的限制。已经保证横纵和两个斜向,所以只要填满就表示找到一种情况。 answer++; return; } int pos = limit & (~(h|r|l)); //这里也很巧妙,直接或,取反,再与,pos得到的就是这一排可以放置,但还没有被取到的列。 while (pos) { int p = pos & (-pos); //这个表示取最右边可行的列。 pos -= p; //避免下次递归再次取到这个点,所以把这一位置0.因为p二进制中只有1位为1. dfs(h+p, (r+p)<<1, (l+p)>>1); //递归,h+p表示下一行不能选取的列,(r+p)<<1和(l+p)>>1保证所有步骤中对下一行的限制。 } //总的来说,递归按行,h保证列,r和l保证斜向。 } };
21.Balanced Binary Tree
这题就是遍历了。和记录树的深度差不多。为了减少遍历次数,我还优化错了几次。。不过思路也简单,就是记录下左右子树的深度,然后比较差值。如果有差值超过1,那么就停止遍历。
class Solution { public: bool answer; bool isBalanced(TreeNode *root) { if ( root == NULL ) return 1; answer = true; DFS(root); return answer; } int DFS(TreeNode *root){ if( root == NULL ) return 0; int left,right; if( answer ) left = DFS( root->left ) + 1; else return 0; if( answer ) right = DFS( root->right ) + 1; else return 0; int balance = left - right; if( balance > 1 || balance <-1){ answer = false; return 0; } return left > right ? left : right ; } };
22.Swap Nodes in Pairs
这一题本来就想每次两个结点两个结点的换值,结果看说明不让。。。那就只能换点了。。还是错了几次才过。关键是1,2,3,4这个链表和1,2链表要考虑清楚。
class Solution { public: ListNode *swapPairs(ListNode *head) { if ( head == NULL ) return NULL; if ( head->next == NULL ) return head; ListNode *p = head; ListNode *pnext = head->next; ListNode *temp = pnext->next; ListNode *ret = pnext; ListNode *pre = p; while ( p ){ p->next = temp; pnext->next = p; if( pre != p) pre->next = pnext; pre = p; p = p->next; if( p != NULL && p->next) pnext = p->next,temp = pnext->next; else break; } return ret; } };
23.Convert Sorted Array to Binary Search Tree
做这题我突然意识到我这篇文章完全可以拆成好几篇来发布= = 然后点击量就上去了?想想还是算了,自己写博客是为了自己理清楚知识。所以看官们就将就这看一下吧。
这一题我开始还准备上AVL树。。但仔细一审题,发现是上升排序的数组。那么用二分法做就可以了。
首先设置一个队列Q和一个数据结构S。用Q来保存S
思路广度优先搜索类似,但这里是把二分的条件放到S里。然后每次取出条件,然后二分插入。
typedef struct{ TreeNode *father; int start; int end; bool dir; }myNode; #define LEFT 0 #define RIGHT 1 class Solution { public: TreeNode * root; TreeNode *sortedArrayToBST(vector<int> &num) { int length = num.size(); if( num.size() == 0 ) return NULL; queue<myNode> myQueue; myNode firstNode; firstNode.father = NULL; firstNode.start = 0; firstNode.end = length - 1; myQueue.push(firstNode); while( !myQueue.empty() ){ myNode &cur = myQueue.front(); myNode temp; if( cur.start == cur.end ){ insert(cur.father, num[cur.start], cur.dir); } else if( cur.start < cur.end) { int middle = (cur.start + cur.end) / 2; TreeNode *Pcur; Pcur = insert(cur.father, num[ middle ], cur.dir); temp.father = Pcur; ///left temp.dir = LEFT; temp.start = cur.start; temp.end = middle - 1; myQueue.push(temp); //right temp.dir = RIGHT; temp.start = middle + 1; temp.end = cur.end; myQueue.push(temp); } myQueue.pop(); } return root; } TreeNode* insert(TreeNode *father, int val, bool dir){ TreeNode * item; item = (TreeNode *)malloc(sizeof(TreeNode)); item->val = val; item->left = item->right = NULL; if( father == NULL ){ root = item; return item; } if( !dir ) father->left = item; else father->right = item; return item; } };
24.Remove Duplicates from Sorted Array
这题和前面的18题一样,改一下即可。就是要注意空数组返回0
class Solution { public: int removeDuplicates(int A[], int n) { if ( A == NULL || n == 0) return 0; int elem = A[0]; int begin = 1; for(int i = 1 ; i < n ; i++){ if( elem != A[i] ){ elem = A[i]; A[ begin++ ] = A[i]; } } return begin; } };
25.Symmetric Tree
这种对称的题目一般可以用递归做。直觉上直接对称的遍历效率比较高。从题目来看其树是按root来对称,所以递归要保证两个树上两条线是反向的。简单来说,如果有一条到叶子结点路径是LL,那么对称的一条就是RR的路径,同理LR对应RL。非递归的话,用栈来存储方向的思路应该也可以。当然这就麻烦了。
class Solution { public: bool isSymmetric(TreeNode *root) { if ( root == NULL ) return true; return twoside(root->left, root->right); } bool twoside(TreeNode *left, TreeNode *right){ if( left == NULL && right == NULL ) return true; if( left == NULL || right == NULL || left->val != right-> val) return false; return twoside(left->left, right->right) && twoside(left->right, right->left); } };
26.Gray Code
对于格雷码生成我说明一下算法。
从全0串开始,第一个串为全0串;
如果当前串有偶数个位为1,则下一个串为将当前串最低位反转所得;
如果当前串有奇数个位为1,则找出当前串中为1的最低位,将该位的高一位反转即可得到下一个串;循环以上步骤直至n位二进制串溢出。
这是我在网上找的。。数电的东西差不多都快忘了。
我刚开始又理解错题目意思了。。。。。。。。我以为是自动生成格雷码然后其值为n时返回序列。。。是说怎么不对。。。
既然生成规则已经讲明。算法思路也就清楚了。当然我这种是比较直接的方式,我看讨论里面的方法比我的有效的多。。。。。算了,估计用的数学方法,也就不深究了。
class Solution { public: int gray[40]; int maxlength; vector<int> grayCode(int n) { vector<int> answer; if ( n == 0 ){ answer.push_back(0); return answer; } if ( n == 1){ answer.push_back(0); answer.push_back(1); return answer; } maxlength = n - 1; for(int i = 0 ; i <= maxlength ; i++){ gray[i] = 0; } answer.push_back(0); unsigned int count = 1; unsigned int limit = ( 1 << n ); int check; while(count < limit){ next(); check = graytoint(); answer.push_back( check ); count ++ ; } return answer; } int graytoint(){ int ret = 0; for(int i = 0 ; i <= maxlength ; i++ ){ if( gray[i] ) ret |= ( 1 << (maxlength - i) ); } return ret; } void next(){ int t = 0; int i; for (i = 0; i <= maxlength ; t += gray[i],i++ ); if ( t & 1 ) for ( i-- ; !gray[i] ; i-- ); gray[ i - 1 ]= 1 - gray[ i - 1 ]; } };
27.Merge Sorted Array
开始又想复杂了。。开始的时候我想的是顺序插入,把A还未遍历的放在B的前端。当然着很复杂了,写了半天,感觉很乱,写不下去才发现直接从m+n-1的位置开始反着遍历就够了。。。效率也高。。
class Solution { public: int start; int end; int *a; int *b; void merge(int A[], int m, int B[], int n) { if( B == NULL || n == 0) return ; if( A == NULL || m == 0){ for(int i = 0 ; i < n ; i++ ) A[i] = B[i]; return ; } int i; for( i = m + n -1 ,m--, n-- ; i >= 0 ; i--){ if ( A[m] < B[n] ){ A[i] = B[n--]; } else { A[i] = A[m--]; } if( m < 0 || n < 0){ i--; break; } } while( i >= 0 && n >= 0 ){ A[i--] = B[n--]; } } };我看讨论里比较简介的和我思路差不多,就是前面的判断少一些。
28.Sort Colors
这个就是三色旗的问题。直接两端当作栈即可。由于很久之前写的,也没上OJ验证,发现细节位置没注意。。
注意以下几个数据:[0],[1,0],[0,0,],[1,2,0]。也就是当
class Solution { public: void sortColors(int A[], int n) { if( n == 1) return ; int red = 0; int blue = n - 1; for(int i = 0 ; i <= blue ; ){ if(A[i] == 0 ){ if( i != red){ //为了让0连续出现的时候i可以更新 swap(A[i],A[red++]); continue; } } else if (A[i] == 2) { swap(A[i],A[blue--]); continue; } i++; } } void swap(int &a,int &b){ int temp = a; a = b; b = temp; } };
29.Pascal‘s Triangle
这不是杨辉三角吗。。。学C的时候都做过。。。不过学会了二维vector的用法。开始我还直接当二位数组用了。。但发现貌似非要插入一个数才会分配空间。。。
<pre name="code" class="cpp">class Solution { public: vector<vector<int> > generate(int numRows) { vector<vector<int> > answer(numRows); if ( numRows == 0 ) return answer; for(int i = 0; i < numRows ; i++ ){ answer[i].resize(i+1); answer[i][0] = 1; for(int j = 1; j < i ; j++){ answer[i][j] = answer[i-1][j-1] + answer[i-1][j]; } if( i ) answer[i][i] = 1; } return answer; } };
30.Plus One
这个用vector也简单。就不多说了。
class Solution { public: vector<int> plusOne(vector<int> &digits) { int len = digits.size(); bool carry = 1; for(int i = len - 1; carry && i >= 0 ; i--){ int temp = digits[i] + carry; carry = temp / 10; digits[i] = temp % 10; } if( carry ){ digits.insert(digits.begin(), 1); } return digits; } };
做题小结:
刷了30题。感觉刷题还是比较辛苦的。也有些问题需要改正。
1.还是思考少了,感觉有思路就直接写了,没有去深入分析问题。
2.容易想复杂。还是应当从最简单的思路开始该起。当然这就很容易变成凑出来的代码。。
3.多读读题。这一点很重要,很多时候虽说一些细处没有说明,但还是可以推断出来的。