==========================================
1.树
剑指68 二叉搜索树的最近公共祖先
如果一个root是pq的公共祖先,要么,pq在root的两侧,要么,p或者q是root,另外一个是其左右子树中的节点。利用二叉树的性质,小于根节点的都在根节点的左侧,大于根节点的都在根节点的右侧。他们的公共祖先应该就在从根节点到他们的第一个大于a小于b的分叉节点。
剑指68-2 二叉树的最近公共祖先
考虑对二叉树后序遍历,搜索pq节点,找到后返回,自底向上回溯。
当一个节点第一次同时左右子树都找到pq两个节点的时候,说明他就是最近公共祖先,我们对其返回。
当前一个root左子树有p或者q,而右子树没有,要么,pq均在左子树,这个左子树节点就是作为公共祖先返回,要么,pq在最近公共祖先的两侧,那么返回该节子点作为指示该子树包含一个节点。
如果root两个子树搜都没搜到,说明不可能为祖先,返回空节点
==========================================
2、链表
==========================================
3、买卖股票
121. 买卖股票的最佳时机
Dp 思想, 一维 每天有两种状态,持有股票 buy 不持有股票 sell。初始化buy为负无穷,sell为卖出,设为0。每天判断是否是最低值,是否有必要买入。然后判断今天卖出的利润是不是比之前的利润高,如果是卖出。
int buy = INT_MIN, sell = 0;
for (const int & x : prices)
{
buy = max(buy, -x);
sell = max(sell, buy + x);
}
return sell;
}
122. 买卖股票的最佳时机 II
你可以尽可能地完成更多的交易(多次买卖一支股票)
1.贪心算法,一次遍历,只要当前股票价值递增就保留计算利润,只要股票价格降低就重新买入。
2、dp, 二维dp,一个表示持有股票的收益,一个表示不持有股票的收益。当天持有股票的收益为max(昨天不持有股票的收益减去今天买股票的钱,昨天持有股票今天不做变动)。当天不持有股票的收益为max(昨天持有股票今天卖出的收益,昨天不持有股票的收益)
3、dp 只有i与i-1之间的状态转换,只需要两个变量就能实现 buy sell。buy初始化为INTMIN,初始时没有收益。sell为卖出股票 ,收益为零。 第i天开始的时候,要根据股价涨跌判断是买入还是卖出,如果价格跌了,考虑昨天卖出,今天买入。如果价格涨了,考虑昨天持有,今天卖出的利润
int buy = INT_MIN, sell = 0;
for (const int & x : prices)
{
buy = max(buy, sell - x);//第i天,如果价格跌了,考虑昨天卖出,今天买入
sell = max(sell, buy + x);//如果价格涨了,考虑昨天持有,今天卖出的利润
}
123. 买卖股票的最佳时机 III
你最多可以完成 两笔 交易。
DP 思路和上一题一样, 考虑状态转移方程,在第i天,如果是第一次买入股票,我们可以选择买也可以选择不买,有buy1 = max( buy1‘’, -prices[i]),buy1’是上一天的。如果是对于第一次卖股票操作而言,我们可以不卖,也可以卖了赚钱,有sell1 = max(sell1, buy1 + prices[i])。考虑到当天买入卖出收益为零,可以直接使用buy1。buy2和sell2是第二次买卖的操作,和第一次意义一样。
然后考虑边界条件。 初始时,没有收入,将所有持有设为负无穷。不持有为0
for (const int & x : prices)
{
buy1 = max(buy1, -x);
sell1 = max(sell1, buy1 + x);
buy2 = max(buy2, sell1 - x);
sell2 = max(sell2, buy2 + x);
}
188. 买卖股票的最佳时机 IV
你最多可以完成 k 笔 交易。
DP 状态 dp[天数][持有与否][第k次卖出] , 考虑状态转移方程,
dp[i][1][k] = max( dp[i-1][1][k], dp[i - 1][0][k-1] - price[i])
dp[i][0][k] = max( dp[i-1][0][k], dp[i - 1][1][k] + price[i])
实际上只有i和i-1的转化,可以降到二维。dp[2][k]
考虑边界条件, 初始时,没有收入,将所有持有收益设为负无穷。不持有为0
for (int i = 0; i < size; i++)
{
dp[1][0] = max(dp[1][0], -prices[i]);
dp[0][0] = max(dp[0][0], dp[1][0] + prices[i]);
for (int j = 1; j < k; j++)
{
dp[1][j] = max(dp[0][j-1] - prices[i], dp[1][j]);
dp[0][j] = max(dp[1][j] + prices[i], dp[0][j]);
}
}
309. 最佳买卖股票时机含冷冻期
你可以尽可能多的完成交易
DP 考虑状态方程 dp【天数】【买入、卖出、冷冻期】。
考虑第i天买入,要么不买,要么只能在至少今天不是冷冻期买,dp[i][0] = max(dp[i -1][0], dp[i-1][2] - prices[i])。
对于卖出,计算在之前持有股票的基础之上卖出,dp[i][1] = dp[i-1] + prices[i]。
对于冷冻期,如果昨天没做操作,那么收益不变,如果昨天卖出了,那么今天就是冷冻期。收益为dp[i][2] = max(dp[i-1][1], dp[i -1][2])。
可以看的出来状态仅仅与昨天今天有关,只需要维护昨天今天收入卖出冷冻六个变量值即可
int buy = INT_MIN, sell0 = 0, sell1 = 0;
int nbuy, nsell0, nsell1;
for (const auto & x : prices)
{
nbuy = max(buy, sell1 - x);
nsell0 = buy + x;
nsell1 = max(sell0, sell1);
buy = nbuy;
sell0 = nsell0;
sell1 = nsell1;
}
return max(sell0, sell1);
714. 买卖股票的最佳时机含手续费
非负整数
fee
代表了交易股票的手续费用。你可以无限次地完成交易
Dp 还是无限买卖的思路,只是卖的时候价格手续费
int buy = INT_MIN + fee, sell = 0; // +fee防止溢出
for (const auto & x : prices)
{
buy = max(buy, sell - x);
sell = max(sell, buy + x - fee);
}
==========================================
4、排序数组
153. 寻找旋转排序数组中的最小值
单调递增数组的旋转。
这个题目的主要思路是用二分法,不断地逼近最小值。
一般而言可以分为两种情况,原数组较大一部分在旋转后的左侧,另一种是在数组右侧。
由此我们可以以数组的中间值进行判断,
当中间值小于数组右侧的值时,说明在当前部分,数组是递增的,最小值应该在包括中间值的左侧
当中间值大于数组右侧的值时,说明右侧发生了旋转,最小值应该在不包括中间值的右侧。
154. 寻找旋转排序数组中的最小值 II
这里有两种方式,
一种和上面的方法一样,不过当中间值等于右侧值的时候,可以想到,最小值可能在中间值的左侧也可能在中间值的右侧。但是既然右边界和中间值相等,必然不会是右边界,所以我们可以缩小右边界。
一种是排除重复元素的,不考虑相等的事了。
剑指03数组中的重复的数字
如果是空间要求O(1)的话可以使用一种原地hash 的。
因为题目中给的元素是0 - n-1的,我们可以想到将元素i放到数组的位置i,
将他放到正确的位置,但是如果这两个地方的元素相等时,说明发生了重复。
==========================================
5、矩阵遍历
给定一个m x n
二维字符网格board
和一个字符串单词word
。如果word
存在于网格中,返回true
;否则,返回false
。
其核心思想是遍历矩阵,对于矩阵中的每一个节点,进行一次dfs,如果当前节点未访问,且当前char和word匹配,设置当前节点为已经访问。然后右下两个方向再次dfs。当最后一个word匹配成功的时候返回1。
一般的思路是用visited,这里可以使用一个‘\0’暂时替换对应的元素,使用完成后再赋给其原来的值。
for(int i = 0; i < r ; i++)
for(int j = 0 ; j < c ;j++)
if(helper(board, i, j, word, 0))