515. Paint House
这里有n
个房子在一列直线上,现在我们需要给房屋染色,分别有红色蓝色和绿色。每个房屋染不同的颜色费用也不同,你需要设计一种染色方案使得相邻的房屋颜色不同,并且费用最小,返回最小的费用。
费用通过一个n
x3
的矩阵给出,比如cost[0][0]
表示房屋0
染红色的费用,cost[1][2]
表示房屋1
染绿色的费用。
提示:f[i][j]为第i栋房子涂成j颜色所需的最小花费。最后返回的是第n-1栋房子(从0开始),三个里面的最小值。
由于计算每一个房子时只需要前一次的数据,所以用滚动数组来减少空间。
答案:
class Solution {
public:
/**
* @param costs: n x 3 cost matrix
* @return: An integer, the minimum cost to paint all houses
*/
int minCost(vector<vector<int>> &costs) {
int n = costs.size();
if (!n) return 0;
vector<vector<int>> f(2, vector<int>(3, 0x7fffffff));
f[0][0] = costs[0][0];
f[0][1] = costs[0][1];
f[0][2] = costs[0][2];
for (int i = 1; i < n; i++)
for (int j = 0; j < 3; j++) {
f[i&1][j] = 0x7fffffff;
for (int k = 0; k < 3; k++)
if (j != k)
f[i&1][j] = min(f[i&1][j], f[!(i&1)][k] + costs[i][j]);
}
return min(f[(n-1)&1][0], min(f[(n-1)&1][1], f[(n-1)&1][2]));
}
};
664. Counting Bits
给出一个 非负 整数 num,对所有满足 0 ≤ i ≤ num
条件的数字 i 均需要计算其二进制表示中数字 1 的个数并以数组的形式返回。
提示:设dp[i]dp[i]表示i的二进制表示中数字1的个数。状态转移方程为:dp[i]=dp[i>>1]+i\%2dp[i]=dp[i>>1]+i%2。
第二种取巧的方法是《剑指offer》中提到的i&(i-1)就是使其最右边的1变为0。
答案:
class Solution {
public:
/**
* @param num: a non negative integer number
* @return: an array represent the number of 1's in their binary
*/
vector<int> countBits(int num) {
int len = num + 1;
vector<int> dp(len, 0);
for(int i = 1; i <= num; ++i) {
dp[i] = dp[i>>1] + i % 2;
}
return dp;
}
};
public class Solution {
public int[] countBits(int num) {
int[] f = new int[num+1];
int i;
f[0] = 0;
for (i=1; i<=num; ++i) {
f[i] = f[i&(i-1)] + 1;
}
return f;
}
}
516. Paint House II
这里有n
个房子在一列直线上,现在我们需要给房屋染色,共有k
种颜色。每个房屋染不同的颜色费用也不同,你需要设计一种染色方案使得相邻的房屋颜色不同,并且费用最小。
费用通过一个n
xk
的矩阵给出,比如cost[0][0]
表示房屋0
染颜色0
的费用,cost[1][2]
表示房屋1
染颜色2
的费用。
提示:按照原先的思路时间复杂度为O(NK^2),为了减小时间复杂度到O(NK),我们只需记录第i栋房子的最小值与次小值。
前者在lintcode上只能通过94%的数据。
答案:时间O(NK^2),空间O(1)。 i&1相当于将原来的行数每两行一组, !(i&1)相当于i-1。
//O(NK^2)
class Solution {
public:
/**
* @param costs: n x k cost matrix
* @return: an integer, the minimum cost to paint all houses
*/
int minCostII(vector<vector<int>> &costs) {
// write your code here
int n = costs.size();
if (!n) return 0;
int m = costs[0].size();
vector<vector<int>> f(2, vector<int>(m, 0x7fffffff));
f[0][0] = costs[0][0];
f[0][1] = costs[0][1];
f[0][2] = costs[0][2];
for(int k = 0; k < m; k++){
f[0][k] = costs[0][k];
}
for (int i = 1; i < n; i++)
for (int j = 0; j < m; j++) {
f[i&1][j] = 0x7fffffff;
for (int k = 0; k < m; k++)
if (j != k)
f[i&1][j] = min(f[i&1][j], f[!(i&1)][k] + costs[i][j]);
}
//return min(f[(n-1)&1][0], min(f[(n-1)&1][1], f[(n-1)&1][2]));
int result = 0x7fffffff;
for(int k = 0; k < m; k++){
if(f[(n-1)&1][k] < result){
result = f[(n-1)&1][k];
}
}
return result;
}
};
时间O(NK),空间O(1)
class Solution {
public:
/**
* @param costs: n x k cost matrix
* @return: an integer, the minimum cost to paint all houses
*/
int minCostII(vector<vector<int>> &costs) {
// write your code here
int n = costs.size();
if (!n) return 0;
int m = costs[0].size();
vector<vector<int>> f(2, vector<int>(m, 0x7fffffff));
for(int k = 0; k < m; k++){
f[0][k] = costs[0][k];
}
int min1,min2,j1,j2;
for (int i = 1; i < n; i++){
min1 = min2 = 0x7fffffff;
for (int j = 0; j < m; j++) {
if(f[!(i&1)][j] < min1){
min2 = min1;
j2 = j1;
min1 = f[!(i&1)][j];
j1 = j;
}
else if(f[!(i&1)][j] < min2){
min2 = f[!(i&1)][j];
j2 = j;
}
}
for(int k = 0; k < m; k++){
if(k != j1){
f[i&1][k] = min1 + costs[i][k];
}
else{
f[i&1][k] = min2 + costs[i][k];
}
}
}
int result = 0x7fffffff;
for(int k = 0; k < m; k++){
if(f[(n-1)&1][k] < result){
result = f[(n-1)&1][k];
}
}
return result;
}
};
392. House Robber
假设你是一个专业的窃贼,准备沿着一条街打劫房屋。每个房子都存放着特定金额的钱。你面临的唯一约束条件是:相邻的房子装着相互联系的防盗系统,且 当相邻的两个房子同一天被打劫时,该系统会自动报警。
给定一个非负整数列表,表示每个房子中存放的钱, 算一算,如果今晚去打劫,在不触动报警装置的情况下, 你最多可以得到多少钱 。
提示:线性DP题目.
设 dp[i] 表示前i家房子最多收益, 答案是 dp[n], 状态转移方程是
dp[i] = max(dp[i-1], dp[i-2] + A[i-1])
考虑到dp[i]的计算只涉及到dp[i-1]和dp[i-2], 因此可以O(1)空间解决.
答案:其中的取余运算可以用几个变量代替但是可读性不好。
class Solution {
public:
/**
* @param A: An array of non-negative integers
* @return: The maximum amount of money you can rob tonight
*/
long long houseRobber(vector<int> &A) {
// write your code here
int n = A.size();
if(n == 0) return 0;
long long int res[2];
res[0] = A[0];
res[1] = max(A[0], A[1]);
for(int i = 2; i < n; i++){
res[i%2] = max(res[(i-1)%2], res[(i-2)%2] + A[i]);
}
return res[(n-1)%2];
}
};
534. House Robber II
在上次打劫完一条街道之后,窃贼又发现了一个新的可以打劫的地方,但这次所有的房子围成了一个圈,这就意味着第一间房子和最后一间房子是挨着的。每个房子都存放着特定金额的钱。你面临的唯一约束条件是:相邻的房子装着相互联系的防盗系统,且 当相邻的两个房子同一天被打劫时,该系统会自动报警。
给定一个非负整数列表,表示每个房子中存放的钱, 算一算,如果今晚去打劫,在不触动报警装置的情况下, 你最多可以得到多少钱 。
提示:在之前的基础上分两种情况,要么偷第一个房子不偷最后一个,要么偷最后一个不偷第一个。两个都不偷的情况包含在第 二种情况。 如果不另外写一个计算函数的话,一定要注意两种情况的初始条件。
答案:
class Solution {
public:
/**
* @param nums: An array of non-negative integers.
* @return: The maximum amount of money you can rob tonight
*/
int houseRobber2(vector<int> &nums) {
// write your code here
int n = nums.size();
if(n == 0) return 0;
if(n == 1) return nums[0];
if(n == 2) return max(nums[0], nums[1]);
long long int res[2];
//condition 1
res[0] = nums[0];
res[1] = max(nums[0], nums[1]);
for(int i = 2; i < n - 1; i++){
res[i%2] = max(res[(i-1)%2], res[(i-2)%2] + nums[i]);
}
long long int tmpMax = res[(n-2)%2];
//condition 2
res[0] = 0;
res[1] = nums[1];
for(int i = 2; i < n; i++){
res[i%2] = max(res[(i-1)%2], res[(i-2)%2] + nums[i]);
}
tmpMax = max(tmpMax, res[(n-1)%2]);
return tmpMax;
}
};
149. Best Time to Buy and Sell Stock
假设有一个数组,它的第i个元素是一支给定的股票在第i天的价格。如果你最多只允许完成一次交易(例如,一次买卖股票),设计一个算法来找出最大利润。
提示:记录两个值,一个是目前为止的最小值,目前为止的最大利润。最大利润为min之后的最大值减去min。
答案:
class Solution {
public:
/**
* @param prices: Given an integer array
* @return: Maximum profit
*/
int maxProfit(vector<int> &prices) {
// write your code here
if(prices.size() == 0){
return 0;
}
int min = 0x7fffffff;
int profit = 0;
for(int i : prices){
min = i < min ? i : min;
profit = (i - min) > profit ? i - min : profit;
}
return profit;
}
};
150. Best Time to Buy and Sell Stock II
给定一个数组 prices
表示一支股票每天的价格.
你可以完成任意次数的交易, 不过你不能同时参与多个交易 (也就是说, 如果你已经持有这支股票, 在再次购买之前, 你必须先卖掉它).
设计一个算法求出最大的利润.
提示:这个最简单,只要明天比今天价格高就买入。
答案:
class Solution {
public:
/**
* @param prices: Given an integer array
* @return: Maximum profit
*/
int maxProfit(vector<int> &prices) {
int n = prices.size();
int profit = 0;
for (int i = 1; i < n; i++) {
if (prices[i] - prices[i - 1] > 0) {
profit += prices[i] - prices[i - 1];
}
}
return profit;
}
};
151. Best Time to Buy and Sell Stock III
假设你有一个数组,它的第i个元素是一支给定的股票在第i天的价格。设计一个算法来找到最大的利润。你最多可以完成两笔交易。
提示:分为五种情况。前面的题基本用坐标型来写的,此题用序列型来写比较好,前0天只能处于阶段1。当天买的不计算红利。
最下面的式子有问题,最后一项无意义可删除,因为其卖了接着买相当于一次买卖。
答案:
class Solution {
public:
/**
* @param prices: Given an integer array
* @return: Maximum profit
*/
const int MinValue = 0x80000000;
int maxProfit(vector<int> &prices) {
// write your code here
int n = prices.size();
if(n == 0) return 0;
vector<vector<int>> dp(n + 1,vector<int>(5 + 1));
for(int k = 1; k <= 5; k++){
dp[0][k] = MinValue;
}
dp[0][1] = 0;
for(int i = 1; i <= n; i++){
//手中未持有股票
for(int j = 1; j <= 5; j+=2){
//前一天也未持有股票
dp[i][j] = dp[i-1][j];
if(j > 1 && i > 1 && dp[i-1][j-1] != MinValue){
// 前一天持有,今天获得红利
dp[i][j] = max(dp[i][j], dp[i-1][j-1] + prices[i-1] - prices[i-2]);
}
}
//手中持有股票
for(int j = 2; j <= 5; j+=2){
//前一天未持有股票今天买入
dp[i][j] = dp[i-1][j-1];
if(i > 1 && dp[i-1][j] != MinValue){
// 前一天持有股票今天获得红利
dp[i][j] = max(dp[i][j], dp[i-1][j] + prices[i-1] - prices[i-2]);
}
}
}
int res = 0;
for (int j = 1; j <= 5; j += 2) {
res = max(res, dp[n][j]);
}
return res;
}
};
393. Best Time to Buy and Sell Stock IV
提示:如果K > N/2,那么相当于BTBSS II,否则将 III 扩展到 2K+1,五种阶段不变。
答案:
class Solution {
public:
/**
* @param K: An integer
* @param prices: An integer array
* @return: Maximum profit
*/
int maxProfit(int K, vector<int> &prices) {
// write your code here
const int MinValue = 0x80000000;
int n = prices.size();
if(n == 0) return 0;
int res = 0;
//相当于任意次买卖
if(K > n/2){
for (int i = 1; i < n; i++) {
if (prices[i] - prices[i - 1] > 0) {
res += prices[i] - prices[i - 1];
}
}
return res;
}
vector<vector<int>> dp(n + 1,vector<int>(2*K + 2));
for(int k = 1; k <= 2*K + 1; k++){
dp[0][k] = MinValue;
}
dp[0][1] = 0;
for(int i = 1; i <= n; i++){
//手中未持有股票
for(int j = 1; j <= 2*K + 1; j+=2){
//前一天也未持有股票
dp[i][j] = dp[i-1][j];
if(j > 1 && i > 1 && dp[i-1][j-1] != MinValue){
// 前一天持有,今天获得红利
dp[i][j] = max(dp[i][j], dp[i-1][j-1] + prices[i-1] - prices[i-2]);
}
}
//手中持有股票
for(int j = 2; j <= 2*K + 1; j+=2){
//前一天未持有股票今天买入
dp[i][j] = dp[i-1][j-1];
if(i > 1 && dp[i-1][j] != MinValue){
// 前一天持有股票今天获得红利
dp[i][j] = max(dp[i][j], dp[i-1][j] + prices[i-1] - prices[i-2]);
}
}
}
for (int j = 1; j <= 2*K + 1; j += 2) {
res = max(res, dp[n][j]);
}
return res;
}
};
76. Longest Increasing Subsequence
给定一个整数序列,找到最长上升子序列(LIS),返回LIS的长度。
提示:此题用动态规划时间复杂度为O(N^2),O(NlogN)的二分查找方法放在第七讲。动态规划的思路是建立数组,f[i]表示以i为结尾的最长子序列,转移方程:Dp[i] = max{Dp[j]} + 1
答案:
class Solution {
public:
/**
* @param nums: The integer array
* @return: The length of LIS (longest increasing subsequence)
*/
int longestIncreasingSubsequence(vector<int> nums) {
int f[nums.size()];
int max = 0;
for (int i = 0; i < nums.size(); i++) {
f[i] = 1;
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
f[i] = f[i] > f[j] + 1 ? f[i] : f[j] + 1;
}
}
if (f[i] > max) {
max = f[i];
}
}
return max;
}
};
602. Russian Doll Envelopes
给一定数量的信封,带有整数对 (w, h)
分别代表信封宽度和高度。一个信封的宽高均大于另一个信封时可以放下另一个信封。
求最大的信封嵌套层数。
提示:此题目用动态规划做时间复杂度也是O(N^2)。使用二分法优化的方法后面再讲。
信封存在两个维度,首先贪心按照其中一个维度将信封排序,然后在另一个维度上面寻找最长上升子序列。
如排序按照长度从小到大,同样长度按照宽度从大到小排序
答案:
bool cmp(const pair<int,int>&x, const pair<int, int>&y) {
return x.first != y.first ? x.first < y.first : x.second > y.second;
}
class Solution {
public:
/*
* @param envelopes: a number of envelopes with widths and heights
* @return: the maximum number of envelopes
*/
int maxEnvelopes(vector<pair<int, int>>& envelopes) {
// write your code here
int n = envelopes.size();
if (n == 0) {
return 0;
}
sort(envelopes.begin(), envelopes.end(), cmp);
vector<int> f(n);
int i, j, res = 0;
for(i = 0; i < n; i++){
f[i] = 1;
for(j = 0; j < i; j++){
if(envelopes[j].first < envelopes[i].first && envelopes[j].second < envelopes[i].second){
f[i] = max(f[i], f[j] + 1);
}
}
res = max(res, f[i]);
}
return res;
}
};