谈心算法核心是拿局部最优逐渐实现整体的最优,没有固定模版,对于具体问题要看能不能按照谈心算法的思想实现
这一题的思路可分别从满足大胃口和小胃口入手,这里用到双指针,避免两个for循环。让数组先从小到大排序,然后按照从小到大、从左到右比较的顺序来进行比较
class Solution {
public int findContentChildren(int[] g, int[] s) {
int res = 0;//记录满足的数量
Arrays.sort(g);
Arrays.sort(s);
for (int i = 0,j = 0;i < g.length && j < s.length;) {
if (g[i] > s[j]) j++;
else if (g[i] <= s[j]) {
++res;
++i;
++j;
}
}
return res;
}
}
关键是怎样利用谈心思想找到数组的波峰波谷
class Solution {
public int wiggleMaxLength(int[] nums) {
//利用树形结合,将数据的分布画出,每一个使得前后相邻差值发生变化的元素,就可以使得节点数加1
int resu = 1;//默认一个元素可以行成一个摆动
int preDiff =0;//默认其实点的diff值为0
int curDiff = 0;
for(int i = 0; i < nums.length -1; ++i) {
//本质是从左到右找极值点
curDiff = nums[i+1] - nums[i];//这样可以解决前两个点,这样每次得到的i都是位于波峰还有波谷
if (curDiff > 0 && preDiff <=0 || curDiff < 0 && preDiff >=0) {
resu++;
preDiff = curDiff;//异号就进行交换
}
}
return resu;
}
}
贪心算法在这里要的是局部数组之和大于0的部分,使得结果转为0的节点必然不是,接着从下一个大于0的节点开始记录就可以
class Solution {
public int maxSubArray(int[] nums) {
//记录遍历过程中的累加和,当出现结果为负数时就清零,从下一个节点重新开始计算
int max = Integer.MIN_VALUE;
int count=0;
for (int i = 0; i < nums.length; ++i) {
count += nums[i];
max = count > max ? count : max;
if (count < 0) count=0;//说明nums【i】为负数,那么最大子数组一定不包含该节点。从下一个大于0的节点重新开始探索。
}
return max;
}
}
注意核心方法是找到一个排序的规则,然后按照规则来选择相对应的对象从而得到结果。
这一题参考左程云的贪心算法讲解。
class Solution {
public static class Node {
int p;
int c;
public Node(int p, int c) {
this.p = p;
this.c = c;
}
}
public static class MinCostComparator implements Comparator<Node> {
@Override
public int compare(Node o1, Node o2) {
return o1.c - o2.c;
}
}
public static class MaxProfitComparator implements Comparator<Node> {
@Override
public int compare(Node o1, Node o2) {
return o2.p - o1.p;
}
}
public static int findMaximizedCapital(int k, int w, int[] profits, int[] capital) {
PriorityQueue<Node> minCostQ = new PriorityQueue<>(new MinCostComparator());
PriorityQueue<Node> maxProfitQ = new PriorityQueue<>(new MaxProfitComparator());
//将所有项目按照花费从小到大扔到小根堆中
for (int i = 0; i < profits.length; ++i) {
minCostQ.add(new Node(profits[i], capital[i]));
}
//接着按照能够本钱往大顶堆中放入能够承接的项目,选择最顶上利润最大的来做
for (int i = 0; i < k; ++i) {
while (!minCostQ.isEmpty() && minCostQ.peek().c <= w) {
maxProfitQ.add(minCostQ.poll());
}
if (maxProfitQ.isEmpty()) {//如果没有项目可以做
return w;
}
w += maxProfitQ.poll().p;
}
return w;
}
}
关键是从第一个点开始的跳动位置范围是否包含住数组的结尾。根据for循环结果来更改i的界限,那么对于起始点为0的也能有效去除
class Solution {
public boolean canJump(int[] nums) {
//通过倒序来进行判断
if (nums.length == 1) return true;
int res = 0;
for (int i = 0; i <= res; ++i) {//for循环界限可以变化
res = Math.max(i + nums[i], res);
if (res >= nums.length - 1) return true;
}
return false;
}
}
这一题也是贪心算法的很好体现,先尽量让所有能转为正数的数转为正数,然后求和,之后再次对数组进行排序,将最小值放在数组头部,根据k剩余情况进行判断。看leetcode下面的解析,思路比代码随想录的还要棒
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
Arrays.sort(nums);//将数组从小到大排列
int sum = 0;
for (int i = 0 ; i < nums.length ; ++i) {//把能转的负数都转为正数
if (nums[i] < 0 && k > 0) {
nums[i] = -nums[i];
k--;
}
sum += nums[i];
}
Arrays.sort(nums);//这里把数组剩下最小的数放在头部
//如果k已经用完,那么结果就是sum
//如果k没有用完,剩余的k为偶数,那么结果仍未sum,为奇数,那么将nums[0]变为负值,结果减去其2倍就可以
return sum = (k % 2) == 0 ? sum : (sum - 2 * nums[0]);
}
}
参考之前做过的一道题,如果总体油耗满足能够跑满一圈,找开始位置,就是看前面的累加之和,如果有小于0的情况,那么满足要求的点必然在该点之后,知道找到最后损耗之和始终大于0的起始点
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
//根据线总体再局部的思路,从头开始如果出现累加和小于0,那么开始的位置取下一个,知道下一个是的当前的消耗大于0
int start = 0, curSum = 0, totalSum = 0;
for (int i = 0; i < gas.length; ++i) {
curSum += gas[i] - cost[i];
totalSum += gas[i] - cost[i];
if (curSum < 0) {
start = i + 1;
curSum = 0;
}
}
if (totalSum < 0) return -1;//证明可以跑完一圈。
return start;
}
}
关键是对每次收到钱的找零判断,要注意总体能找0,但是过程有错误的地方
class Solution {
public boolean lemonadeChange(int[] bills) {
//记录将10块钱找情之后的5、10、20剩余数量
int m = 0, n = 0, q = 0;
for (int i = 0; i < bills.length; ++i) {
if (bills[i] == 5) ++m;
if (bills[i] == 10) {
++n;
--m;
if (m < 0) return false;
}
if (bills[i] == 20) {
++q;
//20元的找零要根据剩余钱的情况进行判断,5元10元都有的情况,一定能完成一次找零
//只有5元的情况,只有5元数量大于3张才能完成找零
//剩余情况都为不能实现,所以要尽可能在遍历完成之前判断出对错,不然会出现过程中不能找零,但是最终可以实现的错误情况
if (m > 0 && n >0) {
--m;
--n;
// } else if (m > 0 && n == 0) {
// if (m / 3 >= 1) {
// m -= 3;
// } else return false;
// } else return false;
} else if (m / 3 >= 1) {
m -= 3;
} else return false;
}
}
return true;
}
}
对于这种考虑双重因素的排序,一般按照其中一个升序,再按照一中一个降序,这样可以减少交换的次数,这一题把身高按照降序排列,这样排在前面的都是高于后面的,之后再链表中重新在索引位置插入元素就可以
class Solution {
public int[][] reconstructQueue(int[][] people) {
//将people里面的数据在身高相同时按照身高的ki升序排列,不相同时按照身高降序
Arrays.sort(people, (o1, o2) -> {
if (o1[0] == o2[0]) return o1[1] - o2[1];
else return o2[0] - o1[0];
});
LinkedList<int[]> list = new LinkedList<>();//list可以再指定索引位置出添加
for (int[] p : people) {
list.add(p[1], p);
}
return list.toArray(new int[people.length][]);//二维数组的创建
}
}
还是两个指标的排序选择,然后对于边界end的更新,这里对于两个数的比较,要注意对于int的边界值比较,最好用integer.compare()来比较
class Solution {
public int findMinArrowShots(int[][] points) {
//涉及到两个指标,那么就牵扯到按照其中一个元素排序,这里按照start
//这里注意如果两个球不重叠,那么就要多射一键,当两个气球重叠时,将重叠的边界按照两个气球最小的右边界来取
Arrays.sort(points, (o1, o2) -> Integer.compare(o1[0],o2[0]));
//这里注意如果使用o1[0]-o2[0],对于极限边界值没法判断
int count = 1;//因为至少有一个气球,至少要射一箭
for (int i = 1; i < points.length; ++i) {
if (points[i][0] > points[i-1][1]){//两个气球不重叠
count++;
} else {//两个气球重叠,更新第i个球的end边界,取两个球的最小值
//如果下一个球的satrt大于这个end那么需要再加一箭,否则重叠
points[i][1] = Math.min(points[i][1], points[i-1][1]);
}
}
return count;
}
}