两数之和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution {public : vector<int > twoSum (vector<int >& nums, int target) { map<int ,int > m; vector<int > ret; for (int i=0 ;i<nums.size ();i++) { auto it = m.find (target-nums[i]); if (it!=m.end ()) { ret.push_back (it->second); ret.push_back (i); break ; } else { m.insert ({nums[i],i}); } } return ret; } };
字母异位词分组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution {public : int m[26 ] = {2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 , 31 , 37 , 41 , 43 , 47 , 53 , 59 , 61 , 67 , 71 , 73 , 79 , 83 , 89 , 97 , 101 }; vector<vector<string>> groupAnagrams (vector<string>& strs) { vector<string> res[strs.size ()]; unordered_map<int , int > map; int cnt = 0 , mod = 1e9 +7 ; for (const auto &s : strs){ long v = 1 ; for (const auto &c : s){ v *= m[c-'a' ]; v %= mod; } if (!map.count (v)){ res[cnt].push_back (s); map[v] = cnt++; }else res[map[v]].push_back (s); } return {res, res + cnt}; } };
最长连续序列
这实际上是集合的应用了,算是哈希表的一种了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : int longestConsecutive (vector<int >& nums) { int res = 0 ; unordered_set<int > uset; for (auto num:nums) uset.insert (num); for (auto x:uset){ if (!uset.count (x - 1 )){ int y = x; while (uset.count (y+1 )){ y++; } res = max (res,y-x+1 ); } } return res; } };
移动0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution {public : void moveZeroes (vector<int >& nums) { int index = 0 ; int indexNum = 0 ; while ( indexNum < nums.size () ) { if ( nums[indexNum] != 0 ) { nums[index] = nums[indexNum] ; index ++ ; } indexNum ++ ; } while ( index < nums.size () ) { nums[index] = 0 ; index ++ ; } } };
盛最多水的容器
暴力做法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution {public : int maxArea (vector<int >& height) { if (height.size ()<1 ) { return 0 ; } int chang=0 ; int kuan=height[0 ]; int s=0 ; for (int i=0 ;i<height.size ();i++) { for (int j=i+1 ;j<height.size ();j++) { kuan=min (height[i],height[j]); chang++; if (s<chang*kuan) { s=chang*kuan; } } chang=0 ; } return s; } };
于是采取双指针加贪心的思路进行求解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution {public : int maxArea (vector<int >& height) { if (height.size ()<1 ) { return 0 ; } int left=0 ; int right=height.size ()-1 ; int s=0 ; while (left<right) { int tmp=min (height[left],height[right]); s=max ((right-left)*tmp,s); if (height[left]>height[right]) { right--; } else { left++; } } return s; } };
三数之和
暴力做法依然时间不够
三层循环不够的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : vector<vector<int >> threeSum (vector<int >& nums) { int len = nums.size (); set<vector<int >> s; sort (nums.begin (), nums.end ()); for (int i = 0 ; i < len - 2 ; i++) { for (int j = i + 1 ; j < len; j++) { for (int k = j + 1 ; k < len; k++) if (nums[i] + nums[j] + nums[k] == 0 ) { vector<int > v = { nums[i], nums[j], nums[k] }; s.insert (v); } } } vector<vector<int >> ans; for (const vector<int >& v : s) ans.push_back (v); return ans; } };
哈希表勉强可以过关
两层循环,一层用哈希表优化了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution {public : vector<vector<int >> threeSum (vector<int >& nums) { int len = nums.size (); vector<vector<int >> ans; sort (nums.begin (), nums.end ()); for (int i = 0 ; i < len; i++) { if (i != 0 && nums[i] == nums[i - 1 ]) continue ; unordered_map<int , int > m; for (int j = i + 1 ; j < len; j++) { if (m.count (-nums[i] - nums[j])) { ans.push_back ({ nums[i], nums[j], -nums[i] - nums[j] }); while (j + 1 < len && nums[j] == nums[j + 1 ]) j++; } else m.insert (pair <int , int >(nums[j], j)); } } return ans; } };
双指针法:
详细介绍一下,我们枚举一重循环,定义两个指针,一个从i+1开始,一个从len-1开始,如果==0,就记录,并且一个加加,一个减减,进行逼近,如果大于0,明显我们排完序后的话右指针要减减,如果小于0我们左指针要加加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution {public : vector<vector<int >> threeSum (vector<int >& nums) { int len = nums.size (); vector<vector<int >> ans; sort (nums.begin (), nums.end ()); for (int i = 0 ; i < len; i++) { int left = i + 1 , right = len - 1 ; if (i != 0 && nums[i] == nums[i - 1 ]) continue ; while (left < right) { if (nums[i] + nums[left] + nums[right] == 0 ) { ans.push_back ({ nums[i], nums[left], nums[right] }); left++; right--; while (left < right && nums[left] == nums[left - 1 ]) left++; while (left < right && nums[right] == nums[right + 1 ]) right--; } else if (nums[i] + nums[left] + nums[right] > 0 ) right--; else left++; } } return ans; } };
接雨水
本题实际上就是个单调栈,找到左边第一个比他大的,找到右边第一个比他大的,然后求解即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : int trap (vector<int >& height) { int ans = 0 ; stack<int > st; for (int i = 0 ; i < height.size (); i++) { while (!st.empty () && height[st.top ()] < height[i]) { int bottom = st.top (); st.pop (); if (st.empty ()) break ; int left = st.top (); int right = i; int h = min (height[left], height[right]) - height[bottom]; ans += h * (right - left - 1 ); } st.push (i); } return ans; } };
最长不含重复字符的子字符串
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度
好像还是哈希表的应用,但要加一个指针的思想罢了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : int lengthOfLongestSubstring (string s) { if (s.size () == 0 ) return 0 ; int res = 0 ; int begin = 0 , end = 0 ; set<char > count_ch; for (int i = 0 ; i < s.size (); i++) { while (count_ch.count (s[i]) != 0 ) { count_ch.erase (s[begin++]); } count_ch.insert (s[i]); end++; res = max (end - begin, res); } return res; } };
无重复字符的最长子串C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : vector<int > findAnagrams (string s, string p) { vector<int > res; if (s.size () < p.size ()) return res; int plen = p.size (); vector<int > vp (26 , 0 ) , vtmp (26 , 0 ) ; for (auto x : p) vp[x - 'a' ]++; for (int i = 0 ; i < s.size (); i++) { if (i >= plen) vtmp[s[i - plen] - 'a' ]--; vtmp[s[i] - 'a' ]++; if (vtmp == vp) res.push_back (i - plen + 1 ); } return res; } };
和为k的子数组C++
前缀和加暴力
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution {public : int subarraySum (vector<int >& nums, int k) { int n=nums.size (); vector<int > sum (n+1 ,0 ) ; for (int i=1 ;i<=n;i++) sum[i]=sum[i-1 ]+nums[i-1 ]; int count=0 ; for (int i=0 ;i<n;i++){ for (int j=i;j<n;j++){ if (sum[j+1 ]-sum[i]==k) count+=1 ; } } return count; } };
前缀和加哈希
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution {public : int subarraySum (vector<int >& nums, int k) { int n=nums.size (); int sum=0 ,res=0 ; unordered_map<int ,int > m; m[0 ]=1 ; for (int i=0 ;i<n;i++){ sum+=nums[i]; res+=m[sum-k]; m[sum]+=1 ; } return res; } };
滑动窗口最大值
实际上就是一个单调队列,维护一个单调递减的就行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : vector<int > maxSlidingWindow (vector<int >& nums, int k) { if (nums.size () == 0 ) return nums; vector<int > arr; deque<int > dq; for (int i = 0 ; i < nums.size (); i++) { while (!dq.empty () && nums[i] > nums[dq.back ()]) dq.pop_back (); if (!dq.empty () && dq.front () <= (i - k)) dq.pop_front (); dq.push_back (i); if (i >= k - 1 ) arr.push_back (nums[dq.front ()]); } return arr; } };
LeetCode 76. 最小覆盖子串
依然滑动窗口,满足就收缩
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : string minWindow (string s, string t) { unordered_map<char , int > hs, ht; for (auto c: t) ht[c] ++ ; string res; int cnt = 0 ; for (int i = 0 , j = 0 ; i < s.size (); i ++ ) { hs[s[i]] ++ ; if (hs[s[i]] <= ht[s[i]]) cnt ++ ; while (hs[s[j]] > ht[s[j]]) hs[s[j ++ ]] -- ; if (cnt == t.size ()) { if (res.empty () || i - j + 1 < res.size ()) res = s.substr (j, i - j + 1 ); } } return res; } };
最大子数组和
暴力解法自然是不能少
1 2 3 4 5 6 7 8 9 10 11 12 13 int MaxSumOfSub1 () { int res=-INF; for (int i=1 ;i<=cnt;i++){ for (int j=i+1 ;j<=cnt;j++){ int sum=0 ; for (int k=i;k<=j;k++) sum+=nums[k]; res=max (res,sum); } } return res; }
前缀和做法
1 2 3 4 5 6 7 8 9 10 11 12 13 public int maxSubArray (int [] nums) { int n = nums.length; int res = Integer.MIN_VALUE; for (int i = 0 ; i < n; i++) { int sum = 0 ; for (int j = i; j < n; j++) { sum += nums[j]; res = Math.max (res, sum); } } return res; }
上面两种应该会被卡,还是康康正解罢
动态规划
如果加上这个数字比这个数字还小,为什么要加呢?(核心)
除此之外就是记录答案
1 2 3 4 5 6 7 8 9 10 11 12 class Solution {public : int maxSubArray (vector<int >& nums) { int pre = 0 , maxAns = nums[0 ]; for (const auto &x: nums) { pre = max (pre + x, x); maxAns = max (maxAns, pre); } return maxAns; } }; 。
合并区间
官方题解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : vector<vector<int >> merge (vector<vector<int >>& intervals) { if (intervals.size () == 0 ) { return {}; } sort (intervals.begin (), intervals.end ()); vector<vector<int >> merged; for (int i = 0 ; i < intervals.size (); ++i) { int L = intervals[i][0 ], R = intervals[i][1 ]; if (!merged.size () || merged.back ()[1 ] < L) { merged.push_back ({L, R}); } else { merged.back ()[1 ] = max (merged.back ()[1 ], R); } } return merged; } };
轮转数组
第一种方法:暴力做法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : void rotate (vector<int >& nums, int k) { int n = nums.size (); int temp[n]; memset (temp,0 ,sizeof (int )*n); k %= n; for (int i = 0 ;i < k;i++){ temp[i] = nums[n-k+i]; } for (int i = k;i < n;i++){ temp[i] = nums[i-k]; } for (int i = 0 ;i < n;i++) nums[i] = temp[i]; } };
翻转数组的思路,加上位运算交换,肯定更快了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution {public : void rotate (vector<int >& nums, int k) { int n = nums.size (); k %= n; if (k == 0 ) return ; reverse (nums, 0 , n - 1 ); reverse (nums, 0 , k - 1 ); reverse (nums, k, n - 1 ); } void reverse (vector<int >&nums,int l,int r) { while (l < r) { nums[l] = nums[l] ^ nums[r]; nums[r] = nums[l] ^ nums[r]; nums[l] = nums[l] ^ nums[r]; l++; r--; } } };
除自身以外数组的乘积(C++)
前缀和的另一种模式前缀积
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution {public : vector<int > productExceptSelf (vector<int >& nums) { int length = nums.size (); vector<int > answer (length) ; answer[0 ] = 1 ; for (int i = 1 ; i < length; i++) { answer[i] = nums[i - 1 ] * answer[i - 1 ]; } int R = 1 ; for (int i = length - 1 ; i >= 0 ; i--) { answer[i] = answer[i] * R; R *= nums[i]; } return answer; } };
Leetcode 41:缺失的第一个正数
讲述一下解法,既然不能开空间,就要想办法(好像是废话)
由于我们如果有 1 2 3 4
,那么我们未尝不可以就是交换1到num[0],2到num[1],这样使得每一个数在自己的位置,然后遍历完就可以
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : int firstMissingPositive (vector<int >& nums) { int n=nums.size (); for (int i=0 ;i<n;++i) { while (nums[i] > 0 && nums[i] <= n && nums[i] != nums[nums[i]-1 ]) { swap (nums[i], nums[nums[i] - 1 ]); } } for (int i=0 ;i<n;++i) { if (nums[i]!=i+1 ) return i+1 ; } return n+1 ; } };
矩阵置0
有点暴力好像是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution {public : void setZeroes (vector<vector<int >>& matrix) { int i=matrix.size (); int j=matrix[0 ].size (); vector<int > hang (i) ; vector<int > lie (j) ; for (int m=0 ;m<i;m++){ for (int n=0 ;n<j;n++){ if (!matrix[m][n]){ hang[m]=lie[n]=true ; } } } for (int m=0 ;m<i;m++){ for (int n=0 ;n<j;n++){ if (hang[m] || lie[n]) matrix[m][n]=0 ; } } } };
这种就更暴力了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : void setZeroes (vector<vector<int >>& matrix) { if (matrix.size () == 0 || matrix[0 ].size () == 0 ) return ; vector<vector<int >> arr (matrix); const int i = matrix.size (), j = matrix[0 ].size (); for (int m = 0 ; m < i; m++) { for (int n = 0 ; n < j; n++) { if (arr[m][n] == 0 ) { for (int k = 0 ; k < j; k++) { matrix[m][k] = 0 ; } for (int t = 0 ; t < i; t++) { matrix[t][n] = 0 ; } } } } } };
螺旋矩阵
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution {public : vector<int > spiralOrder (vector<vector<int >>& matrix) { vector <int > ans; if (matrix.empty ()) return ans; int u = 0 ; int d = matrix.size () - 1 ; int l = 0 ; int r = matrix[0 ].size () - 1 ; while (true ) { for (int i = l; i <= r; ++i) ans.push_back (matrix[u][i]); if (++ u > d) break ; for (int i = u; i <= d; ++i) ans.push_back (matrix[i][r]); if (-- r < l) break ; for (int i = r; i >= l; --i) ans.push_back (matrix[d][i]); if (-- d < u) break ; for (int i = d; i >= u; --i) ans.push_back (matrix[i][l]); if (++ l > r) break ; } return ans; } };
旋转矩阵
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : void rotate (vector<vector<int >>& matrix) { int n = matrix.size (); for (int i = 0 ; i < n / 2 ; ++i) { for (int j = 0 ; j < (n + 1 ) / 2 ; ++j) { int temp = matrix[i][j]; matrix[i][j] = matrix[n - j - 1 ][i]; matrix[n - j - 1 ][i] = matrix[n - i - 1 ][n - j - 1 ]; matrix[n - i - 1 ][n - j - 1 ] = matrix[j][n - i - 1 ]; matrix[j][n - i - 1 ] = temp; } } } };
搜索二维矩阵 II
编写一个高效的算法来搜索 m x n 矩阵 matrix
中的一个目标值 target 。该矩阵具有以下特性:
每行的元素从左到右升序排列。 每列的元素从上到下升序排列。
初始值在左下角,大于target向上移动,小于target向右移动。
给我一种双指针的美感
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : bool searchMatrix (vector<vector<int >>& matrix, int target) { int xloc = matrix.size () - 1 , yloc = 0 ; while (yloc < matrix[0 ].size () && xloc >= 0 ){ if (matrix[xloc][yloc] == target){ return true ; }else if (matrix[xloc][yloc] > target){ xloc--; }else { yloc++; } } return false ; } };
搜索插入位置
二分方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution {public : int searchInsert (vector<int >& nums, int target) { int n = nums.size (); if (n == 0 ) return 0 ; if (target > nums[n-1 ]) return n; int left = 0 , right = n - 1 ; while (left < right) { int mid = left + (right - left) / 2 ; if (target > nums[mid]) { left = mid + 1 ; } else { right = mid; } } return left; } };
搜索二维矩阵
将二维下标转换为一维下标,相当于在一维数组 上做二分查找(推荐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution {public : bool searchMatrix (vector<vector<int >>& matrix, int target) { int m = matrix.size (), n = matrix[0 ].size (); int low = 0 , high = m * n - 1 ; while (low <= high) { int mid = (high - low) / 2 + low; int x = matrix[mid / n][mid % n]; if (x < target) { low = mid + 1 ; } else if (x > target) { high = mid - 1 ; } else { return true ; } } return false ; } };
在排序数组中查找元素的第一个和最后一个位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 class Solution {public : vector<int > searchRange (vector<int >& nums, int target) { vector<int > ans (2 , -1 ) ; ans[0 ] = getleftposition (nums, target); ans[1 ] = getrightposition (nums, target); return ans; } private : int getleftposition (vector<int >& nums, int target) { int left = 0 , right = nums.size () - 1 ; while (left <= right) { int mid = left + ((right - left) >> 1 ); if (target > nums[mid]) { left = mid + 1 ; } else if (target < nums[mid]) { right = mid - 1 ; } else { right = mid - 1 ; } } if (left == nums.size () || nums[left] != target) return -1 ; return left; } int getrightposition (vector<int >& nums, int target) { int left = 0 , right = nums.size () - 1 ; while (left <= right) { int mid = left + ((right - left) >> 1 ); if (target < nums[mid]) { right = mid - 1 ; } else if (target == nums[mid]) { left = mid + 1 ; } else { left = mid + 1 ; } } if (right == - 1 || nums[right] != target) { return -1 ; } return right; } };
搜索旋转排序数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution {public : int search (vector<int >& nums, int target) { int left = 0 , right = nums.size () - 1 ; while (left <= right) { int mid = (left + right) >> 1 ; if (nums[mid] == target) return mid; if (nums[left] <= nums[mid]) { (target >= nums[left] && target < nums[mid]) ? right = mid - 1 : left = mid + 1 ; } else { (target > nums[mid] && target <= nums[right]) ? left = mid + 1 : right = mid - 1 ; } } return -1 ; } };
寻找旋转排序数组的最小值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : int findMin (vector<int >& nums) { int low = 0 ; int high = nums.size () - 1 ; while (low < high) { int pivot = low + (high - low) / 2 ; if (nums[pivot] < nums[high]) { high = pivot; } else { low = pivot + 1 ; } } return nums[low]; } };
有效的括号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : bool isValid (string s) { if (s.size () % 2 != 0 ) return false ; stack<char > st; for (int i = 0 ; i < s.size (); i++) { if (s[i] == '(' ) st.push (')' ); else if (s[i] == '{' ) st.push ('}' ); else if (s[i] == '[' ) st.push (']' ); else if (st.empty () || st.top () != s[i]) return false ; else st.pop (); } return st.empty (); } };
最小栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class MinStack { stack<int > x_stack; stack<int > min_stack; public : MinStack () { min_stack.push (INT_MAX); } void push (int x) { x_stack.push (x); min_stack.push (min (min_stack.top (), x)); } void pop () { x_stack.pop (); min_stack.pop (); } int top () { return x_stack.top (); } int getMin () { return min_stack.top (); } };
字符串解码C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class Solution { public : string decodeString (string s) { stack<int > nums; int num = 0 ; stack<string> st; string tmp; int n = s.size (); for (int i = 0 ; i < n; i++) { if (s[i] >= '0' && s[i] <= '9' ) { num = 10 * num + s[i] - '0' ; } else if ((s[i] >= 'a' && s[i] <= 'z' ) || (s[i] >= 'A' && s[i] <= 'Z' )) { tmp.push_back (s[i]); } else if (s[i] == '[' ) { nums.push (num); num = 0 ; st.push (tmp); tmp.clear (); } else { int cnt = nums.top (); nums.pop (); for (int j = 0 ; j < cnt; j++) { st.top () += tmp; } tmp = st.top (); st.pop (); } } return tmp; } };
每日温度
一眼单调栈啊
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : vector<int > dailyTemperatures (vector<int >& temperatures) { int n = temperatures.size (); vector<int > ans (n) ; stack<int > s; for (int i = 0 ; i < n; ++i) { while (!s.empty () && temperatures[i] > temperatures[s.top ()]) { int previousIndex = s.top (); ans[previousIndex] = i - previousIndex; s.pop (); } s.push (i); } return ans; } };
柱状图中最大的矩形C++
还是单调栈嘛
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : int largestRectangleArea (vector<int >& heights) { stack<int > st; heights.insert (heights.begin (), 0 ); heights.push_back (0 ); st.push (0 ); int result = 0 ; for (int i = 1 ; i < heights.size (); i++) { while (heights[i] < heights[st.top ()]) { int mid = st.top (); st.pop (); int w = i - st.top () - 1 ; int h = heights[mid]; result = max (result, w * h); } st.push (i); } return result; } };
数组中第k个最大元素
大根堆啦
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public : int findKthLargest (vector<int >& nums, int k) { priority_queue<int , vector<int >, less<int >> maxHeap; for (int x : nums) maxHeap.push (x); for (int _ = 0 ; _ < k - 1 ; _ ++) maxHeap.pop (); return maxHeap.top (); } };
数组中前k个高频元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Solution {public : class mycomparison { public : bool operator () (const pair<int , int >& lhs, const pair<int , int >& rhs) { return lhs.second > rhs.second; } }; vector<int > topKFrequent (vector<int >& nums, int k) { unordered_map<int , int > map; for (int i = 0 ; i < nums.size (); i++) { map[nums[i]]++; } priority_queue<pair<int , int >, vector<pair<int , int >>, mycomparison> pri_que; for (unordered_map<int , int >::iterator it = map.begin (); it != map.end (); it++) { pri_que.push (*it); if (pri_que.size () > k) { pri_que.pop (); } } vector<int > result (k) ; for (int i = k - 1 ; i >= 0 ; i--) { result[i] = pri_que.top ().first; pri_que.pop (); } return result; } };
只出现一次的数字
异或性质
1 2 3 4 5 6 7 8 9 10 class Solution {public : int singleNumber (vector<int >& nums) { int ret = 0 ; for (auto e: nums) ret ^= e; return ret; } };
多数元素
第一种哈希
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : int majorityElement (vector<int >& nums) { unordered_map<int , int > counts; int majority = 0 , cnt = 0 ; for (int num: nums) { ++counts[num]; if (counts[num] > cnt) { majority = num; cnt = counts[num]; } } return majority; } };
第二种哈希
1 2 3 4 5 6 7 8 9 class Solution {public : int majorityElement (vector<int >& nums) { sort (nums.begin (), nums.end ()); return nums[nums.size () / 2 ]; } };
颜色分类
直接开始循环就行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : void sortColors (vector<int >& nums) { int n = nums.size (); int ptr = 0 ; for (int i = 0 ; i < n; ++i) { if (nums[i] == 0 ) { swap (nums[i], nums[ptr]); ++ptr; } } for (int i = ptr; i < n; ++i) { if (nums[i] == 1 ) { swap (nums[i], nums[ptr]); ++ptr; } } } };
下一个排列
不讲武德的方法
1 2 3 4 5 6 class Solution {public : void nextPermutation (vector<int >& nums) { next_permutation (nums.begin (), nums.end ()); } };
讲武德的方法
先找到逆序的
再找到第一个比他大的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : void nextPermutation (vector<int >& nums) { int i = nums.size () - 2 ; while (i >= 0 && nums[i] >= nums[i + 1 ]) { i--; } if (i >= 0 ) { int j = nums.size () - 1 ; while (j >= 0 && nums[i] >= nums[j]) { j--; } swap (nums[i], nums[j]); } reverse (nums.begin () + i + 1 , nums.end ()); } };
买卖股票的最佳时机
暴力
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution {public : int maxProfit (vector<int >& prices) { int n = (int )prices.size (), ans = 0 ; for (int i = 0 ; i < n; ++i){ for (int j = i + 1 ; j < n; ++j) { ans = max (ans, prices[j] - prices[i]); } } return ans; } };
贪心做法
记录历史最低,每次思考从最低开始买
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution {public : int maxProfit (vector<int >& prices) { int inf = 1e9 ; int minprice = inf, maxprofit = 0 ; for (int price: prices) { maxprofit = max (maxprofit, price - minprice); minprice = min (price, minprice); } return maxprofit; } };
跳跃游戏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution {public : bool canJump (vector<int >& nums) { int cover = 0 ; if (nums.size () == 1 ) return true ; for (int i = 0 ; i <= cover; i++) { cover = max (i + nums[i], cover); if (cover >= nums.size () - 1 ) return true ; } return false ; } };
跳跃游戏2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : int jump (vector<int >& nums) { int ans = 0 ; int begin = 0 ; int end = 0 ; while (end < nums.size ()-1 ){ int temp = 0 ; for (int i = begin; i <= end; i++){ temp = max (nums[i]+i, temp); } begin = end+1 ; end = temp; ans++; } return ans; } };
划分字母区间
找到最远边界的过程,然后放进去即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : vector<int > partitionLabels (string S) { int hash[27 ] = {0 }; for (int i = 0 ; i < S.size (); i++) { hash[S[i] - 'a' ] = i; } vector<int > result; int left = 0 ; int right = 0 ; for (int i = 0 ; i < S.size (); i++) { right = max (right, hash[S[i] - 'a' ]); if (i == right) { result.push_back (right - left + 1 ); left = i + 1 ; } } return result; } };
动态规划篇
爬楼梯
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution {public : int climbStairs (int n) { if (n <= 1 ) return n; vector<int > dp (n + 1 ) ; dp[1 ] = 1 ; dp[2 ] = 2 ; for (int i = 3 ; i <= n; i++) { dp[i] = dp[i - 1 ] + dp[i - 2 ]; } return dp[n]; } }; class Solution {public : int climbStairs (int n) { if (n <= 1 ) return n; int dp[3 ]; dp[1 ] = 1 ; dp[2 ] = 2 ; for (int i = 3 ; i <= n; i++) { int sum = dp[1 ] + dp[2 ]; dp[1 ] = dp[2 ]; dp[2 ] = sum; } return dp[2 ]; } };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # 杨辉三角 如此经典的动态规划 ```C++ class Solution { public: vector<vector<int>> generate(int numRows) { vector<vector<int>> ret(numRows); for (int i = 0; i < numRows; ++i) { ret[i].resize(i + 1); ret[i][0] = ret[i][i] = 1; //边界条件 for (int j = 1; j < i; ++j) { ret[i][j] = ret[i - 1][j] + ret[i - 1][j - 1]; //递推公式挺好 } } return ret; } };
打家劫舍
动态规划的的四个解题步骤是:
定义子问题
写出子问题的递推关系
确定 DP 数组的计算顺序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : int rob (vector<int >& nums) { if (nums.empty ()) { return 0 ; } int size = nums.size (); if (size == 1 ) { return nums[0 ]; } vector<int > dp = vector <int >(size, 0 ); dp[0 ] = nums[0 ]; dp[1 ] = max (nums[0 ], nums[1 ]); for (int i = 2 ; i < size; i++) { dp[i] = max (dp[i - 2 ] + nums[i], dp[i - 1 ]); } return dp[size - 1 ]; } };
完全平方数
刚看到是不是有一点小懵逼
实际上想想01背包
不就是装满吗
但又想到样例12=4+4+4
明显是完成背包
因此两重正序循环即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution {public : int numSquares (int n) { vector<int > f (n + 1 ) ; for (int i = 1 ; i <= n; i++) { int minn = INT_MAX; for (int j = 1 ; j * j <= i; j++) { minn = min (minn, f[i - j * j]); } f[i] = minn + 1 ; } return f[n]; } };
零钱兑换
依然是完全背包的变式
只是最后需要特判一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : int coinChange (vector<int >& coins, int amount) { int Max = amount + 1 ; vector<int > dp (amount + 1 , Max) ; dp[0 ] = 0 ; for (int i = 1 ; i <= amount; ++i) { for (int j = 0 ; j < (int )coins.size (); ++j) { if (coins[j] <= i) { dp[i] = min (dp[i], dp[i - coins[j]] + 1 ); } } } return dp[amount] > amount ? -1 : dp[amount]; } };
最长递增子序列
最长上升子序列
这个还是弱了点
实际上可以二分优化,队列优化
然而这样写就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : int lengthOfLIS (vector<int >& nums) { int n = (int )nums.size (); if (n == 0 ) { return 0 ; } vector<int > dp (n, 0 ) ; for (int i = 0 ; i < n; ++i) { dp[i] = 1 ; for (int j = 0 ; j < i; ++j) { if (nums[j] < nums[i]) { dp[i] = max (dp[i], dp[j] + 1 ); } } } return *max_element (dp.begin (), dp.end ()); } };
乘积最大子数组
这道题不是那么简单
因为最优解不一定是由前一个最优解转移过来,而可能由最差解转移过来,考虑负数
1 2 3 4 5 6 7 8 9 10 11 12 class Solution {public : int maxProduct (vector<int >& nums) { vector <int > maxF (nums), minF (nums); for (int i = 1 ; i < nums.size (); ++i) { maxF[i] = max (maxF[i - 1 ] * nums[i], max (nums[i], minF[i - 1 ] * nums[i])); minF[i] = min (minF[i - 1 ] * nums[i], min (nums[i], maxF[i - 1 ] * nums[i])); } return *max_element (maxF.begin (), maxF.end ()); } };
分割等和子集
一眼01背包,acmer太熟悉了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : bool canPartition (vector<int >& nums) { int n = nums.size (), m = 0 ; for (auto x : nums) m += x; if (m & 1 ) return false ; m /= 2 ; vector<int > f (m + 1 ) ; for (auto x : nums){ for (int j = m; j >=x ; j --) f[j] = max (f[j], f[j - x] + x); } return f[m] == m; } };
不同路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public: int uniquePaths(int m, int n) { vector<vector<int>> f(m, vector<int>(n)); for (int i = 0; i < m; ++i) { f[i][0] = 1; } for (int j = 0; j < n; ++j) { f[0][j] = 1; } for (int i = 1; i < m; ++i) { for (int j = 1; j < n; ++j) { f[i][j] = f[i - 1][j] + f[i][j - 1]; } } return f[m - 1][n - 1]; } };
最小路径和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution {public : int minPathSum (vector<vector<int >>& grid) { int m = grid.size (), n = grid[0 ].size (); vector<vector<int >> dp (m, vector <int >(n)); dp[0 ][0 ] = grid[0 ][0 ]; for (int i = 0 ; i < m; i++) { for (int j = 0 ; j < n; j++) { if (i == 0 && j != 0 ) { dp[i][j] = grid[i][j] + dp[i][j - 1 ]; } else if (i != 0 && j == 0 ) { dp[i][j] = grid[i][j] + dp[i - 1 ][j]; } else if (i != 0 && j != 0 ) { dp[i][j] = grid[i][j] + min (dp[i - 1 ][j], dp[i][j - 1 ]); } } } return dp[m - 1 ][n - 1 ]; } };
最长公共子序列
经典
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : int longestCommonSubsequence (string text1, string text2) { int m = text1.length (), n = text2.length (); vector<vector<int >> dp (m + 1 , vector <int >(n + 1 )); for (int i = 1 ; i <= m; i++) { char c1 = text1.at (i - 1 ); for (int j = 1 ; j <= n; j++) { char c2 = text2.at (j - 1 ); if (c1 == c2) { dp[i][j] = dp[i - 1 ][j - 1 ] + 1 ; } else { dp[i][j] = max (dp[i - 1 ][j], dp[i][j - 1 ]); } } } return dp[m][n]; } };
编辑距离
更经典了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution {public : int minDistance (string word1, string word2) { vector<vector<int >> dp (word1.size () + 1 , vector <int >(word2.size () + 1 , 0 )); for (int i = 0 ; i < dp.size (); i++) { dp[i][0 ] = i; } for (int j = 0 ; j < dp[0 ].size (); j++) { dp[0 ][j] = j; } for (int i = 1 ; i < dp.size (); i++) { for (int j = 1 ; j < dp[i].size (); j++) { dp[i][j] = min (dp[i - 1 ][j - 1 ], min (dp[i - 1 ][j], dp[i][j - 1 ])) + 1 ; if (word1[i - 1 ] == word2[j - 1 ]) { dp[i][j] = min (dp[i][j], dp[i - 1 ][j - 1 ]); } } } return dp.back ().back (); } };
全排列
库函数大大滴好
1 2 3 4 5 6 7 8 9 10 11 class Solution {public : vector<vector<int >> permute (vector<int >& nums) { vector<vector<int >> result; sort (nums.begin (), nums.end ()); do { result.emplace_back (nums); } while (next_permutation (nums.begin (), nums.end ())); return result; } };
正经回溯步骤
首先,如果我们需要输出搜素中我们的选择,这时候我们可能需要传入一个表示当前走到哪里的参数,第二个,如果我们需要计算出和最大或者是最小问题,我们可能传入一个参数,用来表示他们的和,第三个参数,也就是我们经常说的步数,我们可能要记录我们走了多少步,所以我们往往需要一个step的参数,来表示我们走了多少步,又或者,我们需要选完这个数后,我们要选比这个数大的数,我们此时也需要记录我们当前的数。
初步谈完了关于参数的传入,我们来谈谈关于终止条件,为什么要有终止条件呢,这是很重要的,不然我们的搜索不会停止,无法得到我们需要的结构,搜说条件往往是我们已经遇到死胡同了,接下来没有路可以走了,或者是说,我们已经收集好我们要的东西了,我们不需要再进行搜素了,这些都是需要我们进行终止的,而往往伴随着终止的是一个更新,我们需要更新我们最大值啊1,或是更新我们的最小值啊,又或者进行输出啊,当然是满足我们终止的条件下进行的啦,然后别忘了,我们在终止完后要return哦,不然你搜完第一次就卡在那里了,哪里能达到我们搜索的目的呢,你说是吧。
再来谈谈我们搜说的核心部分,首先我们要试探一下,试探这个点的下一个点有没有被搜索过,也就是说我们在前面要有一个东西,也就是一个判断的数组,来表示我们的下一个东西有没有被搜索过,如果有,可以continue,如果没有,那就标记下一个点为被搜索的状态,并进行递归调用我们的dfs,传入我们的下一个搜索点,注意参数往往要改变,也许是步数的加一,也许是和的加,也可以是下一个点的值,都要结合题目要求具体分析,然后也是比较重要的一点,我们需要进行清理现场,就是说我们要把我们前面标记的拿掉,让他可选择,这样我们就完成了整个dfs的搜索过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : vector<vector<int >> permute (vector<int >& nums) { dfs (nums, 0 ); return res; } private : vector<vector<int >> res; void dfs (vector<int > nums, int x) { if (x == nums.size () - 1 ) { res.push_back (nums); return ; } for (int i = x; i < nums.size (); i++) { swap (nums[i], nums[x]); dfs (nums, x + 1 ); swap (nums[i], nums[x]); } } };
子集
围绕选与不选进行深度优先搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : vector<int > t; vector<vector<int >> ans; void dfs (int cur, vector<int >& nums) { if (cur == nums.size ()) { ans.push_back (t); return ; } t.push_back (nums[cur]); dfs (cur + 1 , nums); t.pop_back (); dfs (cur + 1 , nums); } vector<vector<int >> subsets (vector<int >& nums) { dfs (0 , nums); return ans; } };
电话号码的字母组合
首先使用哈希表存储每个数字对应的所有可能的字母,然后进行回溯操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution {public : string path = "" ; vector<string> ans; unordered_map<char ,string> dic{ {'2' ,"abc" }, {'3' ,"def" }, {'4' ,"ghi" }, {'5' ,"jkl" }, {'6' ,"mno" }, {'7' ,"pqrs" }, {'8' ,"tuv" }, {'9' ,"wxyz" } }; void dfs (int index,string digits) { if (index == digits.length ()){ ans.push_back (path); } string str = dic[digits[index]]; for (int i = 0 ;i<str.length ();i++){ path += str[i]; dfs (index+1 ,digits); path.pop_back (); } } vector<string> letterCombinations (string digits) { if (digits.empty ()){ return ans; } dfs (0 ,digits); return ans; } };
组合总和
隐含了一个剪枝
剪去了一个和大于目标值的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution {public : void dfs (vector<int >& candidates, int target, vector<vector<int >>& ans, vector<int >& combine, int idx) { if (idx == candidates.size ()) { return ; } if (target == 0 ) { ans.emplace_back (combine); return ; } dfs (candidates, target, ans, combine, idx + 1 ); if (target - candidates[idx] >= 0 ) { combine.emplace_back (candidates[idx]); dfs (candidates, target - candidates[idx], ans, combine, idx); combine.pop_back (); } } vector<vector<int >> combinationSum (vector<int >& candidates, int target) { vector<vector<int >> ans; vector<int > combine; dfs (candidates, target, ans, combine, 0 ); return ans; } };
括号生成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : vector<string> generateParenthesis (int n) { dfs ("" , n, n); return res; } private : vector<string> res; void dfs (const string& str, int left, int right) { if (left < 0 || left > right) return ; if (left == 0 && right == 0 ) { res.push_back (str); return ; } dfs (str + '(' , left - 1 , right); dfs (str + ')' , left, right - 1 ); } };
链表相关题目
下面有关于列表的概要讲述。
有两种常用的列表实现,分别为数组列表和链表。如果我们想在列表中存储值,它们是如何实现的呢?
数组列表底层是使用数组存储值,我们可以通过索引在
O(1)的时间访问列表任何位置的值,这是由基于内存寻址的方式。
链表存储的是称为节点的对象,每个节点保存一个值和指向下一个节点的指针。访问某个特定索引的节点需要
O(n)的时间,因为要通过指针获取到下一个位置的节点。
总结一下数组的优缺点:
优点:可以根据偏移实现快速的随机读写。
缺点:扩容,增删元素极慢。
面试问题总结
无法高效获取长度,无法根据偏移快速访问元素,是链表的两个劣势。然而面试的时候经常碰见诸如获取倒数第k个元素,获取中间位置的元素,判断链表是否存在环,判断环的长度等和长度与位置有关的问题。这些问题都可以通过灵活运用双指针来解决。
1 2 3 4 5 6 7 8 9 10 11 12 class Solution {public : ListNode *getIntersectionNode (ListNode *headA, ListNode *headB) { ListNode *A = headA, *B = headB; while (A != B) { A = A != nullptr ? A->next : headB; B = B != nullptr ? B->next : headA; } return A; } };
反转链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : ListNode* reverseList (ListNode* head) { ListNode* prev = nullptr ; ListNode* curr = head; while (curr) { ListNode* next = curr->next; curr->next = prev; prev = curr; curr = next; } return prev; } };
回文链表
题解真的是天才,竟然把链表弄成数组,我这个nt还想着反转一次链表逐个比对,乐。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : bool isPalindrome (ListNode* head) { vector<int > vals; while (head != nullptr ) { vals.emplace_back (head->val); head = head->next; } for (int i = 0 , j = (int )vals.size () - 1 ; i < j; ++i, --j) { if (vals[i] != vals[j]) { return false ; } } return true ; } };
环形链表
应该不难想,快慢指针各自移动,有环你两肯定遇上,没有就不好说了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : bool hasCycle (ListNode* head) { if (head == nullptr || head->next == nullptr ) { return false ; } ListNode* slow = head; ListNode* fast = head->next; while (slow != fast) { if (fast == nullptr || fast->next == nullptr ) { return false ; } slow = slow->next; fast = fast->next->next; } return true ; } };
环形链表II
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class Solution { public : ListNode *detectCycle (ListNode *head) { ListNode* fast = head; ListNode* slow = head; while (true ) { if (fast == nullptr || fast->next == nullptr ) return nullptr ; fast = fast->next->next; slow = slow->next; if (fast == slow) break ; } fast = head; while (slow != fast) { slow = slow->next; fast = fast->next; } return fast; } };
合并两个有序链表·
双指针写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution {public : ListNode* mergeTwoLists (ListNode* list1, ListNode* list2) { ListNode* dum = new ListNode (0 ); ListNode* cur = dum; while (list1 != nullptr && list2 != nullptr ) { if (list1->val < list2->val) { cur->next = list1; list1 = list1->next; } else { cur->next = list2; list2 = list2->next; } cur = cur->next; } cur->next = list1 != nullptr ? list1 : list2; return dum->next; } };
递归写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution {public : ListNode* mergeTwoLists (ListNode* l1, ListNode* l2) { if (l1 == NULL ) { return l2; } if (l2 == NULL ) { return l1; } if (l1->val <= l2->val) { l1->next = mergeTwoLists (l1->next, l2); return l1; } l2->next = mergeTwoLists (l1, l2->next); return l2; } };
两数相加
对齐的方法题解大大的好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 class Solution {public : ListNode* addTwoNumbers (ListNode* l1, ListNode* l2) { int len1=1 ; int len2=1 ; ListNode* p=l1; ListNode* q=l2; while (p->next!=NULL ) { len1++; p=p->next; } while (q->next!=NULL ) { len2++; q=q->next; } if (len1>len2) { for (int i=1 ;i<=len1-len2;i++) { q->next=new ListNode (0 ); q=q->next; } } else { for (int i=1 ;i<=len2-len1;i++) { p->next=new ListNode (0 ); p=p->next; } } p=l1; q=l2; bool count=false ; ListNode* l3=new ListNode (-1 ); ListNode* w=l3; int i=0 ; while (p!=NULL &&q!=NULL ) { i=count+p->val+q->val; w->next=new ListNode (i%10 ); count=i>=10 ?true :false ; w=w->next; p=p->next; q=q->next; } if (count) { w->next=new ListNode (1 ); w=w->next; } return l3->next; } };
删除链表的倒数第N个结点
暴力?好像是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution {public : int getLength (ListNode* head) { int length = 0 ; while (head) { ++length; head = head->next; } return length; } ListNode* removeNthFromEnd (ListNode* head, int n) { ListNode* dummy = new ListNode (0 , head); int length = getLength (head); ListNode* cur = dummy; for (int i = 1 ; i < length - n + 1 ; ++i) { cur = cur->next; } cur->next = cur->next->next; ListNode* ans = dummy->next; delete dummy; return ans; } };
两两交换链表中的节点
还是迭代法比较好理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution {public : ListNode* swapPairs (ListNode* head) { ListNode* dummyHead = new ListNode (0 ); dummyHead->next = head; ListNode* temp = dummyHead; while (temp->next != nullptr && temp->next->next != nullptr ) { ListNode* node1 = temp->next; ListNode* node2 = temp->next->next; temp->next = node2; node1->next = node2->next; node2->next = node1; temp = node1; } ListNode* ans = dummyHead->next; delete dummyHead; return ans; } };
二叉树世界
一般是递归写法,毕竟树很难离得开递归。
二叉树的中序遍历
按照访问左子树——根节点——右子树的方式遍历这棵树
前序遍历:打印 - 左 - 右
中序遍历:左 - 打印 - 右
后序遍历:左 - 右 - 打印
终止条件:当前节点为空时
函数内:递归的调用左节点,打印当前节点,再递归调用右节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : void inorder (TreeNode* root, vector<int >& res) { if (!root) { return ; } inorder (root->left, res); res.push_back (root->val); inorder (root->right, res); } vector<int > inorderTraversal (TreeNode* root) { vector<int > res; inorder (root, res); return res; } };
二叉树的最大深度
知识前置
3 个树的重要概念:层次、深度、高度。
二叉树的层次是从根节点算起,根节点是第一层,依次往下类推。
二叉树的高度是从叶子节点算起,叶子节点高度是
1,依次往上类推。可以看成是高楼,从下往上看,也就是自底向上看。
通过图,也可以看出,二叉树的深度和层次是完全对应的,最大深度为最大层次数。二叉树的深度和高度正好相反。
(1) 自顶向下
自顶向下,就是从根节点递归到叶子节点,计算这一条路径上的深度,并更新维护最大深度。
这个是正儿八经的求深度。每次先维护根节点的深度,再递归左子树、右子树。
(2) 自底向上
自底向上,从叶子节点开始,一层一层的向上,最终汇集在根节点。
总结:dfs
1 2 3 4 5 6 7 8 class Solution {public : int maxDepth (TreeNode* root) { if (root == nullptr ) return 0 ; return max (maxDepth (root->left), maxDepth (root->right)) + 1 ; } };
翻转二叉树
好精美的图,偷了
226_2.gif
交换一下左右节点,然后再递归的交换左节点,右节点
显然,我们从根节点开始,递归地对树进行遍历,并从叶子节点先开始翻转。如果当前遍历到的节点
root的左右两棵子树都已经翻转,那么我们只需要交换两棵子树的位置,即可完成以
root为根节点的整棵子树的翻转。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution {public : TreeNode* invertTree (TreeNode* root) { if (root == nullptr ) { return nullptr ; } TreeNode* left = invertTree (root->left); TreeNode* right = invertTree (root->right); root->left = right; root->right = left; return root; } };
对称二叉树
根据题目的描述,镜像对称,就是左右两边相等,也就是左子树和右子树是相当的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : bool isSymmetric (TreeNode* root) { return root == nullptr || recur (root->left, root->right); } private : bool recur (TreeNode* L, TreeNode* R) { if (L == nullptr && R == nullptr ) return true ; if (L == nullptr || R == nullptr || L->val != R->val) return false ; return recur (L->left, R->right) && recur (L->right, R->left); } };
二叉树的直径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { int ans; int depth (TreeNode* root) { if (root == NULL ) return 0 ; int L = depth (root->left); int R = depth (root->right); ans = max (ans, L + R + 1 ); return max (L, R) + 1 ; } public : int diameterOfBinaryTree (TreeNode* root) { ans = 1 ; depth (root); return ans - 1 ; } };
岛屿数量
为了求出岛屿的数量,我们可以扫描整个二维网格。如果一个位置为
111,则以其为起始节点开始进行深度优先搜索。在深度优先搜索的过程中,每个搜索到的
111 都会被重新标记为 000。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution {private : void dfs (vector<vector<char >>& grid, int r, int c) { int nr = grid.size (); int nc = grid[0 ].size (); grid[r][c] = '0' ; if (r - 1 >= 0 && grid[r-1 ][c] == '1' ) dfs (grid, r - 1 , c); if (r + 1 < nr && grid[r+1 ][c] == '1' ) dfs (grid, r + 1 , c); if (c - 1 >= 0 && grid[r][c-1 ] == '1' ) dfs (grid, r, c - 1 ); if (c + 1 < nc && grid[r][c+1 ] == '1' ) dfs (grid, r, c + 1 ); } public : int numIslands (vector<vector<char >>& grid) { int nr = grid.size (); if (!nr) return 0 ; int nc = grid[0 ].size (); int num_islands = 0 ; for (int r = 0 ; r < nr; ++r) { for (int c = 0 ; c < nc; ++c) { if (grid[r][c] == '1' ) { ++num_islands; dfs (grid, r, c); } } } return num_islands; } };
腐烂的橘子
我认为还是一眼Bfs的
把初始时的腐烂橘子加入队列,并计算新鲜橘子的个数。
BFS
将预处理阶段找到的腐烂橘子向四个方向开始腐烂,需要记录队列的初始长度,当长度减少到0时算一轮感染。再进行次轮感染,直到队列为空。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution {public : int orangesRotting (vector<vector<int >>& grid) { int min = 0 , fresh = 0 ; queue<pair<int , int >> q; for (int i = 0 ; i < grid.size (); i++) { for (int j = 0 ; j < grid[0 ].size (); j++) if (grid[i][j] == 1 ) fresh++; else if (grid[i][j] == 2 ) q.push ({i, j}); } vector<pair<int , int >> dirs = { {-1 , 0 }, {1 , 0 }, {0 , -1 }, {0 , 1 } }; while (!q.empty ()) { int n = q.size (); bool rotten = false ; for (int i = 0 ; i < n; i++) { auto x = q.front (); q.pop (); for (auto cur: dirs) { int i = x.first + cur.first; int j = x.second + cur.second; if (i >= 0 && i < grid.size () && j >= 0 && j < grid[0 ].size () && grid[i][j] == 1 ) { grid[i][j] = 2 ; q.push ({i, j}); fresh--; rotten = true ; } } } if (rotten) min++; } return fresh ? -1 : min; } };
课程表
对于一个有向图中的一个顶点,它的入度是指指向该顶点的边的数量,而出度是指从该顶点出发的边的数量。如果一个顶点的入度为0,则称其为源点;如果一个顶点的出度为0,则称其为汇点。
方法一 拓扑排序
课程之间的关系可以用有向图来表示,能完成课程的依据就是课程组成的有向图中没有环 ,可以通过拓扑排序来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class Solution {public : bool canFinish (int numCourses, vector<vector<int >>& prerequisites) { vector<int > ingree (numCourses, 0 ) ; unordered_map<int , vector<int >> preCourse; for (int i = 0 ; i < prerequisites.size (); ++i){ preCourse[prerequisites[i][0 ]].push_back (prerequisites[i][1 ]); ingree[prerequisites[i][1 ]]++; } queue<int > zero_ingree; int count = 0 ; for (int i = 0 ; i < numCourses; ++i) { if (!ingree[i]) { zero_ingree.push (i); ++count; } } while (!zero_ingree.empty ()) { int cur_course = zero_ingree.front (); zero_ingree.pop (); for (int i = 0 ; i < preCourse[cur_course].size (); ++i) { --ingree[preCourse[cur_course][i]]; if (!ingree[preCourse[cur_course][i]]) { zero_ingree.push (preCourse[cur_course][i]); ++count; } } } if (count == numCourses) return true ; return false ; } };
方法二 深度优先搜索(DFS)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Solution {private : vector<vector<int >> edges; vector<int > visited; bool valid = true ; public : void dfs (int u) { visited[u] = 1 ; for (int v: edges[u]) { if (visited[v] == 0 ) { dfs (v); if (!valid) { return ; } } else if (visited[v] == 1 ) { valid = false ; return ; } } visited[u] = 2 ; } bool canFinish (int numCourses, vector<vector<int >>& prerequisites) { edges.resize (numCourses); visited.resize (numCourses); for (const auto & info: prerequisites) { edges[info[1 ]].push_back (info[0 ]); } for (int i = 0 ; i < numCourses && valid; ++i) { if (!visited[i]) { dfs (i); } } return valid; } };