目录
算法----贪心
在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择
分发饼干
class Solution { // 思路1:优先考虑饼干,小饼干先喂饱小胃口 public int findContentChildren(int[] g, int[] s) { Arrays.sort(g); Arrays.sort(s); int start = 0; int count = 0; for (int i = 0; i < s.length && start < g.length; i++) { if (s[i] >= g[start]) { start++; count++; } } return count; } } class Solution { // 思路2:优先考虑胃口,先喂饱大胃口 public int findContentChildren(int[] g, int[] s) { Arrays.sort(g); Arrays.sort(s); int count = 0; int start = s.length - 1; // 遍历胃口 for (int index = g.length - 1; index >= 0; index--) { if(start >= 0 && g[index] <= s[start]) { start--; count++; } } return count; } }
摆动序列(376)
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。
例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。
//贪心 class Solution { public int wiggleMaxLength(int[] nums) { if (nums.length <= 1) { return nums.length; } //当前差值 int curDiff = 0; //上一个差值 int preDiff = 0; int count = 1; for (int i = 1; i < nums.length; i++) { //得到当前差值 curDiff = nums[i] - nums[i - 1]; //如果当前差值和上一个差值为一正一负 //等于0的情况表示初始时的preDiff if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)) { count++; preDiff = curDiff; } } return count; } } // DP class Solution { public int wiggleMaxLength(int[] nums) { // 0 i 作为波峰的最大长度 // 1 i 作为波谷的最大长度 int dp[][] = new int[nums.length][2]; dp[0][0] = dp[0][1] = 1; for (int i = 1; i < nums.length; i++){ //i 自己可以成为波峰或者波谷 dp[i][0] = dp[i][1] = 1; for (int j = 0; j < i; j++){ if (nums[j] > nums[i]){ // i 是波谷 dp[i][1] = Math.max(dp[i][1], dp[j][0] + 1); } if (nums[j] < nums[i]){ // i 是波峰 dp[i][0] = Math.max(dp[i][0], dp[j][1] + 1); } } } return Math.max(dp[nums.length - 1][0], dp[nums.length - 1][1]); } }
最大子序和
class Solution { public int maxSubArray(int[] nums) { if (nums.length == 1){ return nums[0]; } int sum = Integer.MIN_VALUE; int count = 0; for (int i = 0; i < nums.length; i++){ count += nums[i]; sum = Math.max(sum, count); // 取区间累计的最大值(相当于不断确定最大子序终止位置) if (count <= 0){ count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和 } } return sum; } } // DP 方法 class Solution { public int maxSubArray(int[] nums) { int ans = Integer.MIN_VALUE; int[] dp = new int[nums.length]; dp[0] = nums[0]; ans = dp[0]; for (int i = 1; i < nums.length; i++){ dp[i] = Math.max(dp[i-1] + nums[i], nums[i]); ans = Math.max(dp[i], ans); } return ans; } }
买卖股票的最佳时机||(122)
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
// 贪心思路 class Solution { public int maxProfit(int[] prices) { int result = 0; for (int i = 1; i < prices.length; i++) { result += Math.max(prices[i] - prices[i - 1], 0); } return result; } } // 动态规划 class Solution { public int maxProfit(int[] prices) { // [天数][是否持有股票] int[][] dp = new int[prices.length][2]; // base case dp[0][0] = 0; dp[0][1] = -prices[0]; for (int i = 1; i < prices.length; i++) { // dp公式 dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]); } return dp[prices.length - 1][0]; } }
跳跃游戏(55)
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
class Solution { public boolean canJump(int[] nums) { if (nums.length == 1) { return true; } //覆盖范围, 初始覆盖范围应该是0,因为下面的迭代是从下标0开始的 int coverRange = 0; //在覆盖范围内更新最大的覆盖范围 for (int i = 0; i <= coverRange; i++) { coverRange = Math.max(coverRange, i + nums[i]); if (coverRange >= nums.length - 1) { return true; } } return false; } }
跳跃游戏||
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
// 版本一 class Solution { public int jump(int[] nums) { if (nums == null || nums.length == 0 || nums.length == 1) { return 0; } //记录跳跃的次数 int count=0; //当前的覆盖最大区域 int curDistance = 0; //最大的覆盖区域 int maxDistance = 0; for (int i = 0; i < nums.length; i++) { //在可覆盖区域内更新最大的覆盖区域 maxDistance = Math.max(maxDistance,i+nums[i]); //说明当前一步,再跳一步就到达了末尾 if (maxDistance>=nums.length-1){ count++; break; } //走到当前覆盖的最大区域时,更新下一步可达的最大区域 if (i==curDistance){ curDistance = maxDistance; count++; } } return count; } } // 版本二 class Solution { public int jump(int[] nums) { int result = 0; // 当前覆盖的最远距离下标 int end = 0; // 下一步覆盖的最远距离下标 int temp = 0; for (int i = 0; i <= end && end < nums.length - 1; ++i) { temp = Math.max(temp, i + nums[i]); // 可达位置的改变次数就是跳跃次数 if (i == end) { end = temp; result++; } } return result; } }
K次取反后最大化的数组和(1005)
给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。)
以这种方式修改数组后,返回数组可能的最大和。
class Solution { public int largestSumAfterKNegations(int[] nums, int K) { // 将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小 nums = IntStream.of(nums) .boxed() .sorted((o1, o2) -> Math.abs(o2) - Math.abs(o1)) .mapToInt(Integer::intValue).toArray(); int len = nums.length; for (int i = 0; i < len; i++) { //从前向后遍历,遇到负数将其变为正数,同时K-- if (nums[i] < 0 && K > 0) { nums[i] = -nums[i]; K--; } } // 如果K还大于0,那么反复转变数值最小的元素,将K用完 if (K % 2 == 1) nums[len - 1] = -nums[len - 1]; return Arrays.stream(nums).sum(); } } class Solution { public int largestSumAfterKNegations(int[] A, int K) { if (A.length == 1) return k % 2 == 0 ? A[0] : -A[0]; Arrays.sort(A); int sum = 0; int idx = 0; for (int i = 0; i < K; i++) { if (i < A.length - 1 && A[idx] < 0) { A[idx] = -A[idx]; if (A[idx] >= Math.abs(A[idx + 1])) idx++; continue; } A[idx] = -A[idx]; } for (int i = 0; i < A.length; i++) { sum += A[i]; } return sum; } }
加油站(134)
在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。
你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。
如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。
// 解法1全局考虑 class Solution { public int canCompleteCircuit(int[] gas, int[] cost) { int sum = 0; int min = 0; for (int i = 0; i < gas.length; i++) { sum += (gas[i] - cost[i]); min = Math.min(sum, min); } if (sum < 0) return -1; if (min >= 0) return 0; for (int i = gas.length - 1; i > 0; i--) { min += (gas[i] - cost[i]); if (min >= 0) return i; } return -1; } } // 解法2 class Solution { public int canCompleteCircuit(int[] gas, int[] cost) { int curSum = 0; int totalSum = 0; int index = 0; for (int i = 0; i < gas.length; i++) { curSum += gas[i] - cost[i]; totalSum += gas[i] - cost[i]; if (curSum < 0) { index = (i + 1) % gas.length ; curSum = 0; } } if (totalSum < 0) return -1; return index; } }
分发糖果(135)
你需要按照以下要求,帮助老师给这些孩子分发糖果:
-
每个孩子至少分配到 1 个糖果。
-
相邻的孩子中,评分高的孩子必须获得更多的糖果。
那么这样下来,老师至少需要准备多少颗糖果呢?
class Solution { /** 分两个阶段 1、起点下标1 从左往右,只要 右边 比 左边 大,右边的糖果=左边 + 1 2、起点下标 ratings.length - 2 从右往左, 只要左边 比 右边 大,此时 左边的糖果应该 取本身的糖果数(符合比它左边大) 和 右边糖果数 + 1 二者的最大值,这样才符合 它比它左边的大,也比它右边大 */ public int candy(int[] ratings) { int[] candyVec = new int[ratings.length]; candyVec[0] = 1; for (int i = 1; i < ratings.length; i++) { if (ratings[i] > ratings[i - 1]) { candyVec[i] = candyVec[i - 1] + 1; } else { candyVec[i] = 1; } } for (int i = ratings.length - 2; i >= 0; i--) { if (ratings[i] > ratings[i + 1]) { candyVec[i] = Math.max(candyVec[i], candyVec[i + 1] + 1); } } int ans = 0; for (int s : candyVec) { ans += s; } return ans; } }
柠檬水找零(860)
在柠檬水摊上,每一杯柠檬水的售价为 5 美元。
顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
注意,一开始你手头没有任何零钱。
如果你能给每位顾客正确找零,返回 true ,否则返回 false 。
class Solution { public boolean lemonadeChange(int[] bills) { int five = 0; int ten = 0; for (int i = 0; i < bills.length; i++) { if (bills[i] == 5) { five++; } else if (bills[i] == 10) { five--; ten++; } else if (bills[i] == 20) { if (ten > 0) { ten--; five--; } else { five -= 3; } } if (five < 0 || ten < 0) return false; } return true; } }
根据身高体重建队列(406)
假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。
请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。
class Solution { public int[][] reconstructQueue(int[][] people) { // 身高从大到小排(身高相同k小的站前面) Arrays.sort(people, (a, b) -> { if (a[0] == b[0]) return a[1] - b[1]; return b[0] - a[0]; }); LinkedList<int[]> que = new LinkedList<>(); for (int[] p : people) { que.add(p[1],p); } return que.toArray(new int[people.length][]); } }
用最少数量的箭引爆气球
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。
一支弓箭可以沿着 x 轴从不同点完全垂直地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
class Solution { public int findMinArrowShots(int[][] points) { if (points.length == 0) return 0; Arrays.sort(points, (o1, o2) -> Integer.compare(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 { points[i][1] = Math.min(points[i][1],points[i - 1][1]); } } return count; } }
无重叠区间(435)
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
class Solution { public int eraseOverlapIntervals(int[][] intervals) { Arrays.sort(intervals, (a, b) -> { // 按照区间右边界升序排序 return a[1] - b[1]; }); int count = 0; int edge = Integer.MIN_VALUE; for (int i = 0; i < intervals.length; i++) { // 若上一个区间的右边界小于当前区间的左边界,说明无交集 if (edge <= intervals[i][0]) { edge = intervals[i][1]; } else { count++; } } return count; } } class Solution { public int eraseOverlapIntervals(int[][] intervals) { Arrays.sort(intervals,(a,b)->{ return Integer.compare(a[0],b[0]); }); int remove = 0; int pre = intervals[0][1]; for(int i=1;i<intervals.length;i++){ if(pre>intervals[i][0]) { remove++; pre = Math.min(pre,intervals[i][1]); } else pre = intervals[i][1]; } return remove; } }
划分字母区间(763)
字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。
class Solution { public List<Integer> partitionLabels(String S) { List<Integer> list = new LinkedList<>(); int[] edge = new int[26]; char[] chars = S.toCharArray(); 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) { list.add(i - last); last = i; } } return list; } } class Solution{ /*解法二*/ public int[][] findPartitions(String s) { List<Integer> temp = new ArrayList<>(); int[][] hash = new int[26][2];//26个字母2列 表示该字母对应的区间 for (int i = 0; i < s.length(); i++) { //更新字符c对应的位置i char c = s.charAt(i); if (hash[c - 'a'][0] == 0) hash[c - 'a'][0] = i; hash[c - 'a'][1] = i; //第一个元素区别对待一下 hash[s.charAt(0) - 'a'][0] = 0; } List<List<Integer>> h = new LinkedList<>(); //组装区间 for (int i = 0; i < 26; i++) { //if (hash[i][0] != hash[i][1]) { temp.clear(); temp.add(hash[i][0]); temp.add(hash[i][1]); //System.out.println(temp); h.add(new ArrayList<>(temp)); // } } // System.out.println(h); // System.out.println(h.size()); int[][] res = new int[h.size()][2]; for (int i = 0; i < h.size(); i++) { List<Integer> list = h.get(i); res[i][0] = list.get(0); res[i][1] = list.get(1); } return res; } public List<Integer> partitionLabels(String s) { int[][] partitions = findPartitions(s); List<Integer> res = new ArrayList<>(); Arrays.sort(partitions, (o1, o2) -> Integer.compare(o1[0], o2[0])); int right = partitions[0][1]; int left = 0; for (int i = 0; i < partitions.length; i++) { if (partitions[i][0] > right) { //左边界大于右边界即可纪委一次分割 res.add(right - left + 1); left = partitions[i][0]; } right = Math.max(right, partitions[i][1]); } //最右端 res.add(right - left + 1); return res; } }
合并区间(56)
给出一个区间的集合,请合并所有重叠的区间
class Solution { public int[][] merge(int[][] intervals) { List<int[]> res = new LinkedList<>(); Arrays.sort(intervals, (o1, o2) -> Integer.compare(o1[0], o2[0])); int start = intervals[0][0]; for (int i = 1; i < intervals.length; i++) { if (intervals[i][0] > intervals[i - 1][1]) { res.add(new int[]{start, intervals[i - 1][1]}); start = intervals[i][0]; } else { intervals[i][1] = Math.max(intervals[i][1], intervals[i - 1][1]); } } res.add(new int[]{start, intervals[intervals.length - 1][1]}); return res.toArray(new int[res.size()][]); } } // 版本2 class Solution { public int[][] merge(int[][] intervals) { LinkedList<int[]> res = new LinkedList<>(); Arrays.sort(intervals, (o1, o2) -> Integer.compare(o1[0], o2[0])); res.add(intervals[0]); for (int i = 1; i < intervals.length; i++) { if (intervals[i][0] <= res.getLast()[1]) { int start = res.getLast()[0]; int end = Math.max(intervals[i][1], res.getLast()[1]); res.removeLast(); res.add(new int[]{start, end}); } else { res.add(intervals[i]); } } return res.toArray(new int[res.size()][]); } }
单调递增的数字(738)
给定一个非负整数 N,找出小于或等于 N 的最大的整数,同时这个整数需要满足其各个位数上的数字是单调递增。
class Solution { public int monotoneIncreasingDigits(int n) { String s = String.valueOf(n); char[] chars = s.toCharArray(); int start = s.length(); for (int i = s.length() - 2; i >= 0; i--) { if (chars[i] > chars[i + 1]) { chars[i]--; start = i+1; } } for (int i = start; i < s.length(); i++) { chars[i] = '9'; } return Integer.parseInt(String.valueOf(chars)); } }
买卖股票的最佳时机含手续费(714)
给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
// 贪心思路 class Solution { public int maxProfit(int[] prices, int fee) { int buy = prices[0] + fee; int sum = 0; for (int p : prices) { if (p + fee < buy) { buy = p + fee; } else if (p > buy){ sum += p - buy; buy = p; } } return sum; } } class Solution { // 动态规划 public int maxProfit(int[] prices, int fee) { if (prices == null || prices.length < 2) { return 0; } int[][] dp = new int[prices.length][2]; // bad case dp[0][0] = 0; dp[0][1] = -prices[0]; for (int i = 1; i < prices.length; i++) { dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee); dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]); } return dp[prices.length - 1][0]; } }
监控二叉树(968)
给定一个二叉树,我们在树的节点上安装摄像头。
节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。
计算监控树的所有节点所需的最小摄像头数量。
class Solution { int res=0; public int minCameraCover(TreeNode root) { // 对根节点的状态做检验,防止根节点是无覆盖状态 . if(minCame(root)==0){ res++; } return res; } /** 节点的状态值: 0 表示无覆盖 1 表示 有摄像头 2 表示有覆盖 后序遍历,根据左右节点的情况,来判读 自己的状态 */ 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){ //(2,2) return 0; }else if(left==0||right==0){ // 左右节点都是无覆盖状态,那 根节点此时应该放一个摄像头 // (0,0) (0,1) (0,2) (1,0) (2,0) // 状态值为 1 摄像头数 ++; res++; return 1; }else{ // 左右节点的 状态为 (1,1) (1,2) (2,1) 也就是左右节点至少存在 1个摄像头, // 那么本节点就是处于被覆盖状态 return 2; } } }