贪心法:遵循某种规律,不断贪心的选取当前最优策略的算法设计方法。
例如:有1元、5元、10元,20元,100元,200的钞票无穷多张。现在使用这些钞票支付X元,最少需要多少张?
思路:尽量用面额较大的金额,所以可以将钞票排一下顺序,然后计算出每个大额钞票最多能用几张,依次算出张数进行叠加
#include<stdio.h>
int main()
{
int RMB[]={200,100,20,10,5,1};
int NUM=6;
int X=628;
int count_RMB=0;
for(int i = 0;i<NUM;i++)
{
int use = X/RMB[i];
count_RMB+=use;
X=X-use*RMB[i];
printf("需要面额为%d的%d张,",RMB[i],use);
printf("剩余需要支付金额%d.\n",X);
}
printf("总共需要%d张\n",count_RMB);
return 0;
}
LEECODE题目练习
455:分饼干问题
设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
注意:
你可以假设胃口值为正。
一个小朋友最多只能拥有一块饼干。
示例 1:
输入: [1,2,3], [1,1]
输出: 1
解释:
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。
示例 2:
输入: [1,2], [1,2,3]
输出: 2
解释:
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2.
解法:
分析:1.当某个孩子可以被多个饼干满足时,是否需要优先用某个饼干满足这个孩子?
2.当某个饼干可以满足多个孩子时,是否需要优先满足某个孩子?
思路:
1.首先对需求因子数组g和饼干大小S进行从小到大的排序
2.按照从小到大的顺序使用各个饼干尝试是否可以满足某个孩子,每个饼干只尝试1次,若尝试成功,则换下一个孩子尝试;直到发现没更多的孩子或者没更多的饼干,循环结束。
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
std::sort(g.begin(),g.end());
std::sort(s.begin(),s.end());
int child = 0;
int cookie = 0;
while(child<g.size()&&cookie<s.size()){//当孩子或饼干同时均未尝试完时
if(g[child]<=s[cookie]){
child++; //该糖果满足了孩子,孩子指针向后移动
}
cookie++; //无论成功或失败,每个糖果只尝试一次,cookie向后移动;
}
return child;
}
};
测试程序
int main()
{
Solution solve;
std::vector<int> g;
std::vector<int> s;
g.push_back(5);
g.push_back(10);
g.push_back(2);
g.push_back(9);
g.push_back(15);
g.push_back(9);
s.push_back(6);
s.push_back(1);
s.push_back(20);
s.push_back(3);
s.push_back(8);
printf("%d\n",solve.findContentChildren(g,s));
return 0;
}
输出:
376:摇摆序列
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。
例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。
示例 1: 输入: [1,7,4,9,2,5] 输出: 6
解释: 整个序列均为摆动序列。
示例 2: 输入: [1,17,5,10,13,15,10,5,16,8] 输出: 7
解释: 这个序列包含几个长度为 7 摆动序列,其中一个可为[1,17,10,13,10,16,8]。
示例 3: 输入: [1,2,3,4,5,6,7,8,9] 输出: 2
贪心规律:
当序列有一段连续的递增(或递减时,为形成摇摆子序列,我们只需要保留这段连续的递增(或递减)的首尾元素,这样更可能使得尾部的后一个元素成为摇摆子序列的下一个元素。
class Solution {
public:
int wiggleMaxLength(std::vector<int>& nums) {
if(nums.size()<2)
{
return nums.size();
}
static const int BEGIN = 0;
static const int UP=1;
static const int DOWN=2;
int STATE=BEGIN;
int max_length = 1;
for(int i = 1;i<nums.size();i++)
{
switch(STATE){
case BEGIN:
if(nums[i-1]<nums[i]){
STATE=UP;
max_length++;
}
else if(nums[i-1]>nums[i])
{
STATE=DOWN;
max_length++;
}
break;
case UP:
if(nums[i-1]>nums[i])
{
STATE=DOWN;
max_length++;
}
break;
case DOWN:
if(nums[i-1]<nums[i])
{
STATE=UP;
max_length++;
}
break;
}
}
return max_length;
}
};
402:移除K个数字
给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。
注意: num 的长度小于 10002 且 ≥ k。 num 不会包含任何前导零。
示例 1 :
输入: num = "1432219", k = 3 输出: "1219"
解释: 移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。
示例 2 :
输入: num = "10200", k = 1 输出: "200"
解释: 移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。
示例 3 : 输入: num = "10", k = 2 输出: "0"
解释: 从原数字移除所有的数字,剩余为空就是0。
思路:利用贪心算法找出最优解,依次将字符压入栈,若num[i]<top,则弹出栈 k--;
难点:
1.当遍历完所有字符后,k>0怎么办?说明前面都不满足“比后面数字大”的条件,入栈的都是想等或者比后继小的数,依次弹出栈直到k=0;
2.当字符为‘0’时如何处理?如果此刻栈中没有元素(因为比当前的0大),说明0被挪到首位,则continue,不用入栈;如果栈中有元素,这说明0位于整数后,则push;
class Solution {
public:
string removeKdigits(string num, int k) {
std::vector<int> SS;
std::string result="";
SS.push_back(num[0]-'0');
for(int i = 1;i<num.length();i++)
{
int number = num[i]-'0';
while(SS.size()!=0 && SS[SS.size() - 1] > number && k > 0)
{
SS.pop_back();
k--;
}
if(number==0)
{
if(SS.size()==0)
{
continue;
}
else
{
SS.push_back(number);
}
}
else
{
SS.push_back(number);
}
}
while(SS.size()!=0 && k>0)
{
SS.pop_back();
k--;
}
for(int i =0;i<SS.size();i++)
{
result.append(1,SS[i]+'0');
}
if(result=="")
{
result="0";
}
return result;
}
};
55.跳跃游戏
给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。
示例 1: 输入: [2,3,1,1,4] 输出: true
解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
示例 2: 输入: [3,2,1,0,4] 输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。
算法思路:
1.求从第i位置最远可跳至第index[i]位置,根据从第i位置最远可跳nums[i]步:index[i]=nums[i]+i;
2.初始化:
1)设置变量jump代表当前所处的位置,初始化为0;
2)设置变量maxindex代表从第0位置至第jump位置这个过程中最远可到达的位置,初始化为max_index[0]。
3利用jump扫描index数组,直到jump达到index数组尾部或超过max_index,扫描过程中,更新max_index
4若最终jump为数组长度,则返回true,否则返回false。
class Solution {
public:
bool canJump(vector<int>& nums) {
std::vector<int> index;
for(int i =0;i<nums.size();i++)
{
index.push_back(i+nums[i]);
}
int max_index=index[0];//max_index是jump在最多可以走的步数;
int jump = 0; //超级马里奥
while(jump<index.size()&&jump<=max_index) //如果max_index=nums.size-1;说明jump是可以走到底的;
{
if(max_index<index[jump])
{
max_index=index[jump];
}
jump++;
}
if(jump==index.size())
{
return true;
}
return false;
}
};
45跳跃游戏2
给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例:
输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
这道题的最优解是可以时间复杂度优化到 O(n) 的,那就是采用贪心算法,我们从左边的起点开始跳跃的时候,我们应该跳跃到哪一个点比较合适呢?,显然,每次都跳跃最大长度的话,是不行的。例如对于上面 arr = {2, 3, 1, 1, 4, 2, 1} 这个例子,刚开 arr[0] = 2,那么我们可以跳到 arr[1] = 3 或者 arr[2] = 1 上,显然,我们跳跃 arr[1] = 3 会更好一点。如图(图片来源于网络)
接着同样的道理,我们可以从 arr[1] = 3 这个位置开始跳跃,它可以跳跃到 arr[2] = 1, arr[3] = 1, arr[4] = 4 这三个位置,显然,我们跳到 arr[4] = 4 这个位置好一点,如图(图片来源于网络)
也就是说,我们要跳跃的那个点,可以使得上一次 + 下一次的跳跃总距离最远。代码如下
int jump(vector<int> nums) {
if(nums.size() < 2)
return 0;
int sum = 0;
int end = 0; // 能跳到的最远距离
int max = 0; // 下一步可以跳到的最远距离
for(int i = 0; i < nums.size() - 1; i++){
max = max(max, i + nums[i]);
// 更新当前点
if(i == end){
end = max;
sum++;
}
}
return sum;
}
452射击气球
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在104个气球。
一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
Example:
输入:
[[10,16], [2,8], [1,6], [7,12]]
输出:2
解释:
对于该样例,我们可以在x = 6(射爆[2,8],[1,6]两个气球)和 x = 11(射爆另外两个气球)。
1.对于某个气球,至少需要使用1个弓箭将他、它击穿;
2.在这只气球将其击穿的同时,尽可能击穿其他更多的气球!
思路:
1.对各个气球进行排序,按照气球的左端点从小到大排序。
2.遍历气球数组,同时维护一个射击区间,在满足可以将当前气球射穿的情况下,尽可能击穿更多的气球,每击穿一个新的气球,更新一次射击区间
3.如果新的气球没办法被击穿了,则需要一名弓箭手,即维护一个新的射击区间,随后,遍历气球数组
利用vector实现
bool cmp(std::vector<int> &a,std::vector<int> &b)
{
return a[0]<b[0];
}
class Solution {
public:
int findMinArrowShots(vector<vector<int>>& points) {
if(points.size()==0)
{
return 0;
}
std::sort(points.begin(),points.end(),cmp);
int shot_num=1;
int shot_begin=points[0][0];
int shot_end=points[0][1];
for(int i=0;i<points.size();i++)
{
if(points[i][0]<=shot_end)
{
shot_begin=points[i][0];
if(points[i][1]<shot_end)
{
shot_end=points[i][1];
}
}
else
{
shot_num++;
shot_begin=points[i][0];
shot_end=points[i][1];
}
}
return shot_num;
}
};
利用pair实现
#include<algorithm>
#include <vector>
#include<stdio.h>
bool cmp (const std::pair<int,int> &a,const std::pair<int,int> &b)
//pair是将2个数据组合成一组数据,当需要这样的需求时就可以使用pair,
{
return a.first<b.first; //不必考虑左端点相同的排序;
}
class Solution {
public:
int findMinArrowShots(std::vector<std::pair<int,int> >& points) {
if(points.size()==0)
{
return 0;
}
std::sort(points.begin(),points.end(),cmp);
int shoot_num=1;
int shoot_begin=points[0].first;
int shoot_end = points[0].second;
for(int i=1;i<points.size();i++)
{
if(shoot_end>=points[i].first)
{
shoot_begin=points[i].first;
if(shoot_end>points[i].second)
{
shoot_end=points[i].second;
}
}
else{
shoot_num++;
shoot_begin = points[i].first;
shoot_end = points[i].second;
}
}
return shoot_num;
}
};
int main()
{
std::vector<std::pair<int,int>> points;
points.push_back(std::make_pair(10,16));
points.push_back(std::make_pair(2,8));
points.push_back(std::make_pair(1,6));
points.push_back(std::make_pair(7,12));
Solution solve;
printf("%d\n",solve.findMinArrowShots(points));
return 0;
}
附加题目:
poj2431:最优加油方法
一条公路上,有一个起点和一个终点,之前有n个加油站;已知:
1.加油站到终点的距离d和各个加油站可以加油的量l;
2.起点位置到终点距离为L,起始油箱油量为P;
假设一个单位汽油能走一个单位距离,油箱没有上线,最少加几次有可以从起点开到终点?(无法到达终点,返回-1)
思考:何时加油?油用光时,在哪个站加油?油量最多的加油站最合适
利用最大堆来存储油量;
思路:
1.设置一个最大堆,用来存储经过加油站的汽油量;
2.按照起点到终点的方向,遍历各个加油站之间的距离
3.每次需要走两个加油站之间的距离d,如果发现汽油不够走距离d时,从最大堆中取出一个油量添加,知道可以足够走距离d
4.如果把最大堆的汽油添加仍然不够行进距离d,则无法达到终点;
5.当前油量P减少d;
6.将当前加油站油量添加至最大堆
#include<stdio.h>
#include<vector>
#include<algorithm>
#include<queue>
bool cmp(const std::pair<int,int> &a,const std::pair<int,int> &b){
return a.first>b.first;
}
int get_minmum_stop(int L,int P, std::vector<std::pair<int,int>> &stop)
/*L:起点到终点的距离
P起点初始的油量
stop:pair<加油站至终点的距离,加油站汽油箱>
*/
{
std::priority_queue<int> Q; //存储油量的最大值
int result = 0;//记录加过几次油的变量
stop.push_back(std::make_pair(0,0));//将终点作为一个停靠点,添加进stop;
std::sort(stop.begin(),stop.end(),cmp);//按照距离终点的距离排序
for(int i = 0; i<stop.size();i++)
{
int dis = L-stop[i].first;
while(!Q.empty() && P<dis)
{
P+=Q.top();
Q.pop();
result++;
}
if(Q.empty()&&P<dis)
{
return -1;
}
P = P-dis;
L=stop[i].first;
Q.push(stop[i].second);
}
return result;
}
int main()
{
std::vector<std::pair<int,int>> stop;
int N;
int L;
int P;
int distance;
int fuel;
scanf("%d",&N);
for(int i=0;i<N;i++)
{
scanf("%d %d",&distance,&fuel);
stop.push_back(std::make_pair(distance,fuel));
}
scanf("%d %d",&L,&P);
printf("%d\n",get_minmum_stop(L,P,stop));
return 0;
}
输出:
预备知识:
图的表示与构造
使用map构造邻接表表示的图,map定义为以string为key(代表图的顶点),vector<string>为value(代表图的各个顶点临接的顶点)。
将beginWord push进入wordList。遍历wordList,对任意两个单词wordList[i]与wordList[j],若wordList[i]与wordList[j]只相差1个字符,则将其相连。
bool connect(const std::string &word1,const std::string &word2)
{
int cnt =0;
for(int i =0 ;i<word1.length();i++)
{
if(word1[i]!=word2[i])
{
cnt++;
}
}
return cnt ==1;
}
void construct_graph(std::string &beginWord,
std::vector<std::string>& wordList,
std::map<std::string,std::vector<std::string>> &graph)
{
wordList.push_back(beginWord);
for(int i = 0;i<wordList.size();i++)
{
graph[wordList[i]]=std::vector<std::string> ();
}
for(int i = 0;i<wordList.size();i++)
{
for(int j = i+1;j<wordList.size();j++)
{
if(connect(wordList[i],wordList[j]))
{
graph[wordList[i]].push_back(wordList[j]);
graph[wordList[j]].push_back(wordList[i]);
}
}
}
}
预备知识2 图的宽度遍历
给定图的起点beginWord,终点endWord,图graph,从beginWord开始宽度优先搜索图graph,搜索过程中记录到达步数;
1.设置搜索队列Q,队列节点为pair<顶点,步数>;设置集合visit,记录搜素过得顶点,将<beginWord,1>添加至队列;
2.只有队列不空,取出队列头部元素:
1)若取出的队列头部元素为endWord,返回到达当前节点的步数;
2)否则拓展该节点,将与该节点相邻的且未添加到visit中的节点与步数同时添加至队列Q,并将拓展节点加入visit;
3.若最终都无法搜索到endWord,返回0
int BFS_graph(std::string &beginWord,std::string &endWord,
std::map<std::string,std::vector<std::string>> &graph)
{
std::queue<std::pair<std::string,int>> Q;
std::set<std::string> visit;
Q.push(std::make_pair(beginWord,1));
visit.insert(beginWord);
while(!Q.empty())
{
std::string node = Q.front().first;
int step = Q.front().second;
Q.pop();
if(node==endWord)
{
return step;
}
const std::vector<std::string> &neighbors = graph[node];
for(int i = 0;i<neighbors.size();i++)
{
if(visit.find(neighbors[i])==visit.end())
{
Q.push(std::make_pair(neighbors[i],step+1));
visit.insert(neighbors[i]);
}
}
}
return 0;
}
最终代码
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
std::map<std::string,std::vector<std::string>> graph;
construct_graph(beginWord,wordList,graph);
return BFS_graph(beginWord,endWord,graph);
}
private:
bool connect(const std::string &word1,const std::string &word2)
{
int cnt =0;
for(int i =0 ;i<word1.length();i++)
{
if(word1[i]!=word2[i])
{
cnt++;
}
}
return cnt ==1;
}
void construct_graph(std::string &beginWord,
std::vector<std::string>& wordList,
std::map<std::string,std::vector<std::string>> &graph)
{
wordList.push_back(beginWord);
for(int i = 0;i<wordList.size();i++)
{
graph[wordList[i]]=std::vector<std::string> ();
}
for(int i = 0;i<wordList.size();i++)
{
for(int j = i+1;j<wordList.size();j++)
{
if(connect(wordList[i],wordList[j]))
{
graph[wordList[i]].push_back(wordList[j]);
graph[wordList[j]].push_back(wordList[i]);
}
}
}
}
int BFS_graph(std::string &beginWord,std::string &endWord,
std::map<std::string,std::vector<std::string>> &graph)
{
std::queue<std::pair<std::string,int>> Q;
std::set<std::string> visit;
Q.push(std::make_pair(beginWord,1));
visit.insert(beginWord);
while(!Q.empty())
{
std::string node = Q.front().first;
int step = Q.front().second;
Q.pop();
if(node==endWord)
{
return step;
}
const std::vector<std::string> &neighbors = graph[node];
for(int i = 0;i<neighbors.size();i++)
{
if(visit.find(neighbors[i])==visit.end())
{
Q.push(std::make_pair(neighbors[i],step+1));
visit.insert(neighbors[i]);
}
}
}
return 0;
}
};
126 单词接龙2
给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
如果不存在这样的转换序列,返回一个空列表。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出:
[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]
示例 2:
输入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
输出: []
解释: endWord "cog" 不在字典中,所以不存在符合要求的转换序列。
来源:力扣(LeetCode)
链接:力扣
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思考:
在建立beginWord与wordList的连接时,若单词表已包含beginWord,按照上题方法,建立图会出现什么问题
在宽度优先搜索时,如何保存宽度搜索时的路径?
1.将普通队列更换为vector实现队列,保存所有的搜索节点,即在pop节点时不会丢弃队头元素,只是移动front指针。
2.在队列节点中增加该节点的前驱节点在队列中的下标信息,可通过该下标找到的是队列中的哪个节点搜索到的当前节点
struct Qitem
{
std::string node;
int parent_pos;
int step;
Qitem(std::string node,int parent_pos,int step):node(node),parent_pos(parent_pos),step(step) {}
};
若起始点与终点有多条路径,如何将多条路径全部搜索出?
到达某一位置可能存在多条路径,使用映射记录到达每个位置的最短需要的步数,新拓展到的位置只要未曾到达或到达步数与最短步数相同,即将该位置添加至队列中,从而存储了从不同前驱到达该位置的情况
void BFS_graph(std::string &beginWord,std::string &endWord,
std::map<std::string,std::vector<std::string>> &graph,
std::vector<Qitem> &Q,//使用vector实现的队列,可保存所有信息
std::vector<int> &end_word_pos)//终点endWord所在队列的位置下标
{
std::map<std::string,int> visit; //单词,步数
int min_step = 0;
Q.push_back(Qitem(beginWord.c_str(),-1,1));
visit[beginWord] = 1;
int front = 0;
while(front != Q.size())
{
const std::string &node = Q[front].node;
int step = Q[front].step;
if(min_step !=0 && step> min_step)
{
break;
}
if(node ==endWord)
{
min_step = step;
end_word_pos.push_back(front);
}
const std::vector<std::string> &neighbors = graph[node];
for(int i = 0;i<neighbors.size();i++)
{
if(visit.find(neighbors[i]) == visit.end() || visit[neighbors[i]]==step+1 )
{
Q.push_back(Qitem(neighbors[i],front,step+1));
visit[neighbors[i]] = step +1;
}
}
front ++;
}
}
图的建立问题修改
bool connect(const std::string &word1,const std::string &word2)
{
int cnt =0;
for(int i =0 ;i<word1.length();i++)
{
if(word1[i]!=word2[i])
{
cnt++;
}
}
return cnt ==1;
}
void construct_graph(std::string &beginWord,
std::vector<std::string>& wordList,
std::map<std::string,std::vector<std::string>> &graph)
{
int has_begin_word = 0;
for(int i = 0; i<wordList.size();i++)
{
if(wordList[i] == beginWord)
{
has_begin_word = 1;
}
graph[wordList[i]] = std::vector<std::string>();
}
for(int i = 0;i<wordList.size();i++)
{
for(int j = i+1;j<wordList.size();j++)
{
if(connect(wordList[i],wordList[j]))
{
graph[wordList[i]].push_back(wordList[j]);
graph[wordList[j]].push_back(wordList[i]);
}
}
if(has_begin_word == 0 && connect(beginWord,wordList[i]))
{
graph[beginWord].push_back(wordList[i]);
}
}
}
遍历搜索路径:
class Solution {
public:
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
std::map<std::string,std::vector<std::string>> graph;
construct_graph(beginWord,wordList,graph);
std::vector<Qitem> Q;
std::vector<int> end_word_pos;
BFS_graph(beginWord,endWord,graph,Q,end_word_pos);
std::vector<std::vector<std::string>> result;
for(int i = 0;i<end_word_pos.size();i++)
{
int pos = end_word_pos[i];
std::vector<std::string> path;
while(pos!=-1)
{
path.push_back(Q[pos].node);
pos = Q[pos].parent_pos;
}
result.push_back(std::vector<std::string>());
for(int j = path.size()-1;j>=0;j--)
{
result[i].push_back(path[j]);
}
}
return result;
}
};
最终代码
struct Qitem
{
std::string node;
int parent_pos;
int step;
Qitem(std::string node,int parent_pos,int step):node(node),parent_pos(parent_pos),step(step) {}
};
bool connect(const std::string &word1,const std::string &word2)
{
int cnt =0;
for(int i =0 ;i<word1.length();i++)
{
if(word1[i]!=word2[i])
{
cnt++;
}
}
return cnt ==1;
}
void BFS_graph(std::string &beginWord,std::string &endWord,
std::map<std::string,std::vector<std::string>> &graph,
std::vector<Qitem> &Q,//使用vector实现的队列,可保存所有信息
std::vector<int> &end_word_pos)//终点endWord所在队列的位置下标
{
std::map<std::string,int> visit; //单词,步数
int min_step = 0;
Q.push_back(Qitem(beginWord.c_str(),-1,1));
visit[beginWord] = 1;
int front = 0;
while(front != Q.size())
{
const std::string &node = Q[front].node;
int step = Q[front].step;
if(min_step !=0 && step> min_step)
{
break;
}
if(node ==endWord)
{
min_step = step;
end_word_pos.push_back(front);
}
const std::vector<std::string> &neighbors = graph[node];
for(int i = 0;i<neighbors.size();i++)
{
if(visit.find(neighbors[i]) == visit.end() || visit[neighbors[i]]==step+1 )
{
Q.push_back(Qitem(neighbors[i],front,step+1));
visit[neighbors[i]] = step +1;
}
}
front ++;
}
}
void construct_graph(std::string &beginWord,
std::vector<std::string>& wordList,
std::map<std::string,std::vector<std::string>> &graph)
{
int has_begin_word = 0;
for(int i = 0; i<wordList.size();i++)
{
if(wordList[i] == beginWord)
{
has_begin_word = 1;
}
graph[wordList[i]] = std::vector<std::string>();
}
for(int i = 0;i<wordList.size();i++)
{
for(int j = i+1;j<wordList.size();j++)
{
if(connect(wordList[i],wordList[j]))
{
graph[wordList[i]].push_back(wordList[j]);
graph[wordList[j]].push_back(wordList[i]);
}
}
if(has_begin_word == 0 && connect(beginWord,wordList[i]))
{
graph[beginWord].push_back(wordList[i]);
}
}
}
class Solution {
public:
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
std::map<std::string,std::vector<std::string>> graph;
construct_graph(beginWord,wordList,graph);
std::vector<Qitem> Q;
std::vector<int> end_word_pos;
BFS_graph(beginWord,endWord,graph,Q,end_word_pos);
std::vector<std::vector<std::string>> result;
for(int i = 0;i<end_word_pos.size();i++)
{
int pos = end_word_pos[i];
std::vector<std::string> path;
while(pos!=-1)
{
path.push_back(Q[pos].node);
pos = Q[pos].parent_pos;
}
result.push_back(std::vector<std::string>());
for(int j = path.size()-1;j>=0;j--)
{
result[i].push_back(path[j]);
}
}
return result;
}
};
473火柴棍摆正方形
还记得童话《卖火柴的小女孩》吗?现在,你知道小女孩有多少根火柴,请找出一种能使用所有火柴拼成一个正方形的方法。不能折断火柴,可以把火柴连接起来,并且每根火柴都要用到。
输入为小女孩拥有火柴的数目,每根火柴用其长度表示。输出即为是否能用所有的火柴拼成正方形。
示例 1:
输入: [1,1,2,2,2]
输出: true
解释: 能拼成一个边长为2的正方形,每边两根火柴。
示例 2:
输入: [3,3,3,3,4]
输出: false
解释: 不能用所有火柴拼成一个正方形。
注意:
给定的火柴长度和在 0 到 10^9之间。
火柴数组的长度不超过15。
来源:力扣(LeetCode)
链接:力扣
回溯法:
想象正方形4条边为4个通,将每个火柴回溯的位置在每个桶中,在放完n个火柴杆后,检查4个桶中的火柴杆长度和是否相同,相同则返回真,否则返回假;在回溯过程中,如果当前所有可能向后的回溯,都无法满足条件,即递归函数最终返回假。
优化1:n个火柴总和对4取余需要为0,否则返回假。
优化2:火柴杆按照从小到大的顺序排序,先尝试大的减少回溯可能。
优化3:每次放置时,每条边上不可放置超过总和的1/4长度的火柴杆
class Solution {
public:
bool makesquare(vector<int>& nums) {
if(nums.size()<4)
{
return false;
}
int sum = 0;
for(int i = 0;i<nums.size();i++)
{
sum +=nums[i];
}
if(sum % 4)
{
return false;
}
std::sort (nums.rbegin(),nums.rend());
int bucket[4] = {0};
return generate(0,nums,sum/4,bucket);
}
private:
bool generate(int i,std::vector<int>&nums,int target,int bucket[])
{
if(i>=nums.size())
{
return bucket[0] == target && bucket[1] == target
&& bucket[2] == target && bucket[3] == target;
}
for(int j = 0;j<4;j++)
{
if(bucket[j]+nums[i]>target)
{
continue;
}
bucket[j]+=nums[i];
if(generate(i+1,nums,target,bucket))
{
return true;
}
bucket[j]-=nums[i];
}
return false;
}
};
位运算法:
使用位运算法,构造出所有和为target(总和/4)的子集,存储在向量ok_subset中,这些是候选的边组合。
2.遍历所有的ok_subset,两两进行对比,如果ok_set[i]和ok_set[j]进行与运算的结果为0,则说明ok_set[i]和ok_set[j]表示的是无交集的两个集合(没有选择同样的火柴棍),这两个集合可以代表两个同时存在的满足条件的边,将ok_set[i]与ok_set[j]求或,结果存储在ok_half中,它代表所有满足一半结果的情况。
3.遍历所有的ok_half,两两进行对比,如果ok_half[i]和ok_half[j]进行与运算的结果的结果为0,则返回true(说明4个满足条件的边,即可组成正方形);否则返回false.
class Solution {
public:
bool makesquare(vector<int>& nums) {
if(nums.size()<4)
{
return false;
}
int sum = 0;
for(int i = 0;i<nums.size();i++)
{
sum +=nums[i];
}
if(sum % 4)
{
return false;
}
int target = sum/4;
std::vector<int> ok_subset;
std::vector<int> ok_half;
int all = 1<<nums.size(); //2^nums.size()
for(int i = 0;i<all;i++)
{
int sum = 0;
for(int j=0;j<nums.size();j++)
{
if(i&(1<<j))
{
sum += nums[j];
}
}
if(sum == target)
{
ok_subset.push_back(i);
}
}
for(int i = 0;i<ok_subset.size();i++)
{
for(int j = i+1;j<ok_subset.size();j++)
{
if((ok_subset[i] & ok_subset[j])==0)
{
ok_half.push_back(ok_subset[i] | ok_subset[j]);
}
}
}
for(int i = 0;i<ok_half.size();i++)
{
for(int j = i+1;j<ok_half.size();j++)
{
if((ok_half[i] & ok_half[j])==0)
{
return true;
}
}
}
return false;
}
};
407 收集雨水(待学习)
给你一个 m x n 的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。
示例:
给出如下 3x6 的高度图:
[
[1,4,3,1,3,2],
[3,2,1,3,2,4],
[2,3,3,2,3,1]
]
返回 4 。
如上图所示,这是下雨前的高度图[[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]] 的状态。
下雨后,雨水将会被存储在这些方块中。总的接雨水量是4。