leetcode c++(5)(很多道回溯法、深度优先、广度优先、迭代法、递归法、全排列)

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();
        }
    }
};

猜你喜欢

转载自blog.csdn.net/weixin_44575911/article/details/108701591
今日推荐