文章目录
1.电话号码的字母组合(dfs.回溯法.深度优先,bfs.广度优先)
ps:广度优先用队列,深度优先用栈或者递归
方法一:深度优先
回溯法,用到了递归。
思路:
①digits遍历外层。
②umo[digits[i]] 数字对应的字母组成的string。
③对umo[digits[i]]的string逐个字母遍历加入到curString中。
④再把curString和i和digits代入backtrace()中回溯(递归)。
⑤再把上一步curString ,pop掉再进入到下一次遍历中。
vector<string>ans;
unordered_map<char, string>uom = {
{
'2',"abc"},
{
'3',"def"},
{
'4',"ghi"},
{
'5' ,"jkl"},
{
'6',"mno"},
{
'7',"pqrs"},
{
'8',"tuv"},
{
'9',"wxyz"}
};//电话数字对应字母表
void backtrack(string digits, int i, string curString)
{
if (i == digits.size())//因为是i+1到的这里,所以size不用减1,这里是指到树的叶子了
ans.push_back(curString);
else
{
for (int k = 0; k < uom[digits[i]].size(); k++)
{
curString.push_back(uom[digits[i]][k]);//一层一层加字母
backtrack(digits, i + 1, curString);//回溯 递归
curString.pop_back();//递归完之后要pop
}
}
}
vector<string> letterCombinations(string digits) {
if (digits.empty())
return ans;
string curString;
backtrack(digits, 0, curString);
return ans;
}
方法二:广度优先
思路:多层循环嵌套
①一开始先在队列里push一个空串(为了让最内层循环能进行,并且让单个字符能变成字符串)。
②最外层循环是层数,用来遍历digits。
③往里一层循环是队列长度,用来遍历队列(由于队列长度会变,所以在进行循环之前应当设一个临时变量来保存队列长度)这一层循环里要进行取头和出队操作。
④最里层是digits[i]所对应的map表中的string长度。这一层循环里要利用外一层的取头的值,来循环合并字符串加入到队列中。
queue<string>q;
unordered_map<char, string>uom = {
{
'2',"abc"},
{
'3',"def"},
{
'4',"ghi"},
{
'5' ,"jkl"},
{
'6',"mno"},
{
'7',"pqrs"},
{
'8',"tuv"},
{
'9',"wxyz"}
};
//bfs广度优先算法
vector<string> letterCombinations(string digits) {
vector<string>ans;
if (digits.size() == 0)
return ans;
q.push("");//为了让初始的时候循环能够实现取头和出队
for (int i = 0; i < digits.size(); i++)//层数
{
int s = q.size();//因为队列的长度会变
for (int j = 0; j<s; j++)//队列长度
{
string curString = q.front();//取头
q.pop();//出队
for (int k = 0; k < uom[digits[i]].size(); k++)//数字代表的字母个数
{
q.push(curString + uom[digits[i]][k]);
}
}
}
while (!q.empty())//这里队列长度会变的,不要老是忘记,别用for了.......
{
ans.push_back(q.front());
q.pop();
}
return ans;
}
这题的深度优先和广度优先的运行时间和内存消耗其实差不多
2.删除链表的倒数第N个节点(减少运行时间消耗)
正常方法:需扫描两趟链表,第一趟得到链表的长度,第二遍删除节点(a->next=a->next->next就可以把a的下一个结点删了)
巧妙方法:只需扫描一遍链表,利用fast和slow,fast比slow快n,当fast指向空的时候,slow->next就可以删了
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {
}
};
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode *newHead = new ListNode(0);//建一个新的头节点,可以在链表只有一个数时把整个链表都删了
newHead->next = head;
ListNode *slow = newHead, *fast = newHead->next;
while (n--) fast = fast->next;//快的要比慢的快n,fast=fast->next是把指针往后移
while (fast) {
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;//改变指向next的位置
return newHead->next;
}//n为倒数第n个节点
int main()
{
ListNode* test = new ListNode(0);
test->next = new ListNode(1);
test->next->next = new ListNode(2);
test->next->next->next = new ListNode(3);
test->next->next->next->next = new ListNode(4);
removeNthFromEnd(test, 2);//移除倒数第二个
while (test!= NULL)
{
cout << test->val<<endl;
test = test->next;
}
//结果为0124
return 0;
}
3.合并两个有序链表(递归)
方法一:直接遍历两个指针,把前面较小的插入到新的链表ans中,空间用太多了,这个方法不好
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {
}
ListNode(int x) : val(x), next(nullptr) {
}
ListNode(int x, ListNode *next) : val(x), next(next) {
}
};
ListNode* chooselist(ListNode*& l1, ListNode*& l2) {
if (l1 == NULL && l2 == NULL)
{
return NULL;
}
else if (l1 == NULL)
{
ListNode* temp = l2;
l2 = l2->next;
return temp;
}
else if (l2 == NULL||l1->val < l2->val)//取小,l1小
{
ListNode* temp = l1;
l1 = l1->next;
return temp;
}
else//l2小
{
ListNode* temp = l2;
l2 = l2->next;
return temp;
}
}
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* first = chooselist(l1, l2);
if (first == NULL)
return NULL;
ListNode* ans = new ListNode(first->val);
ListNode*temp = ans;
while (l1 != NULL || l2 != NULL)
{
temp->next = new ListNode(chooselist(l1, l2)->val);
temp = temp->next;
}
return ans;
}
int main()
{
ListNode* l1 = new ListNode(1,new ListNode(2,new ListNode(4)));
ListNode* l2 = new ListNode(1, new ListNode(3, new ListNode(4)));
ListNode* ans = mergeTwoLists(l1, l2);
//ListNode*l2 = new ListNode(0);
//ListNode* ans=mergeTwoLists(NULL, l2);
while (ans!= NULL)
{
cout << ans->val<<endl;
ans = ans->next;
}
return 0;
}
方法二:是方法一的改进(迭代法)
用新建头节点来避免直接增加整个链表的空间,最后输出的时候用 return list->next输出答案,十分方便
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {
}
ListNode(int x) : val(x), next(nullptr) {
}
ListNode(int x, ListNode *next) : val(x), next(next) {
}
};
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* pre = new ListNode(-1);
ListNode* temp = pre;
while (l1 != NULL && l2 != NULL)
{
if (l1->val < l2->val)//l1小
{
temp->next = l1;
l1 = l1->next;
temp = temp->next;
}
else
{
temp->next = l2;
l2 = l2->next;
temp = temp->next;
}
}
if (l1 == NULL)
temp->next = l2;
else
temp->next = l1;
return pre->next;
}
int main()
{
ListNode* l1 = new ListNode(1,new ListNode(2,new ListNode(4)));
ListNode* l2 = new ListNode(1, new ListNode(3, new ListNode(4)));
ListNode* ans = mergeTwoLists(l1, l2);
//ListNode*l2 = new ListNode(0);
//ListNode* ans=mergeTwoLists(NULL, l2);
while (ans!= NULL)
{
cout << ans->val<<endl;
ans = ans->next;
}
return 0;
}
方法三:递归法
思路:其实就是套娃…然后return最外层就是答案了
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == NULL)
return l2;
else if (l2 == NULL)
return l1;
else if (l1->val < l2->val)//l1小
{
l1->next = mergeTwoLists(l1->next, l2);
return l1;
}
else
{
l2->next = mergeTwoLists(l1,l2->next);
return l2;
}
}
ps:方法二迭代法和方法三递归法的运行时间、内存消耗几乎是一样的
4.合并K个升序链表(上一题的升级版,用到了分治法)
思路:上一题有两个两个合并的升序链表,所以这题k个也可以化为k/2个两个合并的升序链表。
这里两个两个合并的话,要用到队列(栈也行),出两个,进一个合并好的,最后剩一个,就是答案
代码里的:queue<ListNode*>q(deque<ListNode*>(lists.begin(), lists.end()));十分重要!!!!
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {
}
ListNode(int x) : val(x), next(nullptr) {
}
ListNode(int x, ListNode *next) : val(x), next(next) {
}
};
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* pre = new ListNode(-1);
ListNode* temp = pre;
while (l1 != NULL && l2 != NULL)
{
if (l1->val < l2->val)//l1小
{
temp->next = l1;
l1 = l1->next;
temp = temp->next;
}
else
{
temp->next = l2;
l2 = l2->next;
temp = temp->next;
}
}
if (l1 == NULL)
temp->next = l2;
else
temp->next = l1;
return pre->next;
}
//分治法
ListNode* mergeKLists(vector<ListNode*>& lists) {
int listSize = lists.size();
if (listSize == 0)
return NULL;
else if (listSize == 1)
return lists.front();
queue<ListNode*>q(deque<ListNode*>(lists.begin(), lists.end()));//这一步是将vector转queue,注意格式
while (q.size()>=2)
{
ListNode* l1 = q.front();
q.pop();
ListNode* l2 = q.front();
q.pop();
q.push(mergeTwoLists(l1, l2));
}
return q.front();
}
5.两两交换链表中的节点(画图法)
ps:遇到交换的题,把图画出来就很清楚了
(这题用了四个标识符来完成交换)
思路看图
ListNode* swapPairs(ListNode* head) {
if (head == NULL || head->next == NULL)
return head;
ListNode* dummyhead=new ListNode(-1);//加一个头节点
dummyhead->next = head;
ListNode* temp = dummyhead, *swap1 = head;
//ListNode* temp = dummyhead,* swap1 = head, *swap2=head->next,*later=swap2->next;//不能这么建,因为可能总的节点数不够三个
while (swap1 != NULL && swap1->next != NULL)
{
ListNode*swap2=swap1->next,*later=swap2->next;
swap1->next->next = swap1;
swap1->next = later;
temp->next = swap2;
temp = swap1;
swap1 = later;
}
return dummyhead->next;
}
int main()
{
ListNode* list = new ListNode(1);
list->next = new ListNode(2);
list->next->next = new ListNode(3);
list->next->next->next = new ListNode(4);
list->next->next->next->next = new ListNode(5);
ListNode* ans = swapPairs(list);
while (ans!= NULL)
{
cout << ans->val<<endl;
ans = ans->next;
}
return 0;
}
6.括号生成(画图法、递归回溯)
判断什么时候用递归回溯:如果有一个问题,你感觉如果不穷举就无法知道答案,就可以开始回溯了。
思路:先找规律画图,然后根据图来找代码规律,一看二叉树就可以递归了,递归条件其实看二叉树的前三行就好了,然后,输出是当左右括号都输出完的时候。
(二叉树的每一层就是递归里的一次函数执行,if(left>0)是左子树,if(righr>left)是右子树,递归的结果输出顺序是左中右即中序的,把cur’插入树中用的是dfs即深度优先遍历)
回溯算法核心的思想是深度优先遍历。
#include <iostream>
using namespace std;
#include <bits/stdc++.h>
vector<string>ans;
void dfs(int left, int right, string cur)
{
if (left == 0 && right == 0)
{
ans.push_back(cur);
return;
}
if (left > 0)
dfs(left - 1, right, cur + '(');
if (right > left)//当未插入的right的值比left值大时才插入右括号
dfs(left, right - 1, cur + ')');
}
vector<string> generateParenthesis(int n) {
if (n < 1)
return ans;
string cur = "";
dfs(n, n, cur);
return ans;
}
int main()
{
generateParenthesis(3);
return 0;
}
7.全排列(回溯法)
答案:
法一:
vector<vector<int>> permute(vector<int>& nums) {
sort(nums.begin(), nums.end());//必须要有这一步,while那里的函数使用的前提条件就是数组必须是有序的。
int temp[50];
for (int i = 0; i < nums.size(); i++)
temp[i] = nums[i];//因为vector不能用那个函数,只有数组才能用,所以用了个临时数组来while循环
vector<vector<int>>ans;
do {
for (int i = 0; i < nums.size(); i++)
{
nums[i] = temp[i];
ans.push_back(nums);
}
} while (next_permutation(temp,temp+nums.size()));//全排列
}
法二:优化了一下
vector<vector<int>> permute(vector<int>& nums) {
sort(nums.begin(), nums.end());
do {
result.push_back(nums);
}while (next_permutation(nums.begin(), nums.end()));
return result;
}
本来以为会超时,但其实执行用时也还好
全排列格式:
sort();//必须先排序,否则解不齐全
do{
内部即是数组全排列的每次的元素情况
}while(next_permutation(数组名,数组名+数组元素个数))
//大概元素个数在15以下才能使用这个方法。
另一例题
例题的解:
ps:数少的时候,偷懒可以这样写…(如果有些题目要全排序,又要剪枝的话,这个算法是做不到剪枝的,就会有很多无用的步骤,使代码运行超时,所以最好还是用回溯算法)
回溯法(这篇讲得挺好):https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-xiang-jie-by-labuladong-2/
(这篇文章应该是用的Java来解,评论里有个c++的,如下:)
主要图解:
非常经典的回溯递归:
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> res; //全局变量存储答案
// 记录「路径」
vector<int> track; //全局变量存储路径
backtrack(nums, track, res);
return res;
}
void backtrack(vector<int>& nums, vector<int>& track, vector<vector<int>>& res) {
if (track.size() == nums.size()) {
res.push_back(track);
return;
}
for (int i = 0; i < nums.size(); ++i) {
//if (track.contains(nums[i]))
vector<int>::iterator iter = find(track.begin(), track.end(), nums[i]);
if(iter != track.end())
continue;
track.push_back(nums[i]);
backtrack(nums, track, res);
track.pop_back();
}
}
};