主要记录一下昨天做的5道题目。有3道题目没有在给定时间内AC,也是菜的咯。
第一题 LintCode Coins in a line
给定一排n个硬币,从第一个玩家开始从右边取硬币,每次可取一个或者两个硬币,两位玩家轮流进行,取走最后一枚硬币的玩家获胜。问给定n,判断玩家1是否能否获胜?
思路:反向递推法。要想玩家1能取走最后一枚硬币,那么玩家1一定要取走第4枚硬币;要想玩家1能取走第4枚硬币,那么玩家1一定要取走第7枚硬币;以此类推,玩家1获胜的条件就是一定要能取走第3k+1枚硬币。那么给定n,玩家1第一次取几枚硬币可以保证取到第3k+1枚硬币呢?由于第一次玩家1既能取1枚也能取2枚,所以当 n=3k+1或者n=3k+2的时候可以保证玩家1取到第3k+1枚硬币。因此代码很简单,这道题目重点考察了数学思想的应用:
class Solution {
public:
/**
* @param n: an integer
* @return: a boolean which equals to true if the first player will win
*/
bool firstWillWin(int n) {
// write your code here
return n % 3 != 0;
}
};
第二题 Coins in a line II
这道题目是上一题的变形,即最后谁取得的总价值大谁就获胜。但是不能通过简单的数学思路来做了,需要用动态规划的思想来做。所谓动态规划,就是“动着”规划。最关键的步骤就是找到状态表示,确定递推关系。我们用dp[i]来表示玩家从第i枚硬币开始游戏到最后一枚硬币可获得的最大利润。我们从最后一步开始考虑:
1.只有一个硬币,那肯定拿了比不拿好,dp[n-1]=values[n-1];
2.只有两个硬币,那也肯定是全拿了好,否则还得给对手留一个,所以dp[n-2]=values[n-2]+values[n-1];
3.剩下三个硬币,无论我只拿一个还是拿两个反正是拿不到最后一个硬币,所以我肯定是拿两个只给对手留下最后一个硬币,所以dp[n-3]=values[n-3]+values[n-2];
那么接下来考虑一般的情况,假设目前我(玩家1)从硬币i开始选择。
1.我只拿一个硬币,那么对手(玩家2)就从i+1硬币开始,由于对手也有两种选择,即1)对手只拿一个硬币,那么再次轮到我时就是从第i+2枚硬币开始,此时我能获得的最大利润就是dp[i+2],总利润就是values[i]+dp[i+2]。2)对手拿两个硬币,那么同理就是valuse[i]+dp[i+3]。那么对手会如何选择呢?毫无疑问,他肯定会选择两种选择中让我利润最小的那一种,也就是说是values[i]+min(dp[i+2], dp[i+3])
2.我拿了两枚硬币,那么跟上面同样的分析方法,可以得到此时我能获得的利润就是values[i]+values[i+1]+min(dp[i+3], dp[i+4])。
那么我的选择肯定就是两者中的最大值了,这样就得到了递推关系式:
dp[i] = max(values[i]+min(dp[i+2], dp[i+3]), values[i]+values[i+1]+min(dp[i+3], dp[i+4]))。
最后dp[0]就是我从第0枚硬币开始选所能获得的最大利润。那么另一位玩家获得的利润就是总利润-dp[0]。
只需要判断这两个利润哪一个更大从而得出谁会获胜即可。
那么代码如下:
class Solution {
public:
/**
* @param values: a vector of integers
* @return: a boolean which equals to true if the first player will win
*/
bool firstWillWin(vector<int> &values) {
// write your code here
if (values.size() < 3) return true;
int n = values.size(), sum = 0;
vector<int> dp(n+1, 0);//dp[i+4]会出现dp[n]的情况,不如就假设dp[n]=0;
dp[n-1] = values[n-1];
dp[n-2] = values[n-2] + values[n-1];
dp[n-3] = values[n-3] + values[n-2];
for (int i = n-4; i>= 0; i--) {
dp[i] = max(values[i]+min(dp[i+2], dp[i+3]),
values[i]+values[i+1]+min(dp[i+3], dp[i+4]));
}
for (auto value: values) {
sum += value;
}
return dp[0] > sum - dp[0];
}
};
第三题 Combination Sum
dfs题目。
class Solution {
public:
/**
* @param candidates: A list of integers
* @param target:An integer
* @return: A list of lists of integers
*/
vector<vector<int> > combinationSum(vector<int> &candidates, int target) {
// write your code here
vector<vector<int>> ret;
set<vector<int>> s; //用set避免重复
vector<int> tmp;
int sum = 0;
if (candidates.size() == 0) return ret;
sort(candidates.begin(), candidates.end());
for (int i = 0; i < candidates.size(); i++) {
dfs(candidates, s, tmp, i, sum, target);
}
for (auto list : s) {
ret.push_back(list);
}
return ret;
}
void dfs(vector<int> candidates, set<vector<int>> &s, vector<int> tmp,
int idx, int sum, int target) {
//这里sum和tmp都是用了回溯法,因为普通值传递修改形参只会在该函数内有效,退出这个函数
//的时候自动还原成了之前的实参。
sum += candidates[idx];
tmp.push_back(candidates[idx]);
if (sum >= target) {
if (sum == target)
s.insert(tmp);
return;
}
for (int i = idx; i < candidates.size(); i++) {
//从idx开始的原因是 每一个数字可以无限次使用
dfs(candidates, s, tmp, i, sum, target);
}
}
};
第四题 Combination SumII
几乎和上一题没有什么变化。
class Solution {
public:
/**
* @param num: Given the candidate numbers
* @param target: Given the target number
* @return: All the combinations that sum to target
*/
vector<vector<int> > combinationSum2(vector<int> &num, int target) {
// write your code here
set<vector<int>> s;
vector<vector<int>> ret;
vector<int> tmp;
int sum = 0;
if (num.size() == 0) return ret;
sort(num.begin(), num.end());
for (int i = 0; i < num.size(); i++) {
dfs(num, s, tmp, i, sum, target);
}
for (auto list : s) {
ret.push_back(list);
}
return ret;
}
void dfs(vector<int> &num, set<vector<int>> &s, vector<int> tmp, int idx,
int sum, int target) {
sum += num[idx];
tmp.push_back(num[idx]);
if (sum >= target) {
if (sum == target)
s.insert(tmp);
return;
}
for (int i = idx+1; i < num.size(); i++) {
dfs(num, s, tmp, i, sum, target);
}
}
};
第五题 Container With Most Water
思路:Two Points法。用一左一右指针从两端向中间聚集,当左边高度小于右边时,left++;否则right–;
为什么呢?因为当左边高度小于右边时,left++意味着我们舍弃了从left到right-1,right-2等等这些水桶。因为这些水桶的面积一定小于left到right。可以这样想由于水桶的底缩短了,所以要想面积比left到right的大,那么高一定要变大。但实际上height = min(height[left], height[right]) <=height[left],所以这个新高一定不会比之前的那个高大。所以面积一定是变小的。
class Solution {
public:
/**
* @param heights: a vector of integers
* @return: an integer
*/
int maxArea(vector<int> &heights) {
// write your code here
int l = 0, r = heights.size()-1;
int ret = 0, area = 0;
while (l < r) {
area = (r-l) * min(heights[l], heights[r]);
ret = max(ret, area);
if (heights[l] < heights[r]) l++;
else r--;
}
return ret;
}
};
总结:
1.第1、3、5是没有AC的,动态规划确实不熟,而且个人的算法思路仍然很狭窄。
2.感觉一天5道Medium的题目的量刚刚好,做题+看答案+写blog花差不多3个多小时。