文章目录
- 前言
- 一、划分字母区间(力扣763)
- 二、合并区间(力扣56)
- 三、单调递增的数字(力扣738)
- 四、买卖股票的最佳时机含手续费(力扣714)
- 五、监控二叉树(力扣968)
前言
1、划分字母区间
2、合并区间
3、单调递增的数字
4、买卖股票的最佳时机含手续费
5、监控二叉树
一、划分字母区间(力扣763)
给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。
注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。
返回一个表示每个字符串片段的长度的列表。
思路:
在遍历的过程中相当于是要找每一个字母的边界,如果找到之前遍历过的所有字母的最远边界,说明这个边界就是分割点了。此时前面出现过所有字母,最远也就到这个边界了。
- 统计每一个字符最后出现的位置
- 从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点
class Solution {
public List<Integer> partitionLabels(String s) {
List<Integer> res = new LinkedList<>();
char[] chars = s.toCharArray();
int[] edge = new int[26];
for(int i=0;i<chars.length;i++){
edge[chars[i]-'a']=i;
}
int idx = 0;
int last = -1;
for(int i=0;i<chars.length;i++){
idx = Math.max(idx,edge[chars[i]-'a']);
if(i==idx){
res.add(i-last);
last=i;
}
}
return res;
}
}
二、合并区间(力扣56)
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
思路:
class Solution {
public int[][] merge(int[][] intervals) {
List<int[]> res = new LinkedList<>();
Arrays.sort(intervals,(a,b)->Integer.compare(a[0],b[0]));
int start = intervals[0][0];
int rightBound = intervals[0][1];
for(int i=1;i<intervals.length;i++){
if(intervals[i][0]>rightBound){ //不挨着
res.add(new int[]{start,rightBound});
start = intervals[i][0];
rightBound = intervals[i][1];
}
else{//挨着,更新右边界
rightBound = Math.max(rightBound,intervals[i][1]);
}
}
res.add(new int[]{start,rightBound});
return res.toArray(new int[res.size()][]);
}
}
三、单调递增的数字(力扣738)
当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。
给定一个整数 n ,返回 小于或等于 n 的最大数字,且数字呈 单调递增 。
思路:
例如:98,一旦出现strNum[i - 1] > strNum[i]的情况(非单调递增),首先想让strNum[i - 1]–,然后strNum[i]给为9,这样这个整数就是89,即小于98的最大的单调递增整数
局部最优:遇到strNum[i - 1] > strNum[i]的情况,让strNum[i - 1]–,然后strNum[i]给为9,可以保证这两位变成最大单调递增整数。
是从前向后遍历还是从后向前遍历呢?
举个例子,数字:332,从前向后遍历的话,那么就把变成了329,此时2又小于了第一位的3了,真正的结果应该是299。
所以从前向后遍历会改变已经遍历过的结果!
那么从后向前遍历,就可以重复利用上次比较得出的结果了,从后向前遍历332的数值变化为:332 -> 329 -> 299
class Solution {
public int monotoneIncreasingDigits(int N) {
String s = String.valueOf(N);
char[] chars = s.toCharArray();
int start = chars.length;
for(int i=start-2;i>=0;i--){
if(chars[i]>chars[i+1]){
chars[i]--;
start = i+1;
}
}
for(int i = start;i<chars.length;i++){
chars[i]='9';
}
return Integer.parseInt(String.valueOf(chars));
}
}
四、买卖股票的最佳时机含手续费(力扣714)
给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
思路:
- 用两个变量,一个累加利润用,另一个保存当前最优成本
- 当前成本即 -> 买入价格 + 手续费
- 如果哪天价格比这个成本要高,就可以考虑在这天卖了,这时更新成本为当前的价格
- 如果日后哪天价格更优,可以晚一会儿再卖嘛。
- 当然,如果哪天的 价格 + 成本,要比手里的成本还低,那就在这天去买就好了。
- 综上,这天卖不卖,不确定,能有利润我就先记上,以后要是有更高的,我把又涨了的利润也叠加进去,要是有更低的,我就当之前已经卖了,再买新的。只是模拟出利润最大化。
class Solution {
public int maxProfit(int[] prices, int fee) {
int buy = fee + prices[0];//成本即 -> 买入价格 + 手续费
int res = 0;
for(int i = 1;i<prices.length;i++){
if(prices[i]+fee<buy){ //选一个买入价钱更小的
buy = prices[i]+fee; //更新值
}else if(prices[i]>buy){ //哪天价格比这个成本要高,就可以考虑在这天卖了
res += prices[i]-buy; //利润相加
buy = prices[i]; //更新成本为当前的价格
}
}
return res;
}
}
五、监控二叉树(力扣968)
给定一个二叉树,我们在树的节点上安装摄像头。
节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。
计算监控树的所有节点所需的最小摄像头数量。
思路:
确定遍历顺序:后序遍历(从低向上推导)
如何隔两个节点放一个摄像头:
每个节点可能有三种状态:
该节点无覆盖:0
本节点有摄像头:1
本节点有覆盖:2
单层逻辑处理:
情况1:左右节点都有覆盖,那么此时中间节点应该就是无覆盖的状态了。
情况2:左右节点至少有一个无覆盖的情况,则中间节点(父节点)应该放摄像头
情况3:左右节点至少有一个有摄像头,其父节点就应该是2(覆盖的状态)
情况4:头结点没有覆盖
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int res = 0;
public int minCameraCover(TreeNode root) {
if(minCame(root)==0){ //情况四
res++;
}
return res;
}
public int minCame(TreeNode root){
if(root==null) return 2; //空结点默认有覆盖状态
int left = minCame(root.left);
int right = minCame(root.right);
//情况一:
if(left==2 && right==2) return 0;
//情况二:
else if(left==0 || right==0) {
res++;
return 1;
}
//情况三:
else return 2;
}
}