hot-100-2 medium
4/22
回溯,减枝,滑动窗口,动态规划,马拉车算法。
做对了,可以看,有点难,很难, 经典、很经典
3. 无重复字符的最长子串 第1题 23/4/22 (有点难)
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。第一题
方法一:滑动窗口
class Solution {
public:
int lengthOfLongestSubstring(string s) {
// 哈希集合,记录每个字符是否出现过
unordered_set<char> occ;
int n = s.size();
// 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
int rk = -1, ans = 0;
// 枚举左指针的位置,初始值隐性地表示为 -1
for (int i = 0; i < n; ++i) {
if (i != 0) {
// 左指针向右移动一格,移除一个字符
occ.erase(s[i - 1]);
}
while (rk + 1 < n && !occ.count(s[rk + 1])) {
// 不断地移动右指针
occ.insert(s[rk + 1]);
++rk;
}
// 第 i 到 rk 个字符是一个极长的无重复字符子串
ans = max(ans, rk - i + 1);
}
return ans;
}
};
方法一:网友的精简算法
class Solution {
public:
int lengthOfLongestSubstring(string s) {
//创建桶(数组),设定128个元素对应0-127ASCII码值,全部赋0
vector<int> m(128, 0);
//存最大长度
int maxlen = 0;
//head表示窗口最左边的字母序号:如果出现重复的,比如两个相同的字母a,上一个a在桶里存的m[s[i]]是a+1表示a的下一个位置
//那么第二个a出现时,head就=a+1也就是max(head,m[s[i]]),去除了窗口里上一个a,保证窗口里无重复字母
int head = 0;
//遍历字符串
for (int i = 0; i < s.size(); i++) {
//修改最左边的字母序号head
head = max(head, m[s[i]]);
//当前字母对应的ASCII码桶里存下一个位置(i+1),用于更新head
m[s[i]] = i + 1;
//更新长度
maxlen = max(maxlen, i - head + 1);
}
return maxlen;
}
};
方法三:hashmap
class Solution
{
public:
int lengthOfLongestSubstring(string s)
{
//s[start,end) 前面包含 后面不包含
int start(0), end(0), length(0), result(0);
int sSize = int(s.size());
unordered_map<char, int> hash;
while (end < sSize)
{
char tmpChar = s[end];
//仅当s[start,end) 中存在s[end]时更新start
if (hash.find(tmpChar) != hash.end() && hash[tmpChar] >= start)
{
start = hash[tmpChar] + 1;
length = end - start;
}
hash[tmpChar] = end;
end++;
length++;
result = max(result, length);
}
return result;
}
};
5. 最长回文子串 第23题
给你一个字符串 s,找到 s 中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
官方代码方法一:动态规划
class Solution {
public:
string longestPalindrome(string s) {
int n = s.size();
if (n < 2) {
return s;
}
int maxLen = 1;
int begin = 0;
// dp[i][j] 表示 s[i..j] 是否是回文串
vector<vector<int>> dp(n, vector<int>(n));
// 初始化:所有长度为 1 的子串都是回文串
for (int i = 0; i < n; i++) {
dp[i][i] = true;
}
// 递推开始
// 先枚举子串长度
for (int L = 2; L <= n; L++) {
// 枚举左边界,左边界的上限设置可以宽松一些
for (int i = 0; i < n; i++) {
// 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
int j = L + i - 1;
// 如果右边界越界,就可以退出当前循环
if (j >= n) {
break;
}
if (s[i] != s[j]) {
dp[i][j] = false;
} else {
if (j - i < 3) {
dp[i][j] = true;
} else {
dp[i][j] = dp[i + 1][j - 1];
}
}
// 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
if (dp[i][j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
begin = i;
}
}
}
return s.substr(begin, maxLen);
}
};
方法二:中心扩展On^2,O1
class Solution {
public:
pair<int, int> expandAroundCenter(const string& s, int left, int right) {
while (left >= 0 && right < s.size() && s[left] == s[right]) {
--left;
++right;
}
return {left + 1, right - 1};
}
string longestPalindrome(string s) {
int start = 0, end = 0;
for (int i = 0; i < s.size(); ++i) {
auto [left1, right1] = expandAroundCenter(s, i, i);
auto [left2, right2] = expandAroundCenter(s, i, i + 1);
if (right1 - left1 > end - start) {
start = left1;
end = right1;
}
if (right2 - left2 > end - start) {
start = left2;
end = right2;
}
}
return s.substr(start, end - start + 1);
}
};
方法三:Manacher (马拉车)算法
class Solution {
public:
int expand(const string& s, int left, int right) {
while (left >= 0 && right < s.size() && s[left] == s[right]) {
--left;
++right;
}
return (right - left - 2) / 2;
}
string longestPalindrome(string s) {
int start = 0, end = -1;
string t = "#";
for (char c: s) {
t += c;
t += '#';
}
t += '#';
s = t;
vector<int> arm_len;
int right = -1, j = -1;
for (int i = 0; i < s.size(); ++i) {
int cur_arm_len;
if (right >= i) {
int i_sym = j * 2 - i;
int min_arm_len = min(arm_len[i_sym], right - i);
cur_arm_len = expand(s, i - min_arm_len, i + min_arm_len);
} else {
cur_arm_len = expand(s, i, i);
}
arm_len.push_back(cur_arm_len);
if (i + cur_arm_len > right) {
j = i;
right = i + cur_arm_len;
}
if (cur_arm_len * 2 + 1 > end - start) {
start = i - cur_arm_len;
end = i + cur_arm_len;
}
}
string ans;
for (int i = start; i <= end; ++i) {
if (s[i] != '#') {
ans += s[i];
}
}
return ans;
}
};
On,On
11. 盛最多水的容器 双指针最优方法(已懂)23/4/23
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。
思想:左边柱子小,左指针往右移动,右指针柱子小则往左移。每次取小柱子和距离乘积
方法一:双指针 On O1
class Solution {
public:
int maxArea(vector<int>& height) {
int l = 0, r = height.size() - 1;
int ans = 0;
while (l < r) {
int area = min(height[l], height[r]) * (r - l);
ans = max(ans, area);
if (height[l] <= height[r]) {
++l;
}
else {
--r;
}
}
return ans;
}
};
-------------------------------------------------------------------------------------------------
简洁代码:
class Solution {
public:
int maxArea(vector<int>& height) {
int i = 0, j = height.size() - 1, res = 0;
while(i < j) {
res = height[i] < height[j] ?
max(res, (j - i) * height[i++]):
max(res, (j - i) * height[j--]);
}
return res;
}
};
15. 三数之和 23/4/24,使用双指针法 比哈希法高效一些
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三元组。
方法一:同向双指针+去重 On^2 O1 看懂了! 有点难
解题思路
对数组进行排序,这样可以方便后面跳过重复的元素。 从左到右遍历数组,对于每个元素nums[i],用两个指针left和right分别指向它的右边第一个元素和最后一个元素。 判断nums[i] + nums[left] + nums[right]的值是否等于零,如果是,就把这三个元素加入到结果vec中,并且跳过left和right两边的重复元素,然后让left右移一位,right左移一位;如果不是,就根据它们的值是大于零还是小于零来调整left或right的位置,使得它们更接近零。 最后,重复上述过程,直到i到达数组末尾或者left和right相遇。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>>v1;
sort(nums.begin(),nums.end());
int left,right;
for(int i=0;i<nums.size()-1;i++)
{
if(nums[i]>0)break;
if(i>0&&nums[i]==nums[i-1])continue;
left=i+1,right=nums.size()-1;
while(left<right)
{
int sum=nums[i]+nums[left]+nums[right];
if(sum>0)right--;
else if(sum<0)left++;
else
{
v1.push_back({nums[i],nums[left],nums[right]});
while(left<right&&nums[right]==nums[right-1])right--;
while(left<right&&nums[left]==nums[left+1])left++;
right--,left++;
}
}
}
return v1;
}
};
方法二:哈希解法 (不用掌握,就方法一就行)
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
// 找出a + b + c = 0
// a = nums[i], b = nums[j], c = -(a + b)
for (int i = 0; i < nums.size(); i++) {
// 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
if (nums[i] > 0) {
break;
}
if (i > 0 && nums[i] == nums[i - 1]) { //三元组元素a去重
continue;
}
unordered_set<int> set;
for (int j = i + 1; j < nums.size(); j++) {
if (j > i + 2
&& nums[j] == nums[j-1]
&& nums[j-1] == nums[j-2]) { // 三元组元素b去重
continue;
}
int c = 0 - (nums[i] + nums[j]);
if (set.find(c) != set.end()) {
result.push_back({nums[i], nums[j], c});
set.erase(c);// 三元组元素c去重
} else {
set.insert(nums[j]);
}
}
}
return result;
}
};
17. 电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
19. 删除链表的倒数第 N 个结点 23/4/24 自己做的迭代,
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
方法一:迭代自己写的+网友简化版:(网友的简化版很经典!)
class Solution {
public:
int sum=0;
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(!head)return nullptr;
removeNthFromEnd(head->next,n);
sum++;
if(sum==n+1)//去到被删结点前一个结点去
{
head->next=head->next->next;
}
else if(n==sum)//是处理被删结点是头结点时候,else if执行。直接返回后一个结点
return head->next;
return head;
}
};
---------------------------------------------------------------------------------
class Solution {
public:
int a = 0;
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(head == nullptr){
return head;
}
head->next = removeNthFromEnd(head->next , n);
++a;
if(a == n){
return head->next;
}
return head;
}
};
方法二:双指针 OL O1(双指针,second指针比first指针超前 n 个节点)
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(0, head);
ListNode* first = head;
ListNode* second = dummy;
for (int i = 0; i < n; ++i) {
first = first->next;
}
while (first) {
first = first->next;
second = second->next;
}
second->next = second->next->next;
ListNode* ans = dummy->next;
delete dummy;
return ans;
}
};
方法三:栈OL OL(出栈的第n个即需要被删的)
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(0, head);
stack<ListNode*> stk;
ListNode* cur = dummy;
while (cur) {
stk.push(cur);
cur = cur->next;
}
for (int i = 0; i < n; ++i) {
stk.pop();
}
ListNode* prev = stk.top();
prev->next = prev->next->next;
ListNode* ans = dummy->next;
delete dummy;
return ans;
}
};
方法四:计算链表长度
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;
}
};
22. 括号生成
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
方法一:暴力法。
方法二:回溯法
class Solution {
bool valid(const string& str) {
int balance = 0;
for (char c : str) {
if (c == '(') {
++balance;
} else {
--balance;
}
if (balance < 0) {
return false;
}
}
return balance == 0;
}
void generate_all(string& current, int n, vector<string>& result) {
if (n == current.size()) {
if (valid(current)) {
result.push_back(current);
}
return;
}
current += '(';
generate_all(current, n, result);
current.pop_back();
current += ')';
generate_all(current, n, result);
current.pop_back();
}
public:
vector<string> generateParenthesis(int n) {
vector<string> result;
string current;
generate_all(current, n * 2, result);
return result;
}
};
方法三:按括号序列的长度递归
class Solution {
shared_ptr<vector<string>> cache[100] = {nullptr};
public:
shared_ptr<vector<string>> generate(int n) {
if (cache[n] != nullptr)
return cache[n];
if (n == 0) {
cache[0] = shared_ptr<vector<string>>(new vector<string>{""});
} else {
auto result = shared_ptr<vector<string>>(new vector<string>);
for (int i = 0; i != n; ++i) {
auto lefts = generate(i);
auto rights = generate(n - i - 1);
for (const string& left : *lefts)
for (const string& right : *rights)
result -> push_back("(" + left + ")" + right);
}
cache[n] = result;
}
return cache[n];
}
vector<string> generateParenthesis(int n) {
return *generate(n);
}
};
方法四:枚举选左括号还是右括号
class Solution {
public:
vector<string> generateParenthesis(int n) {
int m = n * 2;
vector<string> ans;
string path(m, 0);
function<void(int, int)> dfs = [&](int i, int open) {
if (i == m) {
ans.emplace_back(path);
return;
}
if (open < n) { // 可以填左括号
path[i] = '(';
dfs(i + 1, open + 1);
}
if (i - open < open) { // 可以填右括号
path[i] = ')';
dfs(i + 1, open);
}
};
dfs(0, 0);
return ans;
}
};
31. 下一个排列 23/4/25(有点味道)
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。
例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。
类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。
而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。
给你一个整数数组 nums ,找出 nums 的下一个排列。
注意:必须 原地 修改,只允许使用额外常数空间。
方法一:(已经很简洁了)
找到的a[ i ],它后面一定全是比a[ i ]小的逆序,所以从后往前找到的第一个比a[ i ]大的数字一定是次大的数a[ j ],最后再反转后面数字的即可。
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());
}
};
34. 在排序数组中查找元素的第一个和最后一个位置23/4/26
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
注意:必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
方法:二分查找(第一个是网友版本)如果全是重复一个target数字时间复杂度就是On
class Solution {
public:
/**
这个时间复杂度,肯定是要用二分法分治的!
注意下这个用例即可:[5,7,8,8,8,10,10]
*/
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> res = {-1,-1};
int left = 0,right = nums.size()-1;
while(left<=right){
int mid = (left+right)/2;
if(nums[mid]==target){
for(int i=mid;i>=left&&nums[mid]==nums[i];i--){
res[0]=i;
}
for(int i=mid;i<=right&&nums[mid]==nums[i];i++){
res[1]=i;
}
return res;
}else if(nums[mid]<target){
left = mid+1;
}else{
right = mid-1;
}
}
return res;
}
};
如下是第二个网友的,连连称赞(二分找边界):如果全是重复一个target数字时间复杂度仍Olog n
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
return {searchLeftOrRightBound(nums, target, "left"), searchLeftOrRightBound(nums, target, "right")};
}
private:
int searchLeftOrRightBound(vector<int>& nums, int target, const string& bound) {
int left = 0, right = nums.size() - 1;
int res = -1;
while (left <= right) {
int mid = (left + right) >> 1;
if (nums[mid] < target) {
left = mid + 1;
}
else if (nums[mid] > target) {
right = mid - 1;
}
else {
res = mid;//重点。保存左,右边界
if (bound == "left") {
right = mid - 1;
}
else if (bound == "right") {
left = mid + 1;
}
else {
// 异常处理段
}
}
}
return res;
}
};
33. 搜索旋转排序数组 二分的思想 有味道!23/4/26
整数数组 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) 的算法解决此问题。
搞懂这个题的精髓在于两个定理这个的代码很简洁,明了。
定理一:只有在顺序区间内才可以通过区间两端的数值判断target是否在其中。
定理二:判断顺序区间还是乱序区间,只需要对比 left 和 right 是否是顺序对即可,left <= right,顺序区间,否则乱序区间。
通过不断的用Mid二分,根据定理二,将整个数组划分成顺序区间和乱序区间,然后利用定理一判断target是否在顺序区间,如果在顺序区间,下次循环就直接取顺序区间,如果不在,那么下次循环就取乱序区间。
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]) {
// left 到 mid 是顺序区间
(target >= nums[left] && target < nums[mid]) ? right = mid - 1 : left = mid + 1;
}
else {
// mid 到 right 是顺序区间
(target > nums[mid] && target <= nums[right]) ? left = mid + 1 : right = mid - 1;
}
}
return -1;
}
};
39. 组合总和
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
官方代码:
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;
}
};
网友的:简洁、清晰、高效的回溯+剪枝写法
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
dfs(candidates, 0, target);
return res;
}
private:
vector<vector<int>> res; // 用于返回的最终结果池
vector<int> list; // 递归中的当前组合
void dfs(const vector<int>& candidates, int cur_index, int target) {
if (target == 0) res.push_back(list);
for (int i = cur_index; i < candidates.size(); i++) {
if (candidates[i] > target) return;
list.push_back(candidates[i]);
dfs(candidates, i, target - candidates[i]);
list.pop_back();
}
}
};
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
ans = []
combine = []
size= len(candidates)
candidates.sort()
def dfs(begin, combine, target):
if target < 0:
return
if target == 0:
ans.append(combine)
return
for idx in range(begin, size):
if target - candidates[idx] < 0:
break
dfs(idx, combine+[candidates[idx]], target - candidates[idx])
dfs(0, combine, target)
return ans
39. 组合总和 没看懂! 很经典需要掌握的
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
class Solution { //击败23%,时间效率很慢
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;
}
};
如下代码:leetcode 的别人的方法 0ms 击败100%,
class Solution {
public:
vector<vector<int>>res;
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
//可重复 组合
vector<int> res1;
dfs(candidates,target,res1,0);
return res;
}
void dfs(vector<int> & candidates,int target,vector<int>&res1,int index)
{
if(target==0)
{
res.push_back(res1);
return ;
}
if(target<0)
{
return ;
}
for(int i=index;i<candidates.size();i++)
{
res1.push_back(candidates[i]);
dfs(candidates,target-candidates[i],res1,i);
res1.pop_back();
}
}
};
回溯算法 + 剪枝(回溯经典例题详解)方法二:和上面一个网友方法一致
网友:参考大佬的解释写的C++版本,时间击败100%,内存击败95.88%
class Solution {
public:
vector<int> cur;
void DFS(int begin,int sum,vector<int>& candidates,int target,vector<vector<int>> &res){
if (sum==target)
{
res.push_back(cur);
return;
}
if (sum>target)
{
return;
}
for (int i = begin; i < candidates.size(); i++)
{
cur.push_back(candidates[i]);
DFS(i,sum+candidates[i],candidates,target,res);
cur.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> res;
DFS(0,0,candidates,target,res);
return res;
}
};
代码随想录 方法三
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {
if (sum == target) {
result.push_back(path);
return;
}
// 如果 sum + candidates[i] > target 就终止遍历
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
sum += candidates[i];
path.push_back(candidates[i]);
backtracking(candidates, target, sum, i);
sum -= candidates[i];
path.pop_back();
}
}
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
result.clear();
path.clear();
sort(candidates.begin(), candidates.end()); // 需要排序
backtracking(candidates, target, 0, 0);
return result;
}
};
46. 全排列
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
方法一:官方代码
class Solution {
public:
void backtrack(vector<vector<int>>& res, vector<int>& output, int first, int len){
// 所有数都填完了
if (first == len) {
res.emplace_back(output);
return;
}
for (int i = first; i < len; ++i) {
// 动态维护数组
swap(output[i], output[first]);
// 继续递归填下一个数
backtrack(res, output, first + 1, len);
// 撤销操作
swap(output[i], output[first]);
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int> > res;
backtrack(res, nums, 0, (int)nums.size());
return res;
}
};
网友 喜刷刷:C++ 简洁、优雅、清晰、时空复杂度性价比最高的回溯法
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
dfs(nums);
return res;
}
private:
vector<vector<int>> res;
vector<int> cur;
void dfs(vector<int>& nums) {
if (cur.size() == nums.size()) {
res.push_back(cur);
return;
}
for (int i = 0; i < nums.size(); i++) {
if (nums[i] != 99) {
int temp = nums[i];
cur.push_back(nums[i]);
nums[i] = 99;
dfs(nums);
nums[i] = temp;
cur.pop_back();
}
}
}
};
class Solution {
public:
vector<vector<int>> permute(vector<int> &nums) {
int n = nums.size();
vector<vector<int>> ans;
vector<int> path(n), on_path(n);
function<void(int)> dfs = [&](int i) {
if (i == n) {
ans.emplace_back(path);
return;
}
for (int j = 0; j < n; ++j) {
if (!on_path[j]) {
path[i] = nums[j];
on_path[j] = true;
dfs(i + 1);
on_path[j] = false; // 恢复现场
}
}
};
dfs(0);
return ans;
}
};
方法三:代码随想录
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking (vector<int>& nums, vector<bool>& used) {
// 此时说明找到了一组
if (path.size() == nums.size()) {
result.push_back(path);
return;
}
for (int i = 0; i < nums.size(); i++) {
if (used[i] == true) continue; // path里已经收录的元素,直接跳过
used[i] = true;
path.push_back(nums[i]);
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
}
vector<vector<int>> permute(vector<int>& nums) {
result.clear();
path.clear();
vector<bool> used(nums.size(), false);
backtracking(nums, used);
return result;
}
};
102. 二叉树的层序遍历 23/5/5
每层的结点单独放在一个vector中。返回 vector<vector>类型的容器的容器
这份代码也可以作为二叉树层序遍历的模板,打十个就靠它了。
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) que.push(root);
vector<vector<int>> result;
while (!que.empty()) {
int size = que.size();
vector<int> vec;
// 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
vec.push_back(node->val);
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
result.push_back(vec);
}
return result;
}
};
方法二:递归
class Solution {
public:
void order(TreeNode* cur, vector<vector<int>>& result, int depth)
{
if (cur == nullptr) return;
if (result.size() == depth) result.push_back(vector<int>());
result[depth].push_back(cur->val);
order(cur->left, result, depth + 1);
order(cur->right, result, depth + 1);
}
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
int depth = 0;
order(root, result, depth);
return result;
}
};