刷题日记:面试经典 150 题 DAY2
189. 轮转数组
原题链接 189. 轮转数组
最容易想到的,开辟一个新数组来暂存最后结果,全部轮转完后再复制回去。
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int len = nums.size();
int dist[len];
for(int i = 0;i < len;i++) {
dist[(i+k)%len] = nums[i];
}
for(int i = 0;i < len;i++) {
nums[i] = dist[i];
}
}
};
有两种原地轮转的方法:第一种就是使用一个变量进行暂存,不断地将nums[a]
交换到nums[(a+k)%len]
,可以通过理论分析证明,这样做每一趟能排列
l
c
m
(
l
e
n
,
k
)
/
k
lcm(len,k)/k
lcm(len,k)/k,共需要
g
c
d
(
l
e
n
,
k
)
gcd(len,k)
gcd(len,k)趟
使用代码实现时,不需要考虑最大公约数啥的,只需要使用变量count
来对已经遍历到的元素进行计数,每一趟的判断就是是否回到了本趟的起点
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int len = nums.size();
int count = 0;
int start = 0;
while(count < len) {
int cur = start;
int prev = nums[cur];
do {
int next = (cur+k)%len;
int temp = prev;
prev = nums[next];
nums[next] = temp;
cur = next;
count++;
}while(cur != start);
start++;
}
}
};
通过观察,可以发现结果可以由三步翻转来进行组合
class Solution {
public:
void rotate(vector<int> &nums, int k) {
k %= nums.size();
reverse(nums.begin(), nums.end());
reverse(nums.begin(), nums.begin() + k);
reverse(nums.begin() + k, nums.end());
}
};
121. 买卖股票的最佳时机
原题链接 121. 买卖股票的最佳时机
第一次做的想法:在每一天,其实只要看看在当天之后股票价格最高是多少即可。
遍历两边,第一遍做辅助数组
e
n
d
m
a
x
[
i
]
=
m
a
x
{
p
r
i
c
e
s
[
i
.
.
.
e
n
d
]
}
endmax[i] = max\{prices[i...end]\}
endmax[i]=max{prices[i...end]},从后向前遍历
第二遍用辅助数组计算在每一天能获得的最大利润,并更新整体的最大利润,从前向后遍历
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
int endmax[len];
int t_max = -1;
for(int i = len-1; i>=0; i--){
if(t_max < prices[i]) {
t_max = prices[i];
}
endmax[i] = t_max;
}
int result = 0;
for(int i = 0;i<len-1;i++) {
int sold = endmax[i+1] - prices[i];
if(sold >result) {
result = sold;
}
}
return result;
}
};
第二遍想,其实遍历一趟就行,同时维护前缀最大值和最小值。只不过当前缀最大值所在的下标已经“落后于”当前前缀最小值时,需要强行更新最大值和最大值下标,来保证最大值至少“不落后于”最小值,符合买卖股票的逻辑
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
int high_index = 0, low_index = 0;
int high = -1;
int low = prices[0];
int result = 0;
for(int i = 0;i < len;i++) {
if(prices[i] > high) {
high_index = i;
high = prices[i];
}
if(prices[i] < low) {
low_index = i;
low = prices[i];
}
if(high_index > low_index) {
result = max(result,high-low);
}
if(high_index < low_index) {
high_index = low_index;
high = low;
}
}
return result;
}
};
122. 买卖股票的最佳时机 II
原题链接 122. 买卖股票的最佳时机 II
动态规划,状态设计
d
p
(
d
a
y
=
i
,
w
h
e
t
h
e
r
h
o
l
d
s
=
0
/
1
)
dp(day=i, whether \;holds = 0/1)
dp(day=i,whetherholds=0/1),表示在第i天,手里是否持有股票时,能获得的最大利益
状态转移
- 手里不持有股票:要么与前一天保持一致,要么是前一天持有,今天卖掉了 d p ( i , 0 ) = m a x { d p ( i − 1 , 0 ) , d p ( i − 1 , 1 ) + p r i c e i } dp(i,0)=max\{dp(i-1,0),dp(i-1,1)+price_i\} dp(i,0)=max{dp(i−1,0),dp(i−1,1)+pricei}
- 手里持有股票:要么与前一天保持一致,要么是前一天不持有,今天买了一支 d p ( i , 1 ) = m a x { d p ( i − 1 , 1 ) , d p ( i − 1 , 0 ) − p r i c e i } dp(i,1)=max\{dp(i-1,1),dp(i-1,0)-price_i\} dp(i,1)=max{dp(i−1,1),dp(i−1,0)−pricei}
注意到每一天的状态都只与前一天有关,所以第一维可以被抛弃
class Solution {
public:
int maxProfit(vector<int>& prices) {
int dp[2];
dp[0] = 0;
dp[1] = -prices[0];
for(int i = 1;i < prices.size();i++) {
int dp0 = max(dp[0],dp[1]+prices[i]);
int dp1 = max(dp[1],dp[0]-prices[i]);
dp[0] = dp0;
dp[1] = dp1;
}
return dp[0];
}
};
55. 跳跃游戏
原题链接 55. 跳跃游戏
考虑一个范围bound
,代表能跳到的最大距离,让人物在bound
内一格一格跳,如果人物在抵达数组尾前先抵达了bound
,则说明不能到达结尾
class Solution {
public:
bool canJump(vector<int>& nums) {
int bound = 0;
int i = 0;
do {
bound = max(bound,i+nums[i]);
i++;
}while(i < nums.size() && i <= bound );
return i >= nums.size();
}
};
45. 跳跃游戏 II
原题链接: 45. 跳跃游戏 II
第一遍做,想到的类似dp的做法。step[i]
表示跳到位置i
最少需要几步,所以自然想到从前向后遍历,在位置i
更新step[i+1 ... i+nums[i]]
,更新方式step[j] = min(step[i] + 1,step[j])
class Solution {
public:
int jump(vector<int>& nums) {
int len = nums.size();
int step[len];
memset(step,0x7f,sizeof(step));
step[0] = 0;
for(int i = 0; i < len;i++) {
for(int j = i+1;j < len && j <= i+nums[i];j++) {
step[j] = min(step[i] + 1,step[j]);
}
}
return step[len-1];
}
};
题解中有一个 O ( N ) O(N) O(N)的做法,leetcode上给出的解释我没看懂,在这里给出我的思考。先看代码
class Solution {
public:
int jump(vector<int>& nums) {
int len = nums.size();
int prev_bound = 0, bound = 0;
int step = 0;
for(int i = 0;i < len-1;i++) {
bound = max(bound,i+nums[i]);
if(i == prev_bound) {
prev_bound = bound;
step++;
}
}
return step;
}
};
我认为可以解释为我第一遍做法的优化
- 第一点,step数组可以被优化成一个单独的量,毕竟最后只需要
step[len-1]
- step数组的更新,变得十分简单,这基于以下观察
- step数组是 到达位置i最少的跳跃次数
- 其实最后step数组的形状是,几个连续的区间,区间之间相差1
- 这些区间的分界线,其实就是在55. 跳跃游戏中我们的各个bound
- 所以我们需要记录的只有,step,一个老bound,一个新bound,其暗含的意义其实是
arr_step[prev_bound ... bound] = step
- step数组是 到达位置i最少的跳跃次数