605. Can Place Flowers
给定一组序列,表示了当前的种花情况,再给一个数字,然后判断在现有的情况下还能否种上指定数量的花。
这题我觉得是比较典型的分情况讨论的题,各种可能的情况分清了,就不难了。我的思路就是分情况,然后按照最大限度去“种花”,如果最大限度还不行,那就真的不行了种不了了。
class Solution {
public:
bool canPlaceFlowers(vector<int>& flowerbed, int n)
{
if(!n)
return true;
if(flowerbed.size()==1&&flowerbed[0])
return false;
int i=0,cnt=0;
//简单的分情况讨论,通过当前位置的相邻位置的种花情况决定如何种花可以最大限度的利用土地。
while(i<flowerbed.size()-1)
{
if(flowerbed[i])
i+=2;
else if(!i&&!flowerbed[i]&&!flowerbed[i+1])
flowerbed[i]=1,i+=2,cnt++;
else if(!flowerbed[i-1]&&!flowerbed[i+1])
flowerbed[i]=1,i+=2,cnt++;
else
i++;
if(cnt==n)
return true;
}
int s = flowerbed.size();
if(!flowerbed[s-2]&&!flowerbed[s-1])
cnt++;
return cnt == n;
}
};
70. Climbing Stairs
经典的递归题目,给定总楼梯数,一次只能往上爬一格或者两格,求有多少种方式可以爬到楼顶。本来是几行递归就能解决的题目,但这题它好在它卡了测试时间,使得我们需要通过一些剪枝操作才能满足要求。我也是通过这道题,才比较有效地了解如何做剪枝优化。
▲任何可以用递归求解的问题我们都可以画出其题解对应的递归树,树的节点根据不同的题表示了不用的含义。普通的递归操作可能会重复计算或者重复考虑一些我们已经计算或者考虑过的情况,这种重复是冗余的没必要的,所以我们可以通过去除这些重复的步骤来优化我们的算法。常用的方法是【记忆化搜索】,即每次计算或者考虑完一种情况后我们记录本次情况的结果,如果往下的递归过程中还有碰到这种情况,我们可以直接用以前得到的结果,不必再重新计算。(但这需要各种情况前后不互相影响)。【有时难以看出来的话我们可以自己画递归树来决定要如何剪枝】。
class Solution {
public:
int solve(int n,vector<int>& m)
{
if(!n)
return 1;
if(n<0)
return 0;
if(m[n]) //已经得到结果的情况直接用结果
return m[n];
m[n] = solve(n-1,m)+solve(n-2,m);
return m[n];
}
int climbStairs(int n)
{
vector<int> m(n+1,0); //记忆化搜索数组
return solve(n,m);
}
};
▲二叉树的三种遍历,知其二推其一。这也是我觉得很重要的要掌握的知识。【知道中序遍历和先序遍历可以推出后序遍历,知道中序遍历和后序遍历可以退出先序遍历,即这两种情况唯一确定了一棵二叉树。但是知道先序遍历和后序遍历,无法唯一确定一棵二叉树。】
这种题用递归解决比较好理解,而且重要的是我们要明白树的三种遍历方式的特点。
先序遍历数组:第一个元素为根节点,后面的树为左子树的节点然后接着是右子树的节点;
中序遍历数组:根节点左边的元素是根节点左子树的节点,根节点右边的元素是根节点右子树的节点。
后序遍历数组:数组最后一个元素是根节点,前面的元素从前往后是左子树的节点、右子树的节点。
整个解题思路围绕着这些遍历方式的特点,然后自顶向下地构造二叉树。理解了三种遍历的特点,就不难了,所以下面的代码不做注释,因为我感觉代码本身写得也比较清楚。
106. Construct Binary Tree from Inorder and Postorder Traversal(已知中序和后序确定二叉树)
class Solution {
public:
TreeNode* help(vector<int>& postorder, vector<int>& inorder,int postbegin,int postend,int inbegin,int inend)
{
if(postbegin>=postend||inbegin>=inend)
return NULL;
TreeNode* root = new TreeNode(postorder[postend-1]);
int rootval = postorder[postend-1];
int ind = inbegin;
while(inorder[ind]!=rootval)
ind++;
root->left = help(postorder,inorder,postbegin,postbegin+ind-inbegin,inbegin,ind);
root->right = help(postorder,inorder,postbegin+ind-inbegin,postend-1,ind+1,inend);
return root;
}
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder)
{
return help(postorder,inorder,0,postorder.size(),0,inorder.size());
}
};
105. Construct Binary Tree from Preorder and Inorder Traversal(已知先序和中序确定二叉树)
class Solution {
public:
//不同于上面的代码,本份代码写得比较繁琐,但思路都是一样的,
//繁琐的原因是上一份代码我是用过两个指示位置的变量来划分左右子树
//而本份代码我是用创建了两个数组来表示左右子树。
int findpos(vector<int> v,int key)
{
int i;
for( i=0;i<v.size();++i)
if(v[i]==key)
break;
return i;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if(preorder.empty()||inorder.empty())
return NULL;
TreeNode* root = new TreeNode(preorder[0]);
int rootval = preorder[0];
int ind = findpos(inorder,rootval);
vector<int> newpre,newin;
for(int i=1;i<ind+1;++i)
newpre.push_back(preorder[i]);
for(int i=0;i<ind;++i)
newin.push_back(inorder[i]);
root->left = buildTree(newpre,newin);
newpre.clear(),newin.clear();
int len = inorder.size()-ind;
for(int i=1+ind;i<ind+len;++i)
newpre.push_back(preorder[i]);
for(int i=ind+1;i<ind+len;++i)
newin.push_back(inorder[i]);
root->right = buildTree(newpre,newin);
return root;
}
};
23. Merge k Sorted Lists
合并K个有序的单链表(从小到大的顺序)。这道题我是直接把链表两两合并,最简单的想法。然后看了一下题解,可以用优先队列解题,就顺便熟悉一下优先队列的使用吧。
▲对于有关链表操作的题,我们可以先自己申请一块内存作为链表的头节点或者其他有意义的节点,然后以此节点出发去解题,这样可能有时会方便我们解题。
▲优先队列的初始化:priority_queue<数据类型,容器类型(默认为vector),比较函数(默认为队首元素为最大值)>;如果要自定义比较函数,那么就要声明容器类型,一般是:vector<数据类型>,如:priority_queue<int,vector<int>,cmp>。
其中,如果cmp = greater<int> 则队列为升序队列,cmp = less<int>则队列为降序队列。
▲如果优先队列中的数据类型是我们自定义的数据类型,那么初始化时第三个参数就不是简单的比较函数了,需要为一个定义了括号函数()的类或者结构体,且()函数需要为public!
▲优先队列是用堆实现的,一开始构建堆时时间复杂度为O(n),而插入、删除操作的时间复杂度则只需要O(lgn)。
▲关于优先队列初始化时要自定义的比较函数:如果cmp(first,second){ return first > second},则构建的是升序优先队列,反之为降序。
▲优先队列获取队首元素,其实也就是获取堆顶元素,使用的接口函数是 top()。
1、两两合并:很简单的思路,没什么好注释的
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.empty())
return NULL;
if(lists.size()==1)
return lists[0];
ListNode* merge = new ListNode(0);
ListNode* head = merge;
for(int i=1; i<lists.size(); ++i)
{
ListNode* first = lists[i-1];
ListNode* second = lists[i];
while(first!=NULL && second!=NULL)
{
if(first->val <= second->val)
merge->next = first,first = first->next;
else
merge->next = second,second = second->next;
merge = merge->next;
}
if(first!=NULL)
merge->next = first;
else
merge->next = second;
lists[i] = head->next;
merge = head;
}
merge = head->next;
delete head;
return merge;
}
};
2、优先队列
class cmp{
public:
bool operator()(ListNode* fir,ListNode* sec)
{
return fir->val > sec->val;
}
};
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.empty())
return NULL;
ListNode* dummy = new ListNode(0);
ListNode* newhead = dummy;
priority_queue<ListNode*,vector<ListNode*>,cmp> pq;
for(auto List:lists)
if(List!=NULL)
pq.push(List);
while(!pq.empty())
{
ListNode* cur = pq.top();
pq.pop();
newhead->next = cur;
newhead = newhead->next;
if(cur->next)
pq.push(cur->next);
}
newhead = dummy->next;
delete dummy;
return newhead;
}
};
优先队列还可以这样用,省略了自定义比较函数的步骤!这是因为:
▲pair是一个模板,且已经自定义了比较函数,所以当优先队列里的数据类型是pair时,我们可以不用自己写比较函数,默认顺序为降序,如果要变为升序,我们可以视情况自己写比较函数(同上面所说的),或者可以将pair里面要比较的数值取反传入优先队列,对变成升序了。
▲pair内置的比较函数:先比较第一个元素,视结果决定要不要比较第二个元素,得到最终比较结果。
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.empty())
return NULL;
ListNode* dummy = new ListNode(0);
ListNode* newhead = dummy;
priority_queue<pair<int,ListNode*>> pq;
for(auto List:lists)
if(List!=NULL)
pq.push(make_pair(-(List->val),List));
while(!pq.empty())
{
auto cur = pq.top();
pq.pop();
newhead->next = cur.second;
newhead = newhead->next;
if(cur.second->next)
pq.push({-(cur.second->next)->val,cur.second->next});
}
newhead = dummy->next;
delete dummy;
return newhead;
}
};
791. Custom Sort String
给定两个字符串S和T,按照S中字符出现的顺序对T重新排序,如果T中有S中没有的字符,则这些字符可以出现这种任意位置。
思路:我们把问题尽可能的简单化,将T中的字符分为两类,一类是S中也有的,一类是S中没有的。
class Solution {
public:
string customSortString(string S, string T) {
string ans,remain;
unordered_set<char> ins(S.begin(),S.end());
int cnt[26] = {0};
for(auto t:T)
{
if(!ins.count(t))
remain+=t;
else
cnt[t-'a']++;
}
for(auto ch:S)
{
if(cnt[ch-'a'])
while(cnt[ch-'a']--)
ans+=ch;
}
return ans+remain;
}
};
784. Letter Case Permutation
原题:Given a string S, we can transform every letter individually to be lowercase or uppercase to create another string. Return a list of all possible strings we could create.
主要是想记录这种我觉得比较通用的全排列算法(回溯法):按规定的全排列顺序进行排序。
class Solution {
public:
vector<string> ans;
void perm(string ori,string s,int n)
{
if(s.size()==ori.size())
ans.push_back(s);
else
{
if(isalpha(ori[n]))
{
s.push_back(tolower(ori[n]));
perm(ori,s,n+1);
s[s.size()-1] = toupper(ori[n]);
perm(ori,s,n+1);
}
else
s.push_back(ori[n]),perm(ori,s,n+1);
}
}
vector<string> letterCasePermutation(string S) {
perm(S,"",0);
return ans;
}
};
767. Reorganize String
重排字符串使得相邻的字符不相等。
思路:每一次确定频数最大和第二大的两个字符的位置,不仅保证了局部两个相邻字符不相等,也保证了字符串整体中各对相邻字符不相等。(由于要知道最大频数的字符,所以我们可以结合优先队列和pair)
class Solution {
public:
string reorganizeString(string S)
{
if(S.empty())
return "";
vector<int> letter(26,0);
for(int i=0;i<S.size();++i)
{
letter[S[i]-'a']++;
if(letter[S[i]-'a']>(S.size()+1)/2)
return "";
}
priority_queue<pair<int,char>> pq;
for(int i=0;i<26;++i)
if(letter[i])
pq.push({letter[i],i+'a'});
string ans;
while(!pq.empty())
{
pair<int,char> fir=pq.top(),sec;
pq.pop();
ans+=fir.second;
if(!pq.empty())
{
sec = pq.top(),pq.pop();
ans+=sec.second;
if(--sec.first)
pq.push(sec);
}
if(--fir.first)
pq.push(fir);
}
return ans;
}
};
34. Find First and Last Position of Element in Sorted Array
找出一个升序序列中指定元素开始出现的位置和最后出现的位置。要求算法的时间复杂度必须为O(lgn)。
由时间复杂度的要求我们可以很清楚地知道我们需要用二分查找。
class Solution {
public:
//vector,string,deque的迭代器是有加减法的
vector<int> searchRange(vector<int>& nums, int target) {
if(nums.empty())
return {-1,-1};
auto first = lower_bound(nums.begin(),nums.end(),target);
auto last = upper_bound(nums.begin(),nums.end(),target);
last = prev(last);
if(first==nums.end()||*first!=target) //目标元素不在序列中
return {-1,-1};
if(*last!=target) //序列中只有一个目标元素
return {distance(nums.begin(),first)};
return {distance(nums.begin(),first),distance(nums.begin(),last)};
}
};
通过该题学习几个STL中很好用的函数:
▲lower_bound:返回指定区间(左闭右开)中首个不小于目标元素的迭代器。
▲upper_bound:返回指定区间(左闭右开)中首个大于目标元素的迭代器。
(vector,string,deque的迭代器是有加减法的)
▲包含在<iterator>库中的函数:prev(迭代器):返回指定迭代器的上一个迭代器,内部由advance实现;advance(迭代器,距离):让迭代器前进给定距离,距离可以为负数;next(迭代器):迭代器自增,默认自增1;distance(迭代器1,迭代器2):返回两个迭代器之间的距离。
740. Delete and Earn
选取序列中的数字,你会得到该数字对应数值的分数,然后删除这个数字(1个)以及所有比该数小1的数字或者所有比该数大1的数字。思路:数据范围为[1,10000],不算大,而且根据题意我们需要保证序列有序然后再做动态规划,所以可以用桶排序。对于序列中的每一个数,只有两种状态:被选取和未被选取,每一个数是否选取仅与它相邻的数有关,由于我们做了排序,所以可以认为只与该元素的上一个元素有关,所以可以用O(1)的空间复杂度来解题。
class Solution {
public:
int deleteAndEarn(vector<int>& nums) {
//需要注意的地方:
//数字范围小,可以用桶排序;
//因为选择一个数的同时要删除与其大小相差1的数,所以要保证数字的相邻性再做动态规划
vector<int> cnt(10001,0);
for(int n:nums)
cnt[n]++;
int last_skip=0,last_pick=0,cur_skip=0,cur_pick=0;
for(int i=0;i<10001;++i)
{
cur_skip = max(last_skip,last_pick);
cur_pick = last_skip + i*cnt[i];
last_skip=cur_skip;
last_pick=cur_pick;
}
return max(cur_skip,cur_pick);
}
};
通过这题总结一下做过动态规划题后的一点理解:
▲最重要的就是明确序列中的元素可能有的状态;然后根据题目确定元素状态的选取取决于什么,从而确定动态规划所需要的空间复杂度;再确定不同状态对应什么不同的操作。
739. Daily Temperatures
序列中的元素代表不同日期的气温,判断每一天需要等多少天才能等到气温比该天气温高。
思路:两层循环就能解决的简单问题,但这样效率低,做对了也没什么收获。题解里给出了借助栈的方法。
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& T) {
int n = T.size();
vector<int> ans(n,0);
stack<int> st;
//从后往前,保证对于未访问到的日期,栈中存放的元素是其后的最近一个气温较大的日期
for(int i=n-1;i>=0;--i)
{
while(!st.empty()&&T[st.top()]<=T[i])
st.pop();
if(st.size())
ans[i] = st.top()-i;
st.push(i);
}
return ans;
}
};
738. Monotone Increasing Digits
给定一个数,返回最大的小于该数且高位到低位上的数字是递增的的数。
主要学习一种方法:为了方便读取和比较数字的各个位上的数,可以把数字转为字符串形式。
思路:最大的数且每一位是不严格递增,那我们肯定想要尽可能的使每一位上的数字为9就能保证最大。但还要小于给定的数,所以我们要判断哪一些位可以置为9。
class Solution {
public:
int monotoneIncreasingDigits(int N) {
if(N<10)
return N;
string s = to_string(N);
int len = 1;
//找到不满足递增的第一个位置
while(len<s.size()&&s[len-1]<=s[len])
len++;
while(len>=0&&s[len-1]>s[len])
s[len-1]--,len--;
for(int i=len+1;i<s.size();++i)
s[i] = '9';
int ans = 0;
for(int i=0;i<s.size();++i)
ans = ans*10+s[i]-'0';
return ans;
}
};
724. Find Pivot Index
给定一个序列,找出序列中是否存在一个数,其左边所有元素的和等于右边所有元素的和(左/右边没有元素则和为0)。
下面这种解法只可意会,我是不知道该怎么说。
class Solution {
public:
int pivotIndex(vector<int>& nums) {
int leftsum=0,sum=0;
for(int n:nums)
sum+=n;
for(int i=0;i<nums.size();++i)
{
sum-=nums[i];
if(sum==leftsum)
return i;
leftsum+=nums[i];
}
return -1;
}
};