123.买卖股票的最佳时机III
思路:本题中第i天有五种状态:不操作、第一次持有、第一次不持有、第二次持有、第二次不持有。
注意可以同一天进行买卖,而且是多次买卖。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len=prices.size();
vector<vector<int>> dp(len,vector<int>(5));
dp[0][0]=0;
dp[0][1]=-prices[0];
dp[0][2]=0;
dp[0][3]=-prices[0];
dp[0][4]=0;
for(int i=1;i<len;i++){
dp[i][0]=dp[i-1][0];
dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i]);
dp[i][2]=max(dp[i-1][2],dp[i-1][1]+prices[i]);
dp[i][3]=max(dp[i-1][3],dp[i-1][2]-prices[i]);
dp[i][4]=max(dp[i-1][4],dp[i-1][3]+prices[i]);
}
return dp[len-1][4];
}
};
188.买卖股票的最佳时机IV
本题和上一题不同的就是,第i天可以有2*k+1种状态,需要额外用一个变量j记录第i天第j次买卖。
至于边界条件,可以代入k=2,进行验证。
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
int len=prices.size();
if(len==0) return 0;
vector<vector<int>> dp(len,vector<int>(2*k+1,0));
for(int i=1;i<2*k;i+=2){
dp[0][i]=-prices[0];//第0天持有一定是买入//错因:是prices[0],不是prices[i]
}
for(int i=1;i<len;i++){
for(int j=0;j<2*k;j+=2){
dp[i][j+1]=max(dp[i-1][j+1],dp[i-1][j]-prices[i]);
dp[i][j+2]=max(dp[i-1][j+2],dp[i-1][j+1]+prices[i]);
}
}
return dp[len-1][2*k];
}
};
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
思路:快排的思路。只要left和right不指向同一个元素,如果left指向的元素一直是奇数,就一直往后移动left指针,如果right指向的元素一直是偶数,就一直往前移动right指针,然后两个指针停止的位置肯定是left指向偶数,right指向奇数,那么将这两个元素进行交换即可,然后再开始下一轮。
class Solution {
public:
vector<int> exchange(vector<int>& nums) {
int left=0,right=nums.size()-1;
while(left<right){
while(left<right&&nums[left]%2==1) left++;
while(left<right&&nums[right]%2==0) right--;
int tem=nums[left];
nums[left]=nums[right];
nums[right]=tem;
}
return nums;
}
};
剑指 Offer 57. 和为s的两个数字
思路:一说是在排序数组中查找元素,自然就是二分查找。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
for(int i=0;i<nums.size()-1;i++){
int left=i+1;
int right=nums.size()-1;
int fin=target-nums[i];
while(left<=right){
int mid=(left+right)/2;
if(nums[mid]==fin) return vector<int>{nums[i],fin};
else if(nums[mid]<fin) left=mid+1;
else{
right=mid-1;
}
}
}
return vector<int>{};
}
};
剑指 Offer 58 - I. 翻转单词顺序
思路:双指针法,快指针用于获取我们想要的元素,满指针指向我们获取的新元素更新在哪里。先去除字符串中多余的空格,然后整体反转+局部反转。其实相当于同时操作新数组和旧数组,外层循环是快指针用于遍历旧数组,然后用满指针更新新数组。
class Solution {
public:
void reverse(string&s,int start,int end){
for(int i=start,j=end;i<j;i++,j--){
swap(s[i],s[j]);
}
}
void removeextraspace(string&s){
int slow=0;
for(int fast=0;fast<s.size();fast++){
if(s[fast]!=' '){//找到新的单词了,然后同时移动快慢指针,但是单词之间要留空格
if(slow!=0) s[slow++]=' ';
}
while(fast<s.size()&&s[fast]!=' '){
s[slow++]=s[fast++];
}
}
s.resize(slow);//第一处错误,忘记改了
}
string reverseWords(string s) {
if(s.size()==0) return s;
removeextraspace(s);
reverse(s,0,s.size()-1);
int start=0;//用来记录每次找到的单词的起始位置,并且去除空格之后第一个单词的首字母下标一定是0
for(int i=0;i<=s.size();i++){//第二处错误,边界条件应该有等于,因为这个下标i是为了记录每个单词的末尾后面一个位置
if(i==s.size()||s[i]==' '){
reverse(s,start,i-1);
start=i+1;
}
}
return s;
}
};
错因:1、更新完新数组之后忘记改字符串的大小了,因为我们是在原数组上直接进行更新的,所以要改变新数组的大小。
2、在反转单词的时候for循环的边界条件写错了,应该有等于,因为此时for循环里的下标i是为了我们记录单词的末尾位置,但是小标i指向的是单词的末尾位置的后一个位置,所以要有等于s.size()。
剑指 Offer 12. 矩阵中的路径
思路:回溯法。注意同一个格子的元素不能重复访问,所以将它改成一个#(因为board
和word
仅由大小写英文字母组成,所以#一定不会被访问),然后回溯的时候再改回去即可。当然也可以用visited数组记录访问情况,但是这样的空间复杂度比较高。
class Solution {
public:
bool dfs(vector<vector<char>>& board, string word,int k,int i,int j){
int m=board.size();
int n=board[0].size();
if(!(i>=0&&i<m)||!(j>=0&&j<n)||board[i][j]!=word[k]) return false;
if(k==word.size()-1) return true;
char temp=board[i][j];
board[i][j]='#';//防止重复访问
bool res=dfs(board,word,k+1,i+1,j)||dfs(board,word,k+1,i-1,j)||dfs(board,word,k+1,i,j+1)||dfs(board,word,k+1,i,j-1);
board[i][j]=temp;
return res;
}
bool exist(vector<vector<char>>& board, string word) {
for(int i=0;i<board.size();i++){
for(int j=0;j<board[0].size();j++){
if(dfs(board,word,0,i,j)) return true;
}
}
return false;
}
};
错因:dfs的递归终止条件中的边界条件写错,写成了i<=m和j<=n,应该是<。
本题因为单词内字符是有顺序的,所以可能前面的字符不符合,但是后面的字符符合了,所以必须要进行回溯。
面试题13. 机器人的运动范围
本题和上一题不一样的是,如果遍历到一个格子是不符合条件的,那它一定就是不符合了,所以不用进行回溯。
class Solution {
public:
int getval(int i,int j){//直接求出两个数的数位之和
int res=0;
while(i){
res+=i%10;
i/=10;
}
while(j){
res+=j%10;
j/=10;
}
return res;
}
int dfs(int i, int j, int k,int m,int n,vector<vector<bool>>& visited){
if(i>=m||j>=n||getval(i,j)>k||visited[i][j]==true) return 0;//visited[i][j]==true说明该点已经被之前某条路径访问过了,已经计算过了,如果再沿着这个位置往右往下走的话,必然会走重复的路径//就相当于设了个障碍,不能再继续往后搜索了,该点也不能计算,所以返回0
visited[i][j]=true;
return 1+dfs(i+1,j,k,m,n,visited)+dfs(i,j+1,k,m,n,visited);
}
int movingCount(int m, int n, int k) {
vector<vector<bool>> visited(m,vector<bool>(n,false));
return dfs(0,0,k,m,n,visited);
}
};
注意数位求和的过程。
开始很纠结为什么dfs函数里,visited[i][j]==true,要返回0。因为visited[i][j]==true说明该点已经被之前某条路径经过了,也就是已经计算过了,如果是true就返回0,也就是当前路径经过该点的后面的路径不要再进行搜索了,而且该点也不能进行计算,所以直接返回0。
本题用unordered_set的写法还是不太会,有待继续研究。