估算算法的时间复杂度的时候一般低于10^7 次操作每秒为佳
sort 的时间复杂度与编译器具体实现相关,简单考虑成快速排序nlogn
哈希 1.两数之和 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。 你可以按任意顺序返回答案。 进阶:你可以想出一个时间复杂度小于 O(n2) 的算法吗
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : vector<int > twoSum (vector<int >& nums, int target) { unordered_map<int ,int > map; for (size_t i = 0 ; i < nums.size (); i++){ if (map.contains (target-nums[i])){ vector<int > ret; ret.push_back (map[target-nums[i]]); ret.push_back (i); return ret; } map[nums[i]] = i; } return vector <int >(); } };
2.字母异位词分组 给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
示例 1: 输入: strs = [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”] 输出: [[“bat”],[“nat”,”tan”],[“ate”,”eat”,”tea”]] 解释: 在 strs 中没有字符串可以通过重新排列来形成 “bat”。 字符串 “nat” 和 “tan” 是字母异位词,因为它们可以重新排列以形成彼此。 字符串 “ate” ,”eat” 和 “tea” 是字母异位词,因为它们可以重新排列以形成彼此。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : vector<vector<string>> groupAnagrams (vector<string>& strs) { unordered_map<string,vector<string>> group_strs; for (auto & s : strs){ string sorted_s = s; ranges::sort (sorted_s); group_strs[sorted_s].push_back (s); } vector<vector<string>> ans; ans.reserve (group_strs.size ()); for (auto & [_,value] : group_strs){ ans.push_back (value); } return ans; } };
3.最长连续序列 给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。 请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
输入:nums = [100,4,200,1,3,2] 输出:4 解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4
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 class Solution {public : int longestConsecutive (vector<int >& nums) { if (nums.size () < 2 ) return nums.size (); sort (nums.begin (), nums.end ()); int ans = 1 ; int start = 0 ; int cur = 1 ; for (int i = 1 ; i < nums.size (); i++) { if (nums[i] == nums[i - 1 ]) continue ; if (nums[i] - nums[i - 1 ] == 1 ) { cur++; ans = max (cur, ans); } else { start = i; cur = 1 ; } } return ans; } int longestConsecutive (vector<int >& nums) { unordered_set<int > num_set; for (const int & num : nums){ num_set.insert (num); } int longestStreak = 0 ; for (auto & num : num_set){ if (!num_set.count (num - 1 )){ int currentNum = num; int currentStreak = 1 ; while ( num_set.count (currentNum + 1 )){ currentNum += 1 ; currentStreak += 1 ; } longestStreak = max (longestStreak,currentStreak); } } return longestStreak; } };
双指针 4.移动零 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1: 输入: nums = [0,1,0,3,12] 输出: [1,3,12,0,0]
进阶:你能尽量减少完成的操作次数吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution {public : void moveZeroes (vector<int >& nums) { int n = nums.size (),left = 0 , right = 0 ; while (right < n){ if (0 != nums[right]){ swap (nums[left],nums[right]); left++; } right++; } } };
5.盛水最多的容器 给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明:你不能倾斜容器。
示例 1: 输入:[1,8,6,2,5,4,8,3,7] 输出:49 解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
题解:https://leetcode.cn/problems/container-with-most-water/solutions/207215/sheng-zui-duo-shui-de-rong-qi-by-leetcode-solution/?envType=study-plan-v2&envId=top-100-liked 关键: 接水量=两个指针指向的数字中较小值∗指针之间的距离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : int maxArea (vector<int >& height) { int low = 0 , n = height.size (),high = n-1 ; int max_container = 0 ; while (low < high){ int container = ( high - low ) * min (height[low],height[high]); max_container = max (max_container,container); if (height[low] < height[high]){ low++; }else { high--; } } return max_container; } };
6.三数之和 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三元组。
示例 1: 输入:nums = [-1,0,1,2,-1,-4] 输出:[[-1,-1,2],[-1,0,1]] 解释: nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。 nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。 nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。 不同的三元组是 [-1,0,1] 和 [-1,-1,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 25 26 27 28 29 30 31 32 33 34 35 36 class Solution {public : vector<vector<int >> threeSum (vector<int >& nums) { vector<vector<int >> ans; sort (nums.begin (),nums.end ()); for (int i = 0 ; i < nums.size () - 2 ; i++ ){ if (i > 0 && nums[i] == nums[i-1 ]){ continue ; } if (nums[i] > 0 ){ break ; } int l = i + 1 , r = nums.size ()-1 ; while (l < r ){ int sum = nums[l]+nums[r]+nums[i]; if (sum < 0 ){ l++; }else if (sum > 0 ){ r--; }else { ans.push_back ({nums[i],nums[l],nums[r]}); while (l < r && nums[l] == nums[l+1 ]){ l++; } while (l < r && nums[r] == nums[r-1 ]){ r--; } l++;r--; } } } return ans; } };
7.接雨水 定 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 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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 class Solution {public : int trap (vector<int >& height) { int total_sum = 0 ; vector<int > dp_l_max; vector<int > dp_r_max; dp_l_max.reserve (height.size ()); dp_r_max.reserve (height.size ()); dp_l_max[0 ] = 0 ; int n = height.size (); dp_r_max[n-1 ] = 0 ; for (int i = 0 ; i < height.size () - 1 ; i++) { dp_l_max[i+1 ] = max (dp_l_max[i],height[i]); dp_r_max[n-i-2 ] = max (dp_r_max[n-1 -i],height[n-i-1 ]); } for (int i = 0 ; i < height.size (); i++){ int max_l = dp_l_max[i], max_r = dp_r_max[i]; if (max_r > height[i] && max_l > height[i]){ total_sum += min (max_r,max_l) - height[i]; } } return total_sum; } int trap (vector<int >& height) { int ans = 0 ; stack<int > stk; int n = height.size (); for (int i = 0 ; i < n; ++i) { while (!stk.empty () && height[i] > height[stk.top ()]) { int top = stk.top (); stk.pop (); if (stk.empty ()) { break ; } int left = stk.top (); int currWidth = i - left - 1 ; int currHeight = min (height[left], height[i]) - height[top]; ans += currWidth * currHeight; } stk.push (i); } return ans; } int trap (vector<int >& height) { int n = height.size (); if (n<=2 ) return 0 ; int l=0 ,r=n-1 ; int leftmax=height[l], rightmax=height[r]; int res=0 ; while (l<r) { if (leftmax < rightmax) { res += leftmax-height[l]; l++; leftmax = max (height[l], leftmax); } else { res += rightmax-height[r]; r--; rightmax = max (height[r], rightmax); } } return res; } };
滑动窗口
1.区分是固定长度的窗口还是非固定长度的窗口,非固定长度窗口参照7.的模板 2.字符串比较的不考虑顺序时可以采用字典集的方式降低时间复杂度 3.如果字符串中包含负数,如第9题,可能导致窗口伸缩状态不确定,这种情况滑动窗口不适用
8. 无重复字符的最长子串 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1: 输入: s = “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。注意 “bca” 和 “cab” 也是正确答案。
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) { unordered_set<char > char_sets; int maxlength = 0 ; int curlength = 0 ; for (int l = 0 , r = 0 ; r < s.length ();r++){ while (l < r && char_sets.contains (s[r])){ char_sets.erase (s[l]); l++; curlength -- ; } char_sets.insert (s[r]); curlength++; maxlength = max (maxlength,curlength); } return maxlength; } };
9.找到字符串中所有字母异位词 给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
示例 1: 输入: s = “cbaebabacd”, p = “abc” 输出: [0,6] 解释: 起始索引等于 0 的子串是 “cba”, 它是 “abc” 的异位词。 起始索引等于 6 的子串是 “bac”, 它是 “abc” 的异位词。
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 : vector<int > findAnagrams (string s, string p) { int sLen = s.size (), pLen = p.size (); if (sLen < pLen) { return vector <int >(); } vector<int > ans; vector<int > sCount (26 ) ; vector<int > pCount (26 ) ; for (int i = 0 ; i < pLen; ++i) { ++sCount[s[i] - 'a' ]; ++pCount[p[i] - 'a' ]; } if (sCount == pCount) { ans.emplace_back (0 ); } for (int i = 0 ; i < sLen - pLen; ++i) { --sCount[s[i] - 'a' ]; ++sCount[s[i + pLen] - 'a' ]; if (sCount == pCount) { ans.emplace_back (i + 1 ); } } return ans; } };
子串 10.和为 k 的子数组 给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。 子数组是数组中元素的连续非空序列。
示例 2: 输入:nums = [1,2,3], k = 3 输出:2
前缀合+哈希表
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 : int subarraySum (vector<int >& nums, int k) { int ans = 0 ; unordered_map<int ,int > prefix_sum_count_map; prefix_sum_count_map[0 ] = 1 ; int cur_prefix_sum = 0 ; for (int i = 0 ; i < nums.size ();i++){ cur_prefix_sum += nums[i]; if (prefix_sum_count_map.contains (cur_prefix_sum - k)){ ans += prefix_sum_count_map[cur_prefix_sum-k]; } if (prefix_sum_count_map.contains (cur_prefix_sum)){ prefix_sum_count_map[cur_prefix_sum]++; }else { prefix_sum_count_map[cur_prefix_sum] = 1 ; } } return ans; } };
11.滑动窗口中的最大值 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。
示例 1: 输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 输出:[3,3,5,5,6,7] 解释: 滑动窗口的位置 最大值
[1 3 -1] -3 5 3 6 7 3 1 [3 -1 -3] 5 3 6 7 3 1 3 [-1 -3 5] 3 6 7 5 1 3 -1 [-3 5 3] 6 7 5 1 3 -1 -3 [5 3 6] 7 6 1 3 -1 -3 5 [3 6 7] 7
示例 2: 输入:nums = [1], k = 1 输出:[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 class Solution {public : vector<int > maxSlidingWindow (vector<int >& nums, int k) { priority_queue<pair<int ,int >> pq; for (int i = 0 ; i < k ; i ++ ){ pq.emplace (nums[i],i); } vector<int > ans; ans.push_back (pq.top ().first); for (int i = k ; i < nums.size () ; ++i){ pq.emplace (nums[i],i); while (pq.top ().second <= i-k) { pq.pop (); } ans.push_back (pq.top ().first); } return ans; } };
12.最小覆盖子串 给定两个字符串 s 和 t,长度分别是 m 和 n,返回 s 中的 最短窗口 子串,使得该子串包含 t 中的每一个字符(包括重复字符)。如果没有这样的子串,返回空字符串 “”。
测试用例保证答案唯一。
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 string minWindow (string s, string t) { unordered_map<char ,int > need_map,cur_wind_map; for (auto & c : t){ need_map[c]++; } int r= 0 , l = 0 ; int valid = 0 ; int minLen = INT_MAX, start = 0 ; for (;r < s.size ();r++){ if (need_map.contains (s[r])){ cur_wind_map[s[r]]++; if (cur_wind_map[s[r]] == need_map[s[r]]){ valid++; } } while (valid == need_map.size ()){ if (r - l < minLen){ minLen = r + 1 - l; start = l; } if (need_map.contains (s[l])){ if (cur_wind_map[s[l]] == need_map[s[l]]){ valid--; } cur_wind_map[s[l]]--; } l++; } } if (minLen == INT_MAX){ return "" ; } return s.substr (start,minLen); }
普通数组 13.最大子数组和 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 子数组是数组中的一个连续部分。
示例 1: 输入:nums = [-2,1,-3,4,-1,2,1,-5,4] 输出:6 解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution {public : int maxSubArray (vector<int >& nums) { vector<int > dp; dp.reserve (nums.size ()); dp[0 ] = nums[0 ]; int max_sum = dp[0 ]; for (int i = 1 ; i < nums.size (); i++){ dp[i] = max (dp[i-1 ]+nums[i],nums[i]); max_sum = max (max_sum,dp[i]); } return max_sum; } };
14.合并区间 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
示例 1: 输入:intervals = [[1,3],[2,6],[8,10],[15,18]] 输出:[[1,6],[8,10],[15,18]] 解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
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<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.empty ()){ merged.push_back ({l,r}); }else { if (merged.back ()[1 ] < l){ merged.push_back ({l,r}); }else { merged.back ()[1 ] = max (merged.back ()[1 ],r); } } } return merged; } };
15.轮转数组 给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。 示例 1: 输入: nums = [1,2,3,4,5,6,7], k = 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转 3 步: [5,6,7,1,2,3,4]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : void reverse (vector<int >& nums ,int start,int end) { while (start < end){ swap (nums[start],nums[end]); start++; end--; } } void rotate (vector<int >& nums, int k) { k %= nums.size (); reverse (nums,0 ,nums.size ()-1 ); reverse (nums,0 ,k-1 ); reverse (nums,k,nums.size ()-1 ); } };
16.除了自身以外数组的乘积 给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除了 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请不要使用除法,且在 O(n) 时间复杂度内完成此题。 进阶:你可以在 O(1) 的额外空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)
示例 1: 输入: nums = [1,2,3,4] 输出: [24,12,8,6]
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 class Solution {public : vector<int > productExceptSelf (vector<int >& nums) { int n = nums.size (); vector<int > L (n,0 ) ,R (n, 0 ) ; L[0 ] = 1 ; R[n - 1 ] = 1 ; for (int i = 1 ; i < n; i++){ L[i] = nums[i-1 ]*L[i-1 ]; } for (int i = n - 2 ; i >= 0 ; i--){ R[i] = nums[i+1 ]* R[i+1 ]; } vector<int > ans (n) ; for (int i = 0 ; i < n;i++){ ans[i] = L[i]*R[i]; } return ans; } vector<int > productExceptSelf (vector<int >& nums) { int n = nums.size (); vector<int > ans (n,0 ) ; ans[0 ] = 1 ; for (int i = 1 ; i < n; i++){ ans[i] = ans[i-1 ]*nums[i-1 ]; } int R = 1 ; for (int i = n - 2 ; i >= 0 ; i--){ R = R * nums[i+1 ]; ans[i] = ans[i] * R; } return ans; } };
17.缺失的第一个正数 给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(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 26 27 28 29 30 31 class Solution {public : int firstMissingPositive (vector<int >& nums) { int n = nums.size (); for (int i = 0 ; i < n ; i++){ if (nums[i] <= 0 || nums[i] > n){ nums[i] = n+1 ; } } for (int i = 0 ; i < n ; i++){ int num = abs (nums[i]); if ( num < n+1 ){ if (nums[num - 1 ] > 0 ){ nums[num - 1 ] = - nums[num - 1 ]; } } } for (int i = 0 ; i < n; i++){ if (nums[i] > 0 ){ return i+1 ; } } return n + 1 ; } };
矩阵 18.矩阵置零 给定一个 m x n 的矩阵,如果一个元素为 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 class Solution {public : void setZeroes (vector<vector<int >>& matrix) { int m = matrix.size (); int n = matrix[0 ].size (); vector<int > row (m,0 ) ,col (n,0 ) ; for (int i = 0 ; i < m ; i++){ for (int j = 0 ; j < n ; j++ ){ if (matrix[i][j] == 0 ){ row[i] = 1 ; col[j] = 1 ; } } } for (int i = 0 ; i < m ; i++){ for (int j = 0 ; j < n ; j++){ if (row[i]==1 || col[j]==1 ){ matrix[i][j] = 0 ; } } } } };
19.螺旋矩阵 给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
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 vector<int > spiralOrder (vector<vector<int >>& matrix) { int top = 0 ,left = 0 ; int bottom = matrix.size ()-1 , right = matrix[0 ].size ()-1 ; vector<int > ans; while (top <= bottom && left <= right){ for (int col = left; col <= right ; col++){ ans.push_back (matrix[top][col]); } for (int row = top + 1 ; row <= bottom; row++){ ans.push_back (matrix[row][right]); } if (left < right && top < bottom){ for (int col = right - 1 ; col > left; col--){ ans.push_back (matrix[bottom][col]); } for (int row = bottom; row > top ; row -- ){ ans.push_back (matrix[row][left]); } } left++; right--; top++; bottom--; } return ans; }
20.旋转图像 给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
1 2 3 4 5 6 7 8 9 10 11 void rotate (vector<vector<int >>& matrix) { int n = matrix.size (); auto matrix_new = matrix; for (int i = 0 ; i < n; ++i) { for (int j = 0 ; j < n; ++j) { matrix_new[j][n - i - 1 ] = matrix[i][j]; } } matrix = matrix_new; }
21. 搜索二维矩阵 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:
每行的元素从左到右升序排列。 每列的元素从上到下升序排列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 bool searchMatrix (vector<vector<int >>& matrix, int target) { int m = matrix.size (), n = matrix[0 ].size (); int x = 0 , y = n - 1 ; while (x < m && y >= 0 ){ if (matrix[x][y] == target){ return true ; } if (matrix[x][y] > target){ y--; }else { x++; } } return false ; }
链表 22.相交链表 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
//方法1.哈希,特殊的hash就是原地hash,遍历A,把A都改成负数,然后再遍历一次B,第一个负数就是交点。 //方法2.先对齐,长的指针先走几个。 //方法二,双指针,当两者长度二次遍历然后相同时,长度都为a+b-c
1 2 3 4 5 6 7 8 9 10 11 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; } };
23.反转链表 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ListNode* reverseList (ListNode* head) { ListNode* pre = NULL , *cur = head; if (cur == NULL ){ return NULL ; } while (cur != NULL ){ ListNode* next = cur->next; cur->next = pre; pre = cur; if (next == NULL ){ return cur; } cur = next; } return NULL ; }
24.回文链表 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 bool isPalindrome (ListNode* head) { if (head == nullptr || head->next == nullptr ) { return true ; } ListNode* slow = head; ListNode* fast = head; while (fast != nullptr && fast->next != nullptr ) { slow = slow->next; fast = fast->next->next; } ListNode* prev = nullptr ; ListNode* curr = slow; ListNode* next = nullptr ; while (curr != nullptr ) { next = curr->next; curr->next = prev; prev = curr; curr = next; } ListNode* left = head; ListNode* right = prev; while (right != nullptr ) { if (left->val != right->val) { return false ; } left = left->next; right = right->next; } return true ; }
25.环形链表 检测链表中有没有环形出现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 bool hasCycle (ListNode *head) { if (head == nullptr || head->next == nullptr ){ return false ; } ListNode * slow = head; ListNode * fast = head->next; while (slow != nullptr && fast != nullptr ){ if (fast == slow){ return true ; } slow = slow->next; if (fast->next != nullptr ){ fast = fast->next->next; }else { return false ; } } return false ; }
26.环形链表2 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
参考题解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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; }
27.合并有序链表 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的
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 ListNode* mergeTwoLists (ListNode* list1, ListNode* list2) { ListNode* p1 = list1, * p2 = list2; ListNode* ans = new ListNode; ListNode *t = ans; while (p1 != nullptr && p2 != nullptr ){ if (p1->val <= p2->val){ t->next = p1; t = t->next; p1 = p1->next; }else { t->next = p2; t = t->next; p2 = p2->next; } } while (p1 != nullptr ){ t->next = p1; t = t->next; p1 = p1->next; } while (p2 != nullptr ){ t->next = p2; t = t->next; p2 = p2->next; } ListNode* anshead = ans; ans = ans->next; delete anshead; return ans; }
28.两数相加 给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 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 ListNode* addTwoNumbers (ListNode* l1, ListNode* l2) { ListNode* p1 = l1,*p2 = l2; int cin = 0 ; ListNode * pSum = new ListNode; ListNode *p = pSum; while (p1 != nullptr || p2 != nullptr ){ int val1 = 0 ,val2 = 0 ; if (p1 != nullptr ){ val1 = p1->val; p1 = p1->next; } if (p2 != nullptr ){ val2 = p2->val; p2 = p2->next; } int sum = val1 + val2 + cin; cin = sum / 10 ; ListNode * node = new ListNode; node->val = sum % 10 ; p->next = node; p = p->next; } if (cin == 1 ){ ListNode * node = new ListNode; node->val = 1 ; p->next = node; p = p->next; } return pSum->next; }
29.删除链表的倒数第N个节点 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ListNode* removeNthFromEnd (ListNode* head, int n) { ListNode * s = head,*f = head,*pre = nullptr ; while ( n-- > 0 ){ f = f->next; } if (f == nullptr ) { ListNode* newHead = head->next; delete head; return newHead; } while ( f != nullptr ){ f = f->next; pre = s; s = s->next; } pre->next = s->next; delete s; return head; }
30.两两交换链表中的节点 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
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 ListNode* swapPairs (ListNode* head) { if (head == nullptr || head->next == nullptr ) { return head; } ListNode* first = head; ListNode* second = head->next; ListNode* rest = swapPairs (second->next); second->next = first; first->next = rest; return second; } ListNode* swapPairs (ListNode* head) { if (head == nullptr || head->next == nullptr ) { return head; } ListNode* dummy = new ListNode (0 ); dummy->next = head; ListNode* prev = dummy; while (prev->next && prev->next->next) { ListNode* first = prev->next; ListNode* second = first->next; first->next = second->next; second->next = first; prev->next = second; prev = first; } ListNode* result = dummy->next; delete dummy; return result; }
31.K个一组翻转链表 给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。
k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 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 43 44 45 46 class Solution {public : ListNode* reverseKGroup (ListNode* head, int k) { ListNode * dummpy = new ListNode; dummpy->next = head; ListNode * pre = dummpy; ListNode* end = dummpy; while ( end->next != nullptr ){ for (int i = 0 ; i < k && end != nullptr ; i++){ end = end->next; } if (end == nullptr ) break ; ListNode * start = pre->next; ListNode * _next = end->next; end->next = nullptr ; pre->next = reverse (start); start->next = _next; pre = start; end = pre; } return dummpy->next; } ListNode* reverse (ListNode * head) { ListNode * cur = head; ListNode * pre = nullptr ; while (cur != nullptr ){ ListNode * _next = cur->next; cur->next = pre; pre = cur; cur = _next; } return pre; } };
参考题解: https://leetcode.cn/problems/reverse-nodes-in-k-group/?envType=study-plan-v2&envId=top-100-liked
32.随机链表的复制 给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X 和 Y 两个节点,其中 X.random –> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random –> y 。
返回复制链表的头节点。
用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
val:一个表示 Node.val 的整数。 random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。 你的代码 只 接受原链表的头节点 head 作为传入参数。
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 class Solution {public : Node* copyRandomList (Node* head) { if (head == nullptr ){ return nullptr ; } Node * cur = head; unordered_map<Node* ,Node *> map; while (cur != nullptr ){ map[cur] = new Node (cur->val); cur = cur->next; } cur = head; while (cur != nullptr ){ map[cur]->next = map[cur->next]; map[cur]->random = map[cur->random]; cur = cur->next; } return map[head]; } Node* copyRandomList (Node* head) { if (head == nullptr ){ return nullptr ; } Node * cur = head; while (cur != nullptr ){ Node* next = cur->next; Node* newNode = new Node (cur->val); cur->next = newNode; newNode->next = next; cur = next; } cur = head; while (cur != nullptr ){ if (cur->random != nullptr ){ cur->next->random = cur->random->next; } cur = cur->next->next; } cur = head->next; Node* pre = head,*res = head->next; while (cur->next != nullptr ){ pre->next = pre->next->next; cur->next = cur->next->next; pre = pre->next; cur = cur->next; } pre->next = nullptr ; return res; } };
//参考题解 https://leetcode.cn/problems/copy-list-with-random-pointer/solutions/2361362/138-fu-zhi-dai-sui-ji-zhi-zhen-de-lian-b-6jeo/?envType=study-plan-v2&envId=top-100-liked
33.排序链表 给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。 你可以在 O(n log 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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class Solution {public : ListNode* sortList (ListNode* head) { if (head == nullptr || head->next == nullptr ){ return head; } ListNode* mid = findMid (head); ListNode* left = head,*right = mid->next; mid->next = nullptr ; left = sortList (left); right = sortList (right); return merge (left,right); } private : ListNode* findMid (ListNode* head) { if (head == nullptr || head->next == nullptr ){ return head; } ListNode * slow = head,*fast = head->next; while (fast != nullptr && fast->next != nullptr ){ slow = slow->next; fast = fast->next->next; } return slow; } ListNode* merge (ListNode* l1,ListNode* l2) { ListNode dummpy (0 ) ; ListNode* t = &dummpy; ListNode* p1 = l1,*p2 = l2; while (p1 && p2){ if (p1->val <= p2->val){ t->next = p1; p1 = p1->next; }else { t->next = p2; p2 = p2->next; } t= t->next; } if (p1){ t->next = p1; }else { t->next = p2; } return dummpy.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 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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 class Solution {public : ListNode* sortList (ListNode* head) { if (head == nullptr || head->next == nullptr ){ return head; } int length = getLength (head); ListNode dummy (0 ) ; dummy.next = head; for (int step = 1 ; step < length ; step <<= 1 ){ ListNode* prev = &dummy; ListNode* cur = dummy.next; while (cur != nullptr ){ ListNode* left = cur; ListNode* right = split (left, step); cur = split (right, step); ListNode* merged = merge (left, right); prev->next = merged; while (prev->next != nullptr ){ prev = prev->next; } } } return dummy.next; } private : int getLength (ListNode * head) { int length = 0 ; ListNode * cur = head; while (cur != nullptr ){ cur = cur->next; length++; } return length; } ListNode* split (ListNode* head, int size) { if (head == nullptr ) return nullptr ; ListNode * cur = head; for (int i = 1 ; i < size && cur->next != nullptr ; i++){ cur = cur->next; } ListNode *next = cur->next; cur->next = nullptr ; return next; } ListNode* merge (ListNode* l1, ListNode* l2) { ListNode dummy (0 ) ; ListNode* t = &dummy; while (l1 && l2){ if (l1->val <= l2->val){ t->next = l1; l1 = l1->next; }else { t->next = l2; l2 = l2->next; } t = t->next; } t->next = l1 ? l1 : l2; return dummy.next; } };
34.合并 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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 class Solution {public : ListNode* mergeKLists (vector<ListNode*>& lists) { ListNode dummpy; ListNode * ret ; ListNode * t = &dummpy; vector<ListNode*> vec_cur = lists; int k = lists.size (); while (1 ){ bool all_empty = true ; ListNode* minNode = nullptr ; int min_i = 0 ; for (int i = 0 ; i < k ; i++){ if (vec_cur[i]){ all_empty = false ; if (nullptr == minNode){ minNode = vec_cur[i]; min_i = i; } else if (minNode->val > vec_cur[i]->val){ minNode = vec_cur[i]; min_i = i; } } } if (all_empty){ break ; } t->next = minNode; vec_cur[min_i] = vec_cur[min_i]->next; t = t->next; } return dummpy.next; } ListNode* mergeKLists (vector<ListNode*>& lists) { auto cmp = [](const ListNode * a ,const ListNode * b ){ return a->val > b->val; }; priority_queue<ListNode*,vector<ListNode*>,decltype (cmp)> pq; for (auto head : lists){ if (head){ pq.push (head); } } ListNode dummpy; ListNode* cur = &dummpy; while (!pq.empty ()){ auto node = pq.top (); pq.pop (); if (node->next){ pq.push (node->next); } cur->next = node; cur = cur->next; } return dummpy.next; } };
35.LRU 缓存 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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 struct Node { Node* pre; Node* next; int key; int value; }; class LRUCache {public : LRUCache (int capacity) { m_capacity = capacity; dummpy_head.next = &dummpy_tail; dummpy_tail.pre = &dummpy_head; } int get (int key) { if (m_map.contains (key)){ Node * node = m_map[key]; moveToHead (node); return node->value; } return -1 ; } void put (int key, int value) { if (m_map.contains (key)){ Node * node = m_map[key]; moveToHead (node); node->value = value; }else { Node* new_node = new Node; new_node->value = value; new_node->key = key; m_map[key] = new_node; addToHead (new_node); ++m_size; if (m_size > m_capacity){ Node* removed = removeTail (); m_map.erase (removed->key); delete removed; m_size--; } } return ; } private : void removeNode (Node* node) { node->next->pre = node->pre; node->pre->next = node->next; } Node* removeTail () { Node* tail = dummpy_tail.pre; removeNode (tail); return tail; } void addToHead (Node* node) { dummpy_head.next->pre = node; node->next = dummpy_head.next; node->pre = &dummpy_head; dummpy_head.next = node; } void moveToHead (Node* node) { removeNode (node); addToHead (node); } unordered_map<int ,Node*> m_map; int m_capacity{0 }; int m_size{0 }; Node dummpy_head; Node dummpy_tail; };
二叉树 36.二叉树的中序遍历 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 class Solution {public : vector<int > inorderTraversal (TreeNode* root) { vector<int > ret; if (root == nullptr ) return ret; vector<int > tmp = inorderTraversal (root->left); ret.insert (ret.end (),tmp.begin (),tmp.end ()); ret.push_back (root->val); tmp = inorderTraversal (root->right); ret.insert (ret.end (),tmp.begin (),tmp.end ()); return ret; } vector<int > inorderTraversal (TreeNode* root) { vector<int > ret; stack<TreeNode*> stk; while (root != nullptr || !stk.empty ()){ while (root != nullptr ){ stk.push (root); root = root->left; } root = stk.top (); stk.pop (); ret.push_back (root->val); root = root->right; } return ret; } };
37.二叉树的最大深度 优秀题解 DFS与BFS讲解 https://leetcode.cn/problems/maximum-depth-of-binary-tree/solutions/2361697/104-er-cha-shu-de-zui-da-shen-du-hou-xu-txzrx/?envType=study-plan-v2&envId=top-100-liked
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 maxDepth (TreeNode* root) { if (nullptr == root){ return 0 ; } return 1 + std::max (maxDepth (root->left),maxDepth (root->right)); } int maxDepth (TreeNode* root) { if (root == nullptr ) return 0 ; vector<TreeNode*> que; que.push_back (root); int res = 0 ; while (!que.empty ()) { vector<TreeNode*> tmp; for (TreeNode* node : que) { if (node->left != nullptr ) tmp.push_back (node->left); if (node->right != nullptr ) tmp.push_back (node->right); } que = tmp; res++; } return res; } };
38.翻转二叉树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : void swap (TreeNode* root) { if (root == nullptr ){ return ; } TreeNode * tmp = root->left; root->left = root->right; root->right = tmp; swap (root->left); swap (root->right); return ; } TreeNode* invertTree (TreeNode* root) { swap (root); return root; } };
39.对称二叉树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : void swap (TreeNode* root) { if (root == nullptr ){ return ; } TreeNode * tmp = root->left; root->left = root->right; root->right = tmp; swap (root->left); swap (root->right); return ; } TreeNode* invertTree (TreeNode* root) { swap (root); return root; } };
40.二叉树的直径 给你一棵二叉树的根节点,返回该树的 直径。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : int diameterOfBinaryTree (TreeNode* root) { ans = 0 ; depth (root); return ans; } private : int ans; int depth (TreeNode* root) { if (nullptr == root){ return 0 ; } int L = depth (root->left); int R = depth (root->right); ans = max (ans,L+R); return std::max (L,R)+1 ; } };
41.二叉树的层次遍历 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 {public : vector<vector<int >> levelOrder (TreeNode* root) { queue<TreeNode* > q; vector<vector<int >> ret; if (!root){ return ret; } q.push (root); while (!q.empty ()){ vector<TreeNode*> nextLayer; vector<int > tmp; while (!q.empty ()){ TreeNode* node = q.front (); q.pop (); if (!node) continue ; tmp.push_back (node->val); if (node->left){ nextLayer.push_back (node->left); } if (node->right){ nextLayer.push_back (node->right); } } ret.push_back (tmp); for (auto node : nextLayer){ q.push (node); } } return ret; } };
42.将有序数组转换为二叉搜索树 给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 平衡二叉搜索树(左右子树高度不超过1)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : TreeNode* sortedArrayToBST (vector<int >& nums) { return helper (nums,0 ,nums.size ()-1 ); } private : TreeNode* helper (vector<int >& nums,int left ,int right) { if (left > right){ return nullptr ; } int mid = (right-left)/2 +left; TreeNode* node = new TreeNode (nums[mid]); node->left = helper (nums,left,mid-1 ); node->right = helper (nums,mid+1 ,right); return node; } };
43.验证有效的二叉搜索树 给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下: 节点的左子树只包含 严格小于 当前节点的数。 节点的右子树只包含 严格大于 当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : bool isValidBST (TreeNode* root) { return check (root,LONG_MIN,LONG_MAX); } private : bool check (TreeNode* node , long low,long high) { if (!node){ return true ; } if (node->val >= high || node->val <= low){ return false ; } return check (node->left,low,node->val) && check (node->right,node->val,high); } };
44.二叉搜索树中第 K 小的元素 给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 小的元素(k 从 1 开始计数)。 进阶:如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化算法?
进阶题解 https://leetcode.cn/problems/kth-smallest-element-in-a-bst/solutions/1050055/er-cha-sou-suo-shu-zhong-di-kxiao-de-yua-8o07/?envType=study-plan-v2&envId=top-100-liked
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : int kthSmallest (TreeNode* root, int k) { stack<TreeNode*> stack; while (root != nullptr || stack.size () > 0 ){ while (root != nullptr ){ stack.push (root); root = root->left; } root = stack.top (); stack.pop (); --k; if (k == 0 ){ break ; } root = root->right; } return root->val; } };
45.二叉树的右视图 给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
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 : vector<int > rightSideView (TreeNode* root) { queue<TreeNode*> q; vector<int > ans; if (!root){ return ans; } q.push (root); while (!q.empty ()){ int last_val = 0 ; vector<TreeNode*> tmp; while (!q.empty ()){ TreeNode* node = q.front (); q.pop (); if (node){ last_val = node->val; }else { continue ; } if (node->left){ tmp.push_back (node->left); } if (node->right){ tmp.push_back (node->right); } } ans.push_back (last_val); for (auto node :tmp){ q.push (node); } } return ans; } };
46.二叉树展开为链表 给你二叉树的根结点 root ,请你将它展开为一个单链表:
展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。 展开后的单链表应该与二叉树 先序遍历 顺序相同
你可以使用原地算法(O(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 32 33 34 35 36 37 38 39 40 41 class Solution {public : void flatten (TreeNode* root) { vector<TreeNode * > l; stack<TreeNode*> stack; TreeNode * node = root; while (node != nullptr || !stack.empty ()){ while (node != nullptr ){ l.push_back (node); stack.push (node); node = node->left; } node = stack.top (); stack.pop (); node = node->right; } for (int i = 1 ; i < l.size (); i++){ TreeNode* pre = l[i-1 ], *cur = l[i]; pre->left = nullptr ; pre->right = cur; } } void flatten (TreeNode* root) { TreeNode* cur = root; while (cur != nullptr ){ if (cur->left != nullptr ){ auto next = cur->left; auto predecessor = next; while (predecessor->right != nullptr ){ predecessor = predecessor->right; } predecessor->right = cur->right; cur->left = nullptr ; cur->right = next; } cur = cur->right; } } };
//参考题解 https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/?envType=study-plan-v2&envId=top-100-liked
47.从前序与中序遍历序列构造二叉树 给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
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 : TreeNode* buildTree (vector<int >& preorder, vector<int >& inorder) { int n = preorder.size (); for (int i = 0 ; i < n; ++i) { index[inorder[i]] = i; } return myBuildTree (preorder, inorder, 0 , n - 1 , 0 , n - 1 ); } private : unordered_map<int , int > index; TreeNode* myBuildTree (const vector<int >& preorder, const vector<int >& inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) { if (preorder_left > preorder_right) { return nullptr ; } int preorder_root = preorder_left; int inorder_root = index[preorder[preorder_root]]; TreeNode* root = new TreeNode (preorder[preorder_root]); int size_left_subtree = inorder_root - inorder_left; root->left = myBuildTree (preorder, inorder, preorder_left + 1 , preorder_left + size_left_subtree, inorder_left, inorder_root - 1 ); root->right = myBuildTree (preorder, inorder, preorder_left + size_left_subtree + 1 , preorder_right, inorder_root + 1 , inorder_right); return root; } };
//题解参考: https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/?envType=study-plan-v2&envId=top-100-liked
48.路径总和 III 给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。 路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
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 class Solution {public : int rootSum (TreeNode* root, long long targetSum) { if (!root) { return 0 ; } int ret = 0 ; if (root->val == targetSum) { ret++; } ret += rootSum (root->left, targetSum - root->val); ret += rootSum (root->right, targetSum - root->val); return ret; } int pathSum (TreeNode* root, int targetSum) { if (!root) { return 0 ; } int ret = rootSum (root, targetSum); ret += pathSum (root->left, targetSum); ret += pathSum (root->right, targetSum); return ret; } };
49.二叉树的最近公共祖先 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {private : TreeNode* ans; bool dfs (TreeNode* root,TreeNode* p , TreeNode * q) { if (root == nullptr ) return false ; bool lson = dfs (root->left,p,q); bool rson = dfs (root->right,p,q); if ((lson && rson)||((root->val == p->val || root->val == q->val)&& (lson || rson))){ ans = root; } return lson || rson || (root->val == p->val || root->val == q->val); } public : TreeNode* lowestCommonAncestor (TreeNode* root, TreeNode* p, TreeNode* q) { dfs (root,p,q); return ans; } };
50.二叉树中的最大路径和 二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。 路径和 是路径中各节点值的总和。 给你一个二叉树的根节点 root ,返回其 最大路径和 。
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 : int maxSum = INT_MIN; public : int maxGain (TreeNode* node) { if (node == nullptr ) { return 0 ; } int leftGain = max (maxGain (node->left), 0 ); int rightGain = max (maxGain (node->right), 0 ); int priceNewpath = node->val + leftGain + rightGain; maxSum = max (maxSum, priceNewpath); return node->val + max (leftGain, rightGain); } int maxPathSum (TreeNode* root) { maxGain (root); return maxSum; } };
图论 51.岛屿数量 给你一个由 ‘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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class Solution {private : void dfs (vector<vector<char >>& grid,int r,int c) { int rows = grid.size (); int cols = grid[0 ].size (); grid[r][c] = '0' ; if (r-1 >=0 && grid[r-1 ][c] == '1' ){ dfs (grid,r-1 ,c); } if (r+1 < rows && 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 < cols && grid[r][c+1 ] == '1' ){ dfs (grid,r,c+1 ); } } public : int numIslands (vector<vector<char >>& grid) { int rows = grid.size (); if (rows == 0 ) return 0 ; int cols = grid[0 ].size (); int num_islands = 0 ; for (int r = 0 ; r < rows; r++){ for (int c = 0 ; c < cols; c++){ if (grid[r][c] == '1' ){ ++num_islands; dfs (grid,r,c); } } } return num_islands; } };
52.腐烂的橘子 在给定的 m x n 网格 grid 中,每个单元格可以有以下三个值之一: 值 0 代表空单元格; 值 1 代表新鲜橘子; 值 2 代表腐烂的橘子。 每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。 返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -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 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 58 59 class Solution {public : int orangesRotting (vector<vector<int >>& grid) { int m = grid.size (); int n = grid[0 ].size (); vector<pair<int , int >> directions = {{-1 , 0 }, {1 , 0 }, {0 , -1 }, {0 , 1 }}; queue<pair<int , int >> q; int freshCount = 0 ; for (int i = 0 ; i < m; i++) { for (int j = 0 ; j < n; j++) { if (grid[i][j] == 2 ) { q.push ({i, j}); } else if (grid[i][j] == 1 ) { freshCount++; } } } if (freshCount == 0 ) return 0 ; int minutes = 0 ; while (!q.empty ()) { int size = q.size (); bool rotted = false ; for (int i = 0 ; i < size; i++) { auto [x, y] = q.front (); q.pop (); for (auto [dx, dy] : directions) { int nx = x + dx; int ny = y + dy; if (nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] == 1 ) { grid[nx][ny] = 2 ; q.push ({nx, ny}); freshCount--; rotted = true ; } } } if (rotted) minutes++; } return freshCount == 0 ? minutes : -1 ; } };
53.课程表 你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。 例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。 请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 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 class Solution {public : bool canFinish (int numCourses, vector<vector<int >>& prerequisites) { vector<vector<int >> adj (numCourses); vector<int > inDegree (numCourses, 0 ) ; for (auto & pre : prerequisites) { int course = pre[0 ]; int prerequisite = pre[1 ]; adj[prerequisite].push_back (course); inDegree[course]++; } queue<int > q; for (int i = 0 ; i < numCourses; i++) { if (inDegree[i] == 0 ) { q.push (i); } } int count = 0 ; while (!q.empty ()) { int curr = q.front (); q.pop (); count++; for (int next : adj[curr]) { inDegree[next]--; if (inDegree[next] == 0 ) { q.push (next); } } } return count == numCourses; } };
54.前缀树 Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补全和拼写检查。
请你实现 Trie 类: Trie() 初始化前缀树对象。 void insert(String word) 向前缀树中插入字符串 word 。 boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。 boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。
示例: 输入 [“Trie”, “insert”, “search”, “search”, “startsWith”, “insert”, “search”] [[], [“apple”], [“apple”], [“app”], [“app”], [“app”], [“app”]] 输出 [null, null, true, false, true, null, true]
解释 Trie trie = new Trie(); trie.insert(“apple”); trie.search(“apple”); // 返回 True trie.search(“app”); // 返回 False trie.startsWith(“app”); // 返回 True trie.insert(“app”); trie.search(“app”); // 返回 True
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 class Trie {private : struct TrieNode { bool isEnd; TrieNode* children[26 ]; TrieNode () { isEnd = false ; for (int i = 0 ; i < 26 ; i++) { children[i] = nullptr ; } } }; TrieNode* root; public : Trie () { root = new TrieNode (); } void insert (string word) { TrieNode* node = root; for (char c : word) { int index = c - 'a' ; if (node->children[index] == nullptr ) { node->children[index] = new TrieNode (); } node = node->children[index]; } node->isEnd = true ; } bool search (string word) { TrieNode* node = root; for (char c : word) { int index = c - 'a' ; if (node->children[index] == nullptr ) { return false ; } node = node->children[index]; } return node->isEnd; } bool startsWith (string prefix) { TrieNode* node = root; for (char c : prefix) { int index = c - 'a' ; if (node->children[index] == nullptr ) { return false ; } node = node->children[index]; } return true ; } ~Trie () { clear (root); } private : void clear (TrieNode* node) { if (node == nullptr ) return ; for (int i = 0 ; i < 26 ; i++) { if (node->children[i] != nullptr ) { clear (node->children[i]); } } delete node; } };
每个节点都发散出26个字母的节点。注意这种数据结构的组织方式
回溯 deepseek 提问:常见的回溯类型、模板、区别、执行过程推演。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void backtrack (参数) { if (满足结束条件) { 存放结果; return ; } for (选择 : 所有可能的选择) { if (选择无效) continue ; 做选择(标记已使用); backtrack (新参数); 撤销选择(取消标记); } }
55.全排列 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
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 class Solution {public : vector<vector<int >> permute (vector<int >& nums) { vector<vector<int >> ans; vector<bool > used (nums.size(),false ) ; vector<int > curr; backtrace (ans,nums,used,curr); return ans; } private : void backtrace (vector<vector<int >>& ans,vector<int >&nums,vector<bool >& used,vector<int >& curr) { if (nums.size () == curr.size ()){ ans.push_back (curr); return ; } for (int i = 0 ; i < nums.size ();i++){ if (used[i]){ continue ; } used[i] = true ; curr.push_back (nums[i]); backtrace (ans,nums,used,curr); used[i] = false ; curr.pop_back (); } } };
56.子集 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
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 : vector<vector<int >> subsets (vector<int >& nums) { vector<vector<int >> res; vector<int > cur; backtrace (res,cur,0 ,nums); return res; } private : void backtrace (vector<vector<int >>& res,vector<int >& cur,int start_index,vector<int >& nums) { res.push_back (cur); for (int i = start_index ; i < nums.size ();i++){ cur.push_back (nums[i]); backtrace (res,cur,i+1 ,nums); cur.pop_back (); } } };
57.电话号码的字母组合 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 { vector<vector<char >> char_map; public : vector<string> letterCombinations (string digits) { vector<string> ret; if (digits.empty ()){ return ret; } string curr; char_map = {{},{},{'a' ,'b' ,'c' },{'d' ,'e' ,'f' },{'g' ,'h' ,'i' },{'j' ,'k' ,'l' },{'m' ,'n' ,'o' },{'p' ,'q' ,'r' ,'s' },{'t' ,'u' ,'v' },{'w' ,'x' ,'y' ,'z' }}; backtrace (ret,digits,0 ,curr); return ret; } void backtrace (vector<string>& ret,string digits,int index,string & curr) { if (index == digits.length ()){ ret.push_back (curr); return ; } int num = digits.at (index) - '0' ; for (auto ch : char_map[num]){ curr += ch; backtrace (ret,digits,index+1 ,curr); curr.pop_back (); } } };
58.组合总和 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 对于给定的输入,保证和为 target 的不同组合数少于 150 个。
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 : vector<vector<int >> combinationSum (vector<int >& candidates, int target) { vector<vector<int >> ret; vector<int > curr; backtrace (ret,curr,candidates,target,0 ); return ret; } void backtrace (vector<vector<int >> & ret,vector<int >& curr,vector<int >& candidates,int target,int startIndex) { if (target == 0 ){ ret.push_back (curr); return ; } if (target < 0 ){ return ; } for (int i = startIndex ; i < candidates.size ();i++){ int num = candidates.at (i); curr.push_back (num); target -= num; backtrace (ret,curr,candidates,target,i); target += num; curr.pop_back (); } } };
59.括号生成 数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
输入:n = 3 输出:[“((()))”,”(()())”,”(())()”,”()(())”,”()()()”]
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 : vector<string> generateParenthesis (int n) { vector<string> ret; string curr; backtrace (ret,n,n,curr); return ret; } private : void backtrace (vector<string>&ret,int left,int right, string& curr) { if (left == 0 && right ==0 ){ ret.push_back (curr); return ; } if (left > 0 ){ curr += '(' ; backtrace (ret,left-1 ,right,curr); curr.pop_back (); } if (right > 0 && right > left){ curr += ')' ; backtrace (ret,left,right-1 ,curr); curr.pop_back (); } } };
60.单词搜索 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 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 57 class Solution {public : vector<pair<int ,int >> vec_dics = {{1 ,0 },{-1 ,0 },{0 ,1 },{0 ,-1 }}; bool exist (vector<vector<char >>& board, string word) { int row = board.size (); int col = board[0 ].size (); vector<vector<bool >> used (row, vector <bool >(col, false )); for (int r = 0 ; r < row; r++){ for (int c = 0 ; c < col; c++){ if (board[r][c] == word[0 ]){ if (backtrace (board, word, 0 , r, c, used)){ return true ; } } } } return false ; } bool backtrace (vector<vector<char >>& board, string word, int index, int r, int c, vector<vector<bool >>& used) { if (index == word.length ()){ return true ; } int row = board.size (); int col = board[0 ].size (); if (r < 0 || r >= row || c < 0 || c >= col || used[r][c] || board[r][c] != word[index]){ return false ; } used[r][c] = true ; for (auto dic : vec_dics){ int next_r = r + dic.first; int next_c = c + dic.second; if (backtrace (board, word, index + 1 , next_r, next_c, used)){ return true ; } } used[r][c] = false ; return false ; } };
61.分割回文串 给你一个字符串 s,请你将 s 分割成一些 子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。 输入:s = “aab” 输出:[[“a”,”a”,”b”],[“aa”,”b”]]
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 : vector<vector<string>> partition (string s) { vector<vector<string>> ret; vector<string> curs; backTrace (ret,s,curs,0 ); return ret; } void backTrace (vector<vector<string>>& ret,string s,vector<string>& curs,int startIndex) { if (startIndex == s.length ()){ ret.push_back (curs); return ; } for (int i = startIndex ; i < s.length () ; i++){ if (isPalindrome (s,startIndex,i)){ string substr = s.substr (startIndex,i-startIndex+1 ); curs.push_back (substr); backTrace (ret,s,curs,i+1 ); curs.pop_back (); } } } bool isPalindrome (const string& s, int left, int right) { while (left < right) { if (s[left] != s[right]) { return false ; } left++; right--; } return true ; } };
62.N 皇后 按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
输入:n = 4 输出:[[“.Q..”,”…Q”,”Q…”,”..Q.”],[“..Q.”,”Q…”,”…Q”,”.Q..”]] 解释:如上图所示,4 皇后问题存在两个不同的解法。
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 class Solution {private : vector<vector<string>> result; bool isValid (vector<string>& board, int row, int col, int n) { for (int i = 0 ; i < row; i++) { if (board[i][col] == 'Q' ) return false ; } for (int i = row - 1 , j = col - 1 ; i >= 0 && j >= 0 ; i--, j--) { if (board[i][j] == 'Q' ) return false ; } for (int i = row - 1 , j = col + 1 ; i >= 0 && j < n; i--, j++) { if (board[i][j] == 'Q' ) return false ; } return true ; } void backtrack (vector<string>& board, int row, int n) { if (row == n) { result.push_back (board); return ; } for (int col = 0 ; col < n; col++) { if (isValid (board, row, col, n)) { board[row][col] = 'Q' ; backtrack (board, row + 1 , n); board[row][col] = '.' ; } } } public : vector<vector<string>> solveNQueens (int n) { result.clear (); vector<string> board (n, string(n, '.' )) ; backtrack (board, 0 , n); return result; } };
二分查找 63.搜索插入位置 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : int searchInsert (vector<int >& nums, int target) { int left = 0 ; int right = nums.size ()-1 ; while (left <= right){ int mid = (left + right) / 2 ; if (nums[mid] > target){ right = mid - 1 ; }else if (nums[mid] < target){ left = mid + 1 ; }else { return mid; } } return left; } };
64.搜索二维矩阵 给你一个满足下述两条属性的 m x n 整数矩阵: 每行中的整数从左到右按非严格递增顺序排列。 每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target ,如果 target 在矩阵中,返回 true ;否则,返回 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 class Solution {public :bool searchMatrix (vector<vector<int >>& matrix, int target) { if (matrix.empty () || matrix[0 ].empty ()) return false ; int m = matrix.size (); int n = matrix[0 ].size (); int left = 0 ; int right = m * n - 1 ; while (left <= right) { int mid = left + (right - left) / 2 ; int row = mid / n; int col = mid % n; int value = matrix[row][col]; if (value == target) { return true ; } else if (value < target) { left = mid + 1 ; } else { right = mid - 1 ; } } return false ; } };
65.在排序数组中查找元素的第一个和最后一个位置 给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target,返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution {public : vector<int > searchRange (vector<int >& nums, int target) { auto first = lower_bound (nums.begin (), nums.end (), target); auto last = upper_bound (nums.begin (), nums.end (), target); if (first == nums.end () || *first != target) { return {-1 , -1 }; } return {static_cast <int >(first - nums.begin ()), static_cast <int >(last - nums.begin () - 1 )}; } };
66.搜索旋转排序数组 整数数组 nums 按升序排列,数组中的值 互不相同 。 在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 向左旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 下标 3 上向左旋转后可能变为 [4,5,6,7,0,1,2]。 给你 旋转后的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。 你必须设计一个时间复杂度为 O(log 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 26 27 28 29 30 31 32 33 34 35 36 37 38 class Solution {public : int search (vector<int >& nums, int target) { if (nums.empty ()) return -1 ; int left = 0 ; int right = nums.size () - 1 ; while (left <= right) { int mid = left + (right - left) / 2 ; if (nums[mid] == target) { return mid; } if (nums[left] <= nums[mid]) { if (nums[left] <= target && target < nums[mid]) { right = mid - 1 ; } else { left = mid + 1 ; } } else { if (nums[mid] < target && target <= nums[right]) { left = mid + 1 ; } else { right = mid - 1 ; } } } return -1 ; } };
67.寻找排序数组的最小值 已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到: 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2] 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7] 注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。 给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。 你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。
示例 1: 输入:nums = [3,4,5,1,2] 输出:1 解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。
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 findMin (vector<int >& nums) { int left = 0 ; int right = nums.size () - 1 ; while (left < right) { int mid = left + (right - left) / 2 ; if (nums[mid] > nums[right]) { left = mid + 1 ; } else { right = mid; } } return nums[left]; } };
68. 寻找两个正序数组的中位数 给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。 算法的时间复杂度应该为 O(log (m+n)) 。
示例 1: 输入:nums1 = [1,3], nums2 = [2] 输出:2.00000 解释:合并数组 = [1,2,3] ,中位数 2
示例 2: 输入:nums1 = [1,2], nums2 = [3,4] 输出:2.50000 解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
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 class Solution {public : double findMedianSortedArrays (vector<int >& nums1, vector<int >& nums2) { if (nums1. size () > nums2. size ()) { return findMedianSortedArrays (nums2, nums1); } int m = nums1. size (); int n = nums2. size (); int totalLeft = (m + n + 1 ) / 2 ; int left = 0 ; int right = m; while (left < right) { int i = left + (right - left + 1 ) / 2 ; int j = totalLeft - i; if (nums1[i - 1 ] > nums2[j]) { right = i - 1 ; } else { left = i; } } int i = left; int j = totalLeft - i; int nums1LeftMax = (i == 0 ) ? INT_MIN : nums1[i - 1 ]; int nums1RightMin = (i == m) ? INT_MAX : nums1[i]; int nums2LeftMax = (j == 0 ) ? INT_MIN : nums2[j - 1 ]; int nums2RightMin = (j == n) ? INT_MAX : nums2[j]; if ((m + n) % 2 == 1 ) { return max (nums1LeftMax, nums2LeftMax); } else { return (max (nums1LeftMax, nums2LeftMax) + min (nums1RightMin, nums2RightMin)) / 2.0 ; } } };
栈 69.有效的括号 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 : bool isValid (string s) { stack<char > st; for (char c : s) { if (c == '(' || c == '[' || c == '{' ) { st.push (c); } else { if (st.empty ()) { return false ; } char top = st.top (); if ((c == ')' && top == '(' ) || (c == ']' && top == '[' ) || (c == '}' && top == '{' )) { st.pop (); } else { return false ; } } } return st.empty (); } };
70.最小栈 方法一:辅助栈法(最常用) 使用两个栈: 数据栈:正常存储所有元素 最小栈:同步存储当前栈的最小值
方法二:单栈法(存储差值) 只用一个栈,存储元素与当前最小值的差值 需要额外的数学计算,但节省空间
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 MinStack {private : stack<long long > st; long long minVal; public : MinStack () { minVal = 0 ; } void push (int val) { if (st.empty ()) { st.push (0 ); minVal = val; } else { long long diff = (long long )val - minVal; st.push (diff); if (diff < 0 ) { minVal = val; } } } void pop () { long long diff = st.top (); st.pop (); if (diff < 0 ) { minVal = minVal - diff; } } int top () { long long diff = st.top (); if (diff < 0 ) { return minVal; } else { return minVal + diff; } } int getMin () { return minVal; } };
71.字符串解码 给定一个经过编码的字符串,返回它解码后的字符串。 编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。 你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。 此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。 测试用例保证输出的长度不会超过 105。
示例 1: 输入:s = “3[a]2[bc]” 输出:”aaabcbc”
方法一:辅助栈法(最常用) 使用两个栈: 数字栈:保存当前的重复次数 字符串栈:保存当前层之前的结果 遇到 [ 时,将当前数字和字符串压栈,然后重置 遇到 ] 时,从栈中弹出数字和之前的字符串,进行拼接
方法二:递归法 利用递归处理嵌套,遇到 [ 就递归调用,遇到 ] 就返回
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 class Solution {public : string decodeString (string s) { stack<int > numStack; stack<string> strStack; string currStr = "" ; int num = 0 ; for (char c : s) { if (isdigit (c)) { num = num * 10 + (c - '0' ); } else if (c == '[' ) { numStack.push (num); strStack.push (currStr); currStr = "" ; num = 0 ; } else if (c == ']' ) { int repeatTimes = numStack.top (); numStack.pop (); string prevStr = strStack.top (); strStack.pop (); string temp = "" ; for (int i = 0 ; i < repeatTimes; i++) { temp += currStr; } currStr = prevStr + temp; } else { currStr += c; } } return currStr; } };
72.每日温度 给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。 输入: temperatures = [73,74,75,71,69,72,76,73] 输出: [1,1,4,2,1,1,0,0]
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 > answer (n, 0 ) ; stack<int > st; for (int i = 0 ; i < n; i++) { while (!st.empty () && temperatures[i] > temperatures[st.top ()]) { int prevIndex = st.top (); st.pop (); answer[prevIndex] = i - prevIndex; } st.push (i); } return answer; } };
73.柱状图中最大的矩形 给定 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 class Solution {public : int largestRectangleArea (vector<int >& heights) { vector<int > newHeights (heights.size() + 2 , 0 ) ; for (int i = 0 ; i < heights.size (); i++) { newHeights[i + 1 ] = heights[i]; } stack<int > st; int maxArea = 0 ; for (int i = 0 ; i < newHeights.size (); i++) { while (!st.empty () && newHeights[i] < newHeights[st.top ()]) { int height = newHeights[st.top ()]; st.pop (); int left = st.top (); int right = i; int width = right - left - 1 ; maxArea = max (maxArea, height * width); } st.push (i); } return maxArea; } };
74.数组中第K大元素 给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。
题解https://leetcode.cn/problems/kth-largest-element-in-an-array/solutions/307351/shu-zu-zhong-de-di-kge-zui-da-yuan-su-by-leetcod-2/?envType=study-plan-v2&envId=top-100-liked
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 : int quickselect (vector<int > &nums, int l, int r, int k) { if (l == r) return nums[k]; int partition = nums[l], i = l - 1 , j = r + 1 ; while (i < j) { do i++; while (nums[i] < partition); do j--; while (nums[j] > partition); if (i < j) swap (nums[i], nums[j]); } if (k <= j)return quickselect (nums, l, j, k); else return quickselect (nums, j + 1 , r, k); } int findKthLargest (vector<int > &nums, int k) { int n = nums.size (); return quickselect (nums, 0 , n - 1 , n - k); } }; class Solution {public : void maxHeapify (vector<int >& a, int i, int heapSize) { int l = i * 2 + 1 , r = i * 2 + 2 , largest = i; if (l < heapSize && a[l] > a[largest]) { largest = l; } if (r < heapSize && a[r] > a[largest]) { largest = r; } if (largest != i) { swap (a[i], a[largest]); maxHeapify (a, largest, heapSize); } } void buildMaxHeap (vector<int >& a, int heapSize) { for (int i = heapSize / 2 - 1 ; i >= 0 ; --i) { maxHeapify (a, i, heapSize); } } int findKthLargest (vector<int >& nums, int k) { int heapSize = nums.size (); buildMaxHeap (nums, heapSize); for (int i = nums.size () - 1 ; i >= nums.size () - k + 1 ; --i) { swap (nums[0 ], nums[i]); --heapSize; maxHeapify (nums, 0 , heapSize); } return nums[0 ]; } };
75.前 K 个高频元素 给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 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 class Solution {public : vector<int > topKFrequent (vector<int >& nums, int k) { unordered_map<int , int > freq; for (int num : nums) { freq[num]++; } priority_queue<pair<int , int >, vector<pair<int , int >>, greater<pair<int , int >>> minHeap; for (auto & [num, count] : freq) { minHeap.push ({count, num}); if (minHeap.size () > k) { minHeap.pop (); } } vector<int > result; while (!minHeap.empty ()) { result.push_back (minHeap.top ().second); minHeap.pop (); } return result; } };
76.数据流的中位数 中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。
实现 MedianFinder 类: MedianFinder() 初始化 MedianFinder 对象。 void addNum(int num) 将数据流中的整数 num 添加到数据结构中。 double findMedian() 返回到目前为止所有元素的中位数。与实际答案相差 10-5 以内的答案将被接受。
输入 [“MedianFinder”, “addNum”, “addNum”, “findMedian”, “addNum”, “findMedian”] [[], [1], [2], [], [3], []] 输出 [null, null, null, 1.5, null, 2.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 42 43 44 45 46 47 class MedianFinder {private : priority_queue<int > maxHeap; priority_queue<int , vector<int >, greater<int >> minHeap; public : MedianFinder () { } void addNum (int num) { if (maxHeap.empty () || num <= maxHeap.top ()) { maxHeap.push (num); } else { minHeap.push (num); } if (maxHeap.size () > minHeap.size () + 1 ) { minHeap.push (maxHeap.top ()); maxHeap.pop (); } else if (minHeap.size () > maxHeap.size ()) { maxHeap.push (minHeap.top ()); minHeap.pop (); } } double findMedian () { if (maxHeap.size () == minHeap.size ()) { return (maxHeap.top () + minHeap.top ()) / 2.0 ; } else { return maxHeap.top (); } } };
贪心 77.买卖股票的最佳时机 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 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 42 43 44 class Solution {public : int maxProfit (vector<int >& prices) { if (prices.empty ()) return 0 ; int minPrice = prices[0 ]; int maxProfit = 0 ; for (int i = 1 ; i < prices.size (); i++) { if (prices[i] < minPrice) { minPrice = prices[i]; } else { maxProfit = max (maxProfit, prices[i] - minPrice); } } return maxProfit; } int maxProfit (vector<int >& prices) { int n = prices.size (); if (n <= 1 ) return 0 ; vector<vector<int >> dp (n, vector <int >(2 )); dp[0 ][0 ] = -prices[0 ]; dp[0 ][1 ] = 0 ; for (int i = 1 ; i < n; i++) { dp[i][0 ] = max (dp[i-1 ][0 ], -prices[i]); dp[i][1 ] = max (dp[i-1 ][1 ], dp[i-1 ][0 ] + prices[i]); } return dp[n-1 ][1 ]; } };
78.跳跃游戏 给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。
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 : bool canJump (vector<int >& nums) { int maxReach = 0 ; for (int i = 0 ; i < nums.size (); i++) { if (i > maxReach) { return false ; } maxReach = max (maxReach, i + nums[i]); if (maxReach >= nums.size () - 1 ) { return true ; } } return maxReach >= nums.size () - 1 ; } };
79.跳跃游戏 II 给定一个长度为 n 的 0 索引整数数组 nums。初始位置在下标 0。 每个元素 nums[i] 表示从索引 i 向后跳转的最大长度。换句话说,如果你在索引 i 处,你可以跳转到任意 (i + j) 处:
0 <= j <= nums[i] 且 i + j < n 返回到达 n - 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 class Solution {public : int jump (vector<int >& nums) { int n = nums.size (); if (n <= 1 ) return 0 ; int jumps = 0 ; int currentEnd = 0 ; int farthest = 0 ; for (int i = 0 ; i < n - 1 ; i++) { farthest = max (farthest, i + nums[i]); if (i == currentEnd) { jumps++; currentEnd = farthest; } } return jumps; } };
80.划分字母区间 给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。例如,字符串 “ababcc” 能够被分为 [“abab”, “cc”],但类似 [“aba”, “bcc”] 或 [“ab”, “ab”, “cc”] 的划分是非法的。 注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 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 28 29 class Solution {public : vector<int > partitionLabels (string s) { vector<int > lastPos (26 , 0 ) ; for (int i = 0 ; i < s.length (); i++) { lastPos[s[i] - 'a' ] = i; } vector<int > result; int start = 0 ; int end = 0 ; for (int i = 0 ; i < s.length (); i++) { end = max (end, lastPos[s[i] - 'a' ]); if (i == end) { result.push_back (end - start + 1 ); start = i + 1 ; } } return result; } };
递归 81,爬楼梯 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
1 2 3 4 5 6 7 8 9 10 11 12 class Solution {public : int climbStairs (int n) { int a = 1 , b = 1 , sum; for (int i = 0 ; i < n - 1 ; i++){ sum = a + b; a = b; b = sum; } return b; } };
82.杨辉三角 给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中,每个数是它左上方和右上方的数的和。
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 >> generate (int numRows) { vector<vector<int >> triangle; for (int i = 0 ; i < numRows; i++) { vector<int > row (i + 1 , 1 ) ; for (int j = 1 ; j < i; j++) { row[j] = triangle[i-1 ][j-1 ] + triangle[i-1 ][j]; } triangle.push_back (row); } return triangle; } };
83.打家劫舍 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
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 : int rob (vector<int >& nums) { int n = nums.size (); if (n == 0 ) return 0 ; if (n == 1 ) return nums[0 ]; vector<int > dp (n, 0 ) ; dp[0 ] = nums[0 ]; dp[1 ] = max (nums[0 ], nums[1 ]); for (int i = 2 ; i < n; i++) { dp[i] = max (dp[i-1 ], dp[i-2 ] + nums[i]); } return dp[n-1 ]; } };
84.完全平方数 给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。 完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是
输入:n = 12 输出:3 解释:12 = 4 + 4 + 4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : int numSquares (int n) { vector<int > dp (n + 1 , INT_MAX) ; dp[0 ] = 0 ; for (int j = 1 ; j <= n; j++) { for (int i = 1 ; i * i <= j; i++) { dp[j] = min (dp[j], dp[j - i * i] + 1 ); } } return dp[n]; } };
85.零钱兑换 给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
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 coinChange (vector<int >& coins, int amount) { vector<int > dp (amount + 1 , amount + 1 ) ; dp[0 ] = 0 ; for (int i = 1 ; i <= amount; i++) { for (int coin : coins) { if (i >= coin) { dp[i] = min (dp[i], dp[i - coin] + 1 ); } } } return dp[amount] > amount ? -1 : dp[amount]; } };
86.单词拆分 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。 注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
输入: s = “leetcode”, wordDict = [“leet”, “code”] 输出: true 解释: 返回 true 因为 “leetcode” 可以由 “leet” 和 “code” 拼接成。
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 : bool wordBreak (string s, vector<string>& wordDict) { unordered_set<string> wordSet (wordDict.begin(), wordDict.end()) ; int n = s.length (); vector<bool > dp (n + 1 , false ) ; dp[0 ] = true ; for (int i = 1 ; i <= n; i++) { for (int j = 0 ; j < i; j++) { if (dp[j] && wordSet.count (s.substr (j, i - j))) { dp[i] = true ; break ; } } } return dp[n]; } };
87.最长递增子序列 给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
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 lengthOfLIS (vector<int >& nums) { int n = nums.size (); if (n == 0 ) return 0 ; vector<int > dp (n, 1 ) ; int maxLen = 1 ; for (int i = 1 ; i < n; i++) { for (int j = 0 ; j < i; j++) { if (nums[j] < nums[i]) { dp[i] = max (dp[i], dp[j] + 1 ); } } maxLen = max (maxLen, dp[i]); } return maxLen; } };
88.乘积最大子数组 给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续 子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
测试用例的答案是一个 32-位 整数。
需要注意到负数的情况
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 maxProduct (vector<int >& nums) { int n = nums.size (); if (n == 0 ) return 0 ; vector<int > maxDP (n) , minDP (n) ; maxDP[0 ] = nums[0 ]; minDP[0 ] = nums[0 ]; int result = nums[0 ]; for (int i = 1 ; i < n; i++) { maxDP[i] = max ({nums[i], maxDP[i-1 ] * nums[i], minDP[i-1 ] * nums[i]}); minDP[i] = min ({nums[i], maxDP[i-1 ] * nums[i], minDP[i-1 ] * nums[i]}); result = max (result, maxDP[i]); } return result; } };
89.分割等和子集 给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
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 class Solution { public : bool canPartition (vector<int >& nums) { int sum = accumulate (nums.begin (), nums.end (), 0 ); if (sum % 2 != 0 ) return false ; int target = sum / 2 ; int n = nums.size (); vector<vector<bool >> dp (n + 1 , vector <bool >(target + 1 , false )); for (int i = 0 ; i <= n; i++) { dp[i][0 ] = true ; } for (int i = 1 ; i <= n; i++) { for (int j = 1 ; j <= target; j++) { dp[i][j] = dp[i-1 ][j]; if (j >= nums[i-1 ]) { dp[i][j] = dp[i][j] || dp[i-1 ][j - nums[i-1 ]]; } } } return dp[n][target]; } };
90.最长有效括号 给你一个只包含 ‘(‘ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号 子串 的长度。
左右括号匹配,即每个左括号都有对应的右括号将其闭合的字符串是格式正确的,比如 “(()())”。
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 class Solution {public : int longestValidParentheses (string s) { int n = s.length (); if (n < 2 ) return 0 ; vector<int > dp (n, 0 ) ; int maxLen = 0 ; for (int i = 1 ; i < n; i++) { if (s[i] == ')' ) { if (s[i-1 ] == '(' ) { dp[i] = (i >= 2 ? dp[i-2 ] : 0 ) + 2 ; } else if (i - dp[i-1 ] > 0 && s[i - dp[i-1 ] - 1 ] == '(' ) { dp[i] = dp[i-1 ] + 2 ; if (i - dp[i-1 ] >= 2 ) { dp[i] += dp[i - dp[i-1 ] - 2 ]; } } maxLen = max (maxLen, dp[i]); } } return maxLen; } };
多维动态规划 91.不同路径 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。 问总共有多少条不同的路径?
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 uniquePaths (int m, int n) { vector<vector<int >> dp (m, vector <int >(n, 0 )); for (int i = 0 ; i < m; i++) { dp[i][0 ] = 1 ; } for (int j = 0 ; j < n; j++) { dp[0 ][j] = 1 ; } for (int i = 1 ; i < m; i++) { for (int j = 1 ; j < n; j++) { dp[i][j] = dp[i-1 ][j] + dp[i][j-1 ]; } } return dp[m-1 ][n-1 ]; } };
92.最小路径和 给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 说明:每次只能向下或者向右移动一步。
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 class Solution {public : int minPathSum (vector<vector<int >>& grid) { int m = grid.size (); int n = grid[0 ].size (); vector<vector<int >> dp (m, vector <int >(n, 0 )); dp[0 ][0 ] = grid[0 ][0 ]; for (int j = 1 ; j < n; j++) { dp[0 ][j] = dp[0 ][j-1 ] + grid[0 ][j]; } for (int i = 1 ; i < m; i++) { dp[i][0 ] = dp[i-1 ][0 ] + grid[i][0 ]; } for (int i = 1 ; i < m; i++) { for (int j = 1 ; j < n; j++) { dp[i][j] = min (dp[i-1 ][j], dp[i][j-1 ]) + grid[i][j]; } } return dp[m-1 ][n-1 ]; } };
93.最长回文子串 给你一个字符串 s,找到 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 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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 class Solution {private : int expandAroundCenter (string& s, int left, int right) { int L = left, R = right; while (L >= 0 && R < s.length () && s[L] == s[R]) { L--; R++; } return R - L - 1 ; } public : string longestPalindrome (string s) { if (s.length () < 2 ) return s; int start = 0 , maxLen = 1 ; for (int i = 0 ; i < s.length (); i++) { int len1 = expandAroundCenter (s, i, i); int len2 = expandAroundCenter (s, i, i + 1 ); int len = max (len1, len2); if (len > maxLen) { maxLen = len; start = i - (len - 1 ) / 2 ; } } return s.substr (start, maxLen); } string longestPalindrome (string s) { int n = s.length (); if (n < 2 ) return s; vector<vector<bool >> dp (n, vector <bool >(n, false )); int start = 0 , maxLen = 1 ; for (int i = 0 ; i < n; i++) { dp[i][i] = true ; } for (int len = 2 ; len <= n; len++) { for (int i = 0 ; i + len - 1 < n; i++) { int j = i + len - 1 ; if (s[i] == s[j]) { if (len == 2 ) { dp[i][j] = true ; } else { dp[i][j] = dp[i+1 ][j-1 ]; } } else { dp[i][j] = false ; } if (dp[i][j] && len > maxLen) { maxLen = len; start = i; } } } return s.substr (start, maxLen); } };
94.最长公共子序列 给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 例如,”ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。 两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
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 longestCommonSubsequence (string text1, string text2) { int m = text1.l ength(); int n = text2.l ength(); vector<vector<int >> dp (m + 1 , vector <int >(n + 1 , 0 )); for (int i = 1 ; i <= m; i++) { for (int j = 1 ; j <= n; j++) { if (text1[i-1 ] == text2[j-1 ]) { 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]; } };
95.编辑距离 给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操作: 插入一个字符 删除一个字符 替换一个字符
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 58 59 60 61 62 class Solution {public : int minDistance (string word1, string word2) { int m = word1.l ength(); int n = word2.l ength(); vector<vector<int >> dp (m + 1 , vector <int >(n + 1 , 0 )); for (int j = 1 ; j <= n; j++) { dp[0 ][j] = j; } for (int i = 1 ; i <= m; i++) { dp[i][0 ] = i; } for (int i = 1 ; i <= m; i++) { for (int j = 1 ; j <= n; j++) { if (word1[i-1 ] == word2[j-1 ]) { dp[i][j] = dp[i-1 ][j-1 ]; } else { dp[i][j] = min ({ dp[i-1 ][j] + 1 , dp[i][j-1 ] + 1 , dp[i-1 ][j-1 ] + 1 }); } } } return dp[m][n]; } };
技巧 96.只出现一次的数字 给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : int singleNumber (vector<int >& nums) { int result = 0 ; for (int num : nums) { result ^= num; } return result; } };
97.多数元素 给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/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 25 26 27 28 29 class Solution {public : int majorityElement (vector<int >& nums) { int candidate = nums[0 ]; int count = 1 ; for (int i = 1 ; i < nums.size (); i++) { if (nums[i] == candidate) { count++; } else { count--; if (count == 0 ) { candidate = nums[i]; count = 1 ; } } } return candidate; } };
98.颜色分类 给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。 我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 必须在不使用库内置的 sort 函数的情况下解决这个问题。
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 : void sortColors (vector<int >& nums) { int left = 0 ; int mid = 0 ; int right = nums.size () - 1 ; while (mid <= right) { if (nums[mid] == 0 ) { swap (nums[left], nums[mid]); left++; mid++; } else if (nums[mid] == 1 ) { mid++; } else { swap (nums[mid], nums[right]); right--; } } } };
99.下一个排列 实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。 如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。 要求:必须原地修改,只使用常数额外空间。
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 {public : void nextPermutation (vector<int >& nums) { int n = nums.size (); int i = n - 2 ; while (i >= 0 && nums[i] >= nums[i+1 ]) { i--; } if (i >= 0 ) { int j = n - 1 ; while (j >= 0 && nums[j] <= nums[i]) { j--; } swap (nums[i], nums[j]); } reverse (nums.begin () + i + 1 , nums.end ()); } };
100.寻找重复数 给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。 假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。 你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(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 32 33 34 35 36 37 class Solution {public : int findDuplicate (vector<int >& nums) { int left = 1 ; int right = nums.size () - 1 ; while (left < right) { int mid = left + (right - left) / 2 ; int count = 0 ; for (int num : nums) { if (num <= mid) { count++; } } if (count > mid) { right = mid; } else { left = mid + 1 ; } } return left; } };