力扣-贪心算法从易到难-Java

1,038 阅读12分钟

力扣-贪心算法题解(简单、中等、困难)

贪心-简单难度

392 判断子序列

先从小到大排序并求得总和,然后从后往前逐位求和,假如得到的和超过了总和的一半,说明稳了


class Solution {
    public boolean isSubsequence(String s, String t) {
        int index = -1;
        for(char c:s.toCharArray){
            index = t.indexOf(c,index+1);
            if(index == -1)
                return false;
        }
        return true;
    }
}

1403 非递增顺序的最小子序列

先排序,求总和 sum ,然后再倒序求和,当所求的和大于总和 sum 的一半时,就符合条件了


class Solution {
    public List<Integer> minSubsequence(int[] nums) {
        List<Integer> list = new ArrayList<Integer>();
        int total = 0;
        for(int s:nums){
            total += s;
        }
        Arrays.sort(nums);
        int temp = 0;
        for(int i = nums.length() - 1;i >= 0;i--){
            temp += num[i];
            list.add(nums[i]);
            if(2 * temp > total){
                break;
            }
        }
        return list;
    }
}

1518 换酒问题

  • 解法1:直接模拟过程

class Solution {
    public int numWaterBottles(int numBottles, int numExchange) {
        int bottle = numBottles,ans = numBottles;
        while(bottle >= numExchange){
            bottle -= numExchange;
            ++ans;
            ++bottle;
        }
        return ans;
    }
}

  • 解法2:数学解法(太屌了大佬)

思路是这样的: 按价值来算。 假设酒瓶的价值为1,那么所有酒的总价值就是 numBottles * numExchange 那么去掉瓶子,我们能喝到的酒的价就是 numExchange-1 妙哉!


class Solution {
    public int numWaterBottles(int numBottles, int numExchange) {
        return (numBottles * numExchange-1)/(numExchange-1);
    }
}

1046 最后一块石头的重量

建立一个大根堆,然后每次都取出最顶上两个去碰撞,然后得到小石头再放进堆里面,最后就只剩下最小的那个了


import java.util.PriorityQueue;

public class Solution {
    public int lastStoneWeight(int[] stones) {
        int len = stones.length;
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(len,(o1,o2)->-o1+o2);
        for(int stone:stones){
            maxHeap.add(stone);
        }
        while(maxHeap.size() >= 2){
            Integer head1 = maxHeap.poll();
            Integer head2 = maxHeap.poll();

            maxHeap.offer(head1 - head2);
        }
        return maxHeap.poll();
    }
}

1005 K次取反后最大化的数组和

大致思路: 如果 K >0,就取数组 A 的最小值并取反 否则对数组 A 求和


class Solution {
    public int largestSumAfterKNegations(int[] A, int K) {
        int[] number = new int[201];
        for(int t: A){
            number[t + 100]++;
        }
        int i = 0;
        while(K > 0){
            while(number[i] == 0)
                i++;
            number[i]--;
            number[200 - i]++;
            if(i > 100){
                i = 200 - i;
            }
            K--;
        }
        int sum = 0;
        for(int j = i;j < number.length;j++){
            sum += (j - 100) * number[j];
        }
        return sum;
    }
}

1221 分割平衡字符串

先理解一下题意: 首先定义了什么是平衡字符串:就是一个字符串里面字母L的个数和字母R的个数一样 然后定义了怎么分割:一刀切,切完出来的两个新字符串都得是平衡字符串,不然就不能切 然后求最多能切出来多少个平衡字符串

这个题目的思路也很简单,遍历的过程中,L 的数量跟 R 的相等就切一次

有个巧思,就是用L和R的accii码,然后把字符串看成-3和3的一个数组,求前n个数字和为0的次数


class Solution {
    public int balancedStringSplit(String s) {
    	int stack = 0;
    	int cnt = 0;
    	for(char c:s.toCharArray()){
    		if(c == 'R'){
    			stack++;
    		}
    		else{
    			stack--;
    		}
    		if(stack == 0)
    			cnt++;
    	}
    	return cnt;
    }
}

贪心-中等难度

1578 避免重复字母的最小删除成本

走一次遍历:遇到字符 s[i] 时,如果 s[i+1]s[j] 都是跟它一样的字符的话,统计 s[i..j] 的花费总和 sum ,并保留其中最大的花费 max 。那么 s[i..j] 的最小花费就是 (sum - max)


class Solution {
    public int minCost(String s, int[] cost) {
        int res = 0;
        char[] word = s.toCharArray();
        for(int i = 0;i < word.length;i++){
            char c = word[i];
            int max = cost[i];
            int sum = max;
            while(i + 1 < word.length && word[i + 1] == c){
                sum += cost[++i];
                max = Math.max(max,cost[i]);
            }
            if(sum != max){
                res += sum - max;
            }
        }
        return res;
    }
}



406 根据身高重建队列

大致思路是这样的: 先把所有人按照身高从高到矮排序,准备接下来把他们放进队列中; 放入队列中的规则是,从高到矮,直接看 k 值放进去就好,因为越排在后面的越矮,所以矮的就算放在高的人前面,也不会影响高的人的 k


class Solution {
    public int[][] reconstructQueue(int[][] people) {
        Arrays.sort(people,(o1,o2) -> o1[0] == o2[0] ? o1[1] - o2[1] : o2[0] - o1[0]);

        LinkedList<int[]> list = new LinkedList<>();
        for(int[] i : people){
            list.add(i[1],i);
        }
        return list.toArray(new int[list.size()][2]);
    }
}

1497 检查数组对是否可以被k整除

思路比较容易想: 把原数组的数都对 k 取模,得到的余数+k 变为正数,作为数组下标,用于存储对应的余数值 最后进行配对:配成 k 的一组余数的个数相等&&余数为0的个数为偶数,则可以被 k 整除


class Solution {
    public boolean canArrange(int[] arr, int k) {
        int[] mod = new int[k];
        for(int i:arr){
            mod[(i % k + k) % k]++; 
        }
        for(int i = 1;i < k;i++){
            if(mod[i] != mod[k - i])
                return false;
        }
        return mod[0] % 2 == 0 ? true:false;
    }
}

1338 数组大小减半

思路: 先排序,然后记录每个数字的个数,存起来,然后再对个数进行排序,然后挑个数最多的开始删,删到超过一半就停


class Solution {
    public int minSetSize(int[] arr) {
        Arrays.sort(arr);
        ArrayList<Integer> cnt = new ArrayList<>();
        cnt.add(1);
        for(int i = 1;i < arr.length;i++){
            if(arr[i] == arr[i - 1])
                cnt.set(cnt.size() - 1,cnt.get(cnt.size() - 1) + 1);
            else cnt.add(1);
        }
        Collections.sort(cnt);
        int num = 0;
        for(int i = cnt.size() - 1;i >= 0;i--){
            num += cnt.get(i);
            if(num * 2 >= arr.length)
                return cnt.size() - i;
        }

        return 0;
    }
}

1405 最长的快乐字符串

直接看注释吧


class Solution {
    public String longestDiverseString(int a, int b, int c) {
        //三种字母以及字母的数量
        MyChar[] myChars = new MyChar[]{
            new MyChar('a',a),
            new MyChar('b',b),
            new MyChar('c',c),
        };

        StringBuilder sb = new StringBuilder();

        while(true){
            //先排个序,很明显,排完序以后,myChars[2]就是字母个数最多的那个
            Arrays.sort(myChars);

            if(sb.length() >= 2 && 
            sb.charAt(sb.length() - 1) == myChars[2].ch && 
            sb.charAt(sb.length() - 2) == myChars[2].ch){
                //当前快乐字符串的最后两个字母如果与最多的字母一样:
                if(myChars[1].count-- > 0){
                    sb.append(myChars[1].ch);
                    //次多的上位
                }else{
                    break;
                }
            }else{
                if(myChars[2].count-- > 0){
                    sb.append(myChars[2].ch);
                }else{
                    break;
                }
            }
        }
        return sb.toString();
    }

    //建立一个MyChar类,用来存放字母并记录数量
    //ComparaTo()方法用来为sort做准备
    private class MyChar implements Comparable{
        char ch;
        int count;

        public MyChar(char ch,int count){
            this.ch = ch;
            this.count = count;
        }
        @Override
        public int compareTo(Object o){
            MyChar other = (MyChar)o;
            return this.count - other.count;
        }
    }
}



1589.所有排列中的最大和

看注释吧


class Solution {
    public int maxSumRangeQuery(int[] nums, int[][] requests) {
        //构造nums数组的差分数列,nums数组表示某一天的权重
        int[] dnums = new int[nums.length];
        dnums[0] = nums[0];
        for(int i = 1;i < nums.length;i++){
            dnums[i] = nums[i] - nums[i - 1];
        }
        //遍历所有的区间,用差分来记录区间的权重
        //也就是每个位置,要被查询到的次数,查询一次就+1
        for(int i = 0;i < requests.length;i++){
            dnums[requests[i][0]]++;
            if(requests[i][1] < dnums.length - 1){
                dnums[requests[i][1] + 1]--;
            }
        }
        //前缀和将差分数组恢复至未差分前的状态
        for(int i = 1;i < dnums.length;i++){
            dnums[i] += dnums[i-1];
        }
        //对计算后的数组dnums和原数组对比,最后得出查询数组对于每个位置的查询次数。
        for(int i = 0;i < nums.length;i++){
            dnums[i] = dnums[i] -nums[i];
        }

        Arrays.sort(dnums);
        Arrays.sort(nums);

        long sum = 0;

        for(int i = 0;i < nums.length;i++){
            sum += dnums[i] * nums[i];
        }
        return (int)(sum % 1000000007);
    }
}

1386 安排电影院座位

用位运算。 能满足条件的莫非三种情况, left,middle,right 。 用二进制表示这三种情况,方便理解、表示以及运算。 (只有八位是因为第一个和第十个座位是没有意义的,根本不影响剩下八个位置的安排,不论是否已经被预约) 其中, 1 表示已经被预约, 0 表示还没有被预约 那么,怎么样才能表示某一列的座位可以安排给一个家庭呢? 只需要跟 left,middle,right 这三种情况进行 按位或 运算 如果有其中一种情况在运算后值保持不变,说明可以安排


class Solution {
    public int maxNumberOfFamilies(int n, int[][] reservedSeats) {
    	int left = 0b11110000;
    	int middle = 0b11000011;
    	int right = 0b00001111;

    	Map<Integer,Integer> occupied = new HashMap<Integer,Integer>();
    	for(int[] seat:reservedSeats){
    		if(seat[1] >= 2 && seat[1] <= 9){
    			int origin = occupied.containsKey(seat[0])?occupied.get(seat[0]):0;
    			int value = origin | (1 << (seat[1] - 2));
    			occupied.put(seat[0],value);
    		}
    	}

    	int ans = (n - occupied.size()) * 2;
    	for(Map.Entry<Integer,Integer> entry:occupied.entrySet()){
    		int row = entry.getKey(),bitmask = entry.getValue();
    		if(((bitmask | left) == left) || ((bitmask | middle) == middle) || ((bitmask | right) == right))
    			++ans;
    	}
    	return ans;
    }
}

1353 最多可以参加的会议数目

这种老套路了 想要尽可能多地参加会议,你就得先参加那种最早结束的会议,才不会太浪费时间 这个要自己理解一下


class Solution {
    public int maxEvents(int[][] events) {
    	Arrays.sort(events,new Comparator<int[]>(){
    		@Override
    		public int compare(int[]a,int[]b){
    			return a[0] - b[0];
    		}
    	});

    	PriorityQueue<Integer> queue = new PriorityQueue<>();
    	int day = 0;
    	int id = 0;
    	int n = events.length;
    	int res = 0;
    	while(id < n || !queue.isEmpty()){
    		if(queue.isEmpty()){
    			queue.add(events[id][1]);
    			day = events[id++][0];
    		}
    		while(id < n && events[id][0] <= day){
    			queue.add(events[id++][1]);
    		}
    		if(queue.peek() >= day){
    			day++;
    			res++;
    		}
    		queue.poll();
    	}
    	return res;
    }
}

842 将数组拆分成斐波那契序列

直接看注释吧!


class Solution {
    List<Integer> ans;

	public List<Integer> splitIntoFibonacci(String S) {

		ans = new ArrayList<>();
		return dfs(0, S, 0, 0, 0) ? ans : new ArrayList<>();
	}
	/**
	* @p : 当前指针指向数组的索引
	* @s : 字符串
	* @pre1 : 前面隔一个的数
	* @pre2 : 前一个数
	* @deep : 当前是第几个数
	**/
	public boolean dfs(int p, String s, int pre1, int pre2, int deep) {
		int length = s.length();
		if (p == length)return deep >= 3;
		
		for (int i = 1; i <= 10; i++) {
			//超出长度或者以0开头直接break;
			if (p + i > length || (s.charAt(p) == '0' && i > 1))break;
			//截取字符串
			String sub = s.substring(p, p + i);
			
			long numL = Long.parseLong(sub);
			//判断是否超出范围,或者deep不是0,1却大于他的前两个数之和
			if (numL > Integer.MAX_VALUE || 
				(deep != 0 && deep != 1 && numL > (pre1 + pre2)))break;
			//转成int
			Integer num = (int) numL;
			//满足条件的数,递归加回溯
			if (deep == 0 || deep == 1 || num.equals(pre1 + pre2)) {
				ans.add(num);
				if (dfs(p + i, s, pre2, num, deep + 1))return true;
				ans.remove(num);
			}
		}
		return false;
	}
}

767 重构字符串


class Solution {
    public String reorganizeString(String S) {
    	int N = S.length();
    	int[] counts = new int[26];
    	for(char c:S.toCharArray()) 
    		counts[c-'a'] += 100;
    	for(int i = 0;i < 26;i++)
    		counts[i] += i;
    	Arrays.sort(counts);

    	char[] ans = new char[N];
    	int t = 1;
    	for(int code:counts){
    		int ct = code / 100;
    		char ch = (char)('a' + (code % 100));
    		if(ct >(N + 1) / 2) return "";
    		for(int i = 0;i < ct;++i){
    			if(t >= N) t = 0;
    			ans[t] = ch;
    			t += 2;
    		}
    	}
    	return String.valueOf(ans);
    }
}


class Solution {
    public String reorganizeString(String S) {
    	int N = S.length();
    	int[] count = new int[26];
    	for(char c: S.toCharArray())
    		count[c - 'a']++;
    	PriorityQueue<MultiChar> pq = new PriorityQueue<MultiChar>((a,b) -> 
    		a.count == b.count ? a.letter - b.letter : b.count - a.count);

    	for(int i = 0;i < 26;i++){
    		if(count[i] > 0){
    			if(count[i] > (N + 1) / 2) return "";
    			pq.add(new MultiChar(count[i],(char)('a' + i)));
    		}
    	}
    	StringBuilder ans =new StringBuilder();
    	while(pq.size() >= 2){
    		MultiChar mc1 = pq.poll();
    		MultiChar mc2 = pq.poll();

    		ans.append(mc1.letter);
    		ans.append(mc2.letter);
    		if(--mc1.count > 0) pq.add(mc1);
    		if(--mc2.count > 0) pq.add(mc2);
    	}
    	if(pq.size() > 0) ans.append(pq.poll().letter);
    	return ans.toString();
    }
}


class MultiChar{
	int count;
	char letter;
	MultiChar(int ct,char ch){
		count = ct;
		letter = ch;
	}
}

1253 重构2行二进制矩阵


class Solution {
    public List<List<Integer>> reconstructMatrix(int upper, int lower, int[] colsum) {
    	int up = upper,lo = lower,sum = 0,len = colsum.length;
    	List<List<Integer>> list = new ArrayList<>();
    	for(int i = 0;i < len;i++){
    		if(colsum[i] == 2){
    			up--;
    			lo--;
    		}
    		else if(colsum[i] == 1){
    			sum++;
    		}
    	}
    	if(up + lo != sum || up < 0 || lo < 0){
    		return list;
    	}
    	List<Integer> upl = new ArrayList<>();
    	List<Integer> lol = new ArrayList<>();
    	for(int i = 0;i < len;i++){
    		if(colsum[i] == 2){
    			upl.add(1);
    			lol.add(1);
    		}
    		else if(colsum[i] == 0){
    			upl.add(0);
    			lol.add(0);
    		}
    		else{
    			if(up -- > 0){
    				upl.add(1);
    				lol.add(0);
    			}
    			else{
    				lol.add(1);
    				upl.add(0);
    			}
    		}
    	}
    	list.add(upl);
    	list.add(lol);
    	return list;
    }
}

1567 乘积为正数的最长子数数组长度


class Solution {
    public int getMaxLen(int[] nums) {
    	int n = nums.length;
    	int[][] dp = new int[n][2];
    	int res = 0;
    	if(nums[0] > 0){
    		dp[0][0] = 1;
    	}else if(nums[0] < 0){
    		dp[0][1] = 1;
    	}
    	for(int i =1;i < n;i++){
    		if(nums[i] > 0){
    			dp[i][0] = dp[i-1][0] + 1;
    			if(dp[i - 1][1] > 0)
    				dp[i][1] = dp[i - 1][1] + 1;
    		}else if(nums[i] < 0){
    			if(dp[i - 1][1] > 0)
    				dp[i][0] = dp[i - 1][1] + 1;
    			dp[i][1] = dp[i - 1][0] + 1;
    		}
    	}
    	for(int i = 0;i < n;i++){
    		res = Math.max(res,dp[i][0]);
    	}
    	return res;
    }
}

1111 有效括号的嵌套深度

一个模拟栈的过程


public class Solution{
	public int[] maxDepthAfterSplit(String seq){
		int[] ans = new int[seq.length()];
		int idx = 0;
		for(char c: seq.toCharArray()){
			ans[idx++] = c == '(' ? idx & 1 : ((idx + 1) & 1);
		}
		return ans;
	}
}

贪心-困难难度

1383 最大的团队表现值


class Solution {
    public int maxPerformance(int n, int[] speed, int[] efficiency, int k) {
    	int[][] items = new int[n][2];
    	for(int i = 0;i < n;i++){
    		items[i][0] = speed[i];
    		items[i][1] = efficiency[i];
    	}
    	Arrays.sort(items,new Comparator<int[]>(){
    		@Override
    		public int compare(int[] a,int[] b){
    			return b[1] - a[1]; 
    		}
    	});
    	PriorityQueue<Integer> queue = new PriorityQueue<>();
    	long res = 0,sum = 0;
    	for(int i = 0;i < n;i++){
    		if(queue.size() > k - 1){
    			sum -= queue.poll();
    		}
    		res = Math.max(res,(sum + items[i][0]) * items[i][1]);
    		queue.add(items[i][0]);
    		sum += items[i][0];
    	}
    	return (int)(res % ((int)1e9 + 7));
    }
}

659 分割数组为连续子序列


class Solution {
    public boolean isPossible(int[] nums) {
    	Map<Integer,Integer> map = new HashMap<>();
    	for(int i : nums){
    		map.put(i,map.getOrDefault(i,0) + 1);
    	}
    	for(int i: nums){
    		int subNum = 0;
    		int p = 1;
    		int next = i;
    		while(map.getOrDefault(next,0) >= p){
    			p = map.get(next);
    			map.put(next,p-1);
    			++subNum;
    			++next;
    		}
    		if(subNum > 0 && subNum < 3){
    			return false;
    		}
    	}
    	return true;
    }
}

1568 使陆地分离的最少天数

看注释 后面会写一个Tarjan + 并查集版本


class Solution {
    int m, n;
    boolean[][] used;
    private static final int[][] directions = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}};
    public int minDays(int[][] grid) {
        m = grid.length;
        n = grid[0].length;
        used = new boolean[m][n];
        if(check(grid)){
            return 0;
        }else{
            //枚举 去掉其中一个1看能不能满足
            for(int i = 0; i < m; i++) {
                for(int j = 0; j < n; j++) {
                    if(grid[i][j] == 1){
                        grid[i][j] = 0;
                        if(check(grid)){
                            return 1;
                        }
                        grid[i][j] = 1;
                    }
                }
            }
            return 2;
        }
    }
    public boolean check(int[][] grid){
        //计算是否有多个连通块,或者0个连通块
        int count = 0;
        used = new boolean[m][n];
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                if(!used[i][j] && grid[i][j] == 1){
                    count++;
                    dfs(grid, i, j);
                }
            }
        }
        return count > 1 || count == 0;
    }
    public void dfs(int[][] grid, int i, int j){
        used[i][j] = true;
        for(int k = 0; k < 4; k++){
            int x = i + directions[k][0];
            int y = j +directions[k][1];
            if(inArea(x, y) && !used[x][y] && grid[i][j] == 1){
                dfs(grid, x, y);
            }
        }
    }
    public boolean inArea(int x, int y){
        return x >= 0 && x < m && y >= 0 && y < n;
    }
}


1505 最多k次交换相邻数位后得到的最小整数

看注释


class Solution {

  public String minInteger(String num, int k) {
    // 统计0-9的所有位置
    List<Integer>[] idLists = new List[10];
    for (int i = 0; i < 10; i++) {
      idLists[i] = new ArrayList<>();
    }
    int n = num.length();
    for (int i = 0; i < n; i++) {
      idLists[num.charAt(i) - '0'].add(i);
    }
    // 指向idLists的0-9的当前位置
    int[] ids = new int[10];
    boolean[] seen = new boolean[n];
    StringBuilder res = new StringBuilder();
    // 统计范围内已被使用的下标,计算需要转换的次数时需要去掉已被转换到前面的那些下标
    FenwichTree fwt = new FenwichTree(new int[n]);
    outer:
    for (int i = 0; i < n; i++) {
      if (seen[i]) { // 如果已经被置换过了,跳过
        continue;
      }
      int cur = num.charAt(i) - '0';
      // 查找比当前元素小且满足条件的最小值的下标
      for (int j = 0; j < cur; j++) {
        while (ids[j] < idLists[j].size() && idLists[j].get(ids[j]) < i) {
          ids[j]++;
        }
        if (ids[j] == idLists[j].size()) {
          continue;
        }
        int index = idLists[j].get(ids[j]);
        int seenNum = fwt.sumRange(0, index - 1);
        if (index - seenNum <= k) {
          // 找到了满足条件的值,更新状态
          k -= index - seenNum;
          ids[j]++;
          seen[index] = true;
          fwt.update(index, 1);
          i--;
          res.append(j);
          continue outer;
        }
      }
      // 找不到满足条件且小于当前值的值,更新状态
      seen[i] = true;
      fwt.update(i, 1);
      res.append(cur);
    }
    return res.toString();
  }
}

class FenwichTree {

  private int[] sums;
  private int[] nums;

  public FenwichTree(int[] nums) {
    this.sums = new int[nums.length + 1];
    this.nums = nums;
    for (int i = 0; i < nums.length; i++) {
      updateBit(i + 1, nums[i]);
    }
  }

  public void update(int i, int val) {
    updateBit(i + 1, val - nums[i]);
    nums[i] = val;
  }

  private void updateBit(int i, int diff) {
    while (i < sums.length) {
      sums[i] += diff;
      i += lowBit(i);
    }
  }

  public int sumRange(int i, int j) {
    return preSum(j + 1) - preSum(i);
  }

  private int preSum(int i) {
    int sum = 0;
    while (i > 0) {
      sum += sums[i];
      i -= lowBit(i);
    }
    return sum;
  }

  private int lowBit(int i) {
    return i & (-i);
  }
}

502 IPO

  1. 把所有项目信息,即 capitalprofit ,保存在一个项目节点中
  2. 根据 capital 的大小,把项目放入小根堆
  3. 把小于资金 mcapital 取出放入根据 profit 大小的大根堆
  4. 从大根堆弹出的数就是可以做的利润最大的项目(大根堆是按利润从高到低排序,所以取出的是利润最大的项目) 最终,做完k个项目了或者没有可以做(profit堆为空说明剩余项目的成本大于资金,或者所有项目已经做完)的项目时便结束

class Solution {

	public static class Node{
		int profit;
		int capital;
		public Node(int profit,int capital){
			this.profit = profit;
			this.capital = capital;
		}
	}

	public static class ComparatorOfProfit implements Comparator<Node>{
		@Override
		public int compare(Node n1,Node n2){
			return n2.profit - n1.profit;
		}
	}

	public static class ComparatorOfCapital implements Comparator<Node>{
		@Override
		public int compare(Node n1,Node n2){
			return n1.capital - n2.capital;
		}
	}

    public int findMaximizedCapital(int k, int W, int[] Profits, int[] Capital) {
        PriorityQueue<Node> pqByP = new PriorityQueue<>(new ComparatorOfProfit());
        PriorityQueue<Node> pqByC = new PriorityQueue<>(new ComparatorOfCapital());
        Node[] nodes = new Node[Profits.length];

        for(int i = 0;i < Profits.length;i++){
        	nodes[i] = new Node(Profits[i],Capital[i]);
        }
        for(int i = 0;i < Profits.length;i++){
        	pqByC.add(nodes[i]);
        }
        for(int i = 0;i < k;i++){
        	while(!pqByC.isEmpty() && pqByC.peek().capital <= W){
        		pqByP.add(pqByC.poll());
        	}
        	if(!pqByP.isEmpty()){
        		W += pqByP.poll().profit;
        	}else{
        		break;
        	}
        }
        return W;
    }
}

630 课程表 III

还是那个套路,先参加最早结束的课程


class Solution {
    public int scheduleCourse(int[][] courses) {
    	Arrays.sort(courses,(a,b) -> a[1] - b[1]);
    	PriorityQueue<Integer> queue = new PriorityQueue<>((a,b) -> b - a);
    	int time = 0;
    	for(int[] c:courses){
    		if(time + c[0] <= c[1]){
    			queue.offer(c[0]);
    			time += c[0];
    		}
    		else if(!queue.isEmpty() && queue.peek() > c[0]){
    			time += c[0] - queue.poll();
    			queue.offer(c[0]);
    		}
    	}
    	return queue.size();
    }
}

765 情侣牵手

可恶。单身狗深夜收到暴击。 这个题可以贪心,非常暴力,也就是遇到一个人,他的情侣不跟他坐在一起就去把他情侣找出来换到他身边(但是太暴力啦) 另一种写法是用并查集。关于并查集我写了一个专题,还在写,这里就不再赘述啦,过两天写完了发。 个人感觉虽然贪心暴力过了,而且还双百,但是应该是因为题目的数据规模比较小,所以才会这样。

解法一


//并查集写法

class Solution {

    private class UnionFind{
        int[] parent;
        int[] weight;
        UnionFind(int[] row){
            int m = row.length / 2;
            parent = new int[m];
            weight = new int[m];
            for(int i = 0; i < m; i++){
                parent[i] = i;
                weight[i] = 1;
            }
        }
        public int find(int x){
            if(parent[x] != x) parent[x] = find(parent[x]);
            return parent[x];
        }
        public void union(int x, int y){
            int rootx = find(x);
            int rooty = find(y);
            if(rootx == rooty) return;
            if(weight[rootx] > weight[rooty]){
                parent[rooty] = rootx;
                weight[rootx] += weight[rooty];
            }else{
                parent[rootx] = rooty;
                weight[rooty] += weight[rootx];
            }
        }

        public int getRet(){
            int ret = 0;
            for(int i = 0; i < parent.length; i++){
                if(parent[i] != i) ret++;
            }
            return ret;
        }
    }

    public int minSwapsCouples(int[] row) {
        UnionFind uf = new UnionFind(row);
        for(int i = 0; i < row.length; i += 2){
            uf.union(row[i] / 2, row[i + 1] / 2);
        }
        return uf.getRet();
    }
}


解法二


class Solution {
     public int minSwapsCouples(int[] row) {
        int ans = 0;
        int n = row.length;
        int[] indexArr = new int[n];

        for (int i = 0; i < n; i++) {
            indexArr[row[i]] = i;
        }

        int next = 0;
        for (int i = 0; i < n; i += 2) {
            next = (row[i] & 1) == 0 ? row[i] + 1 : row[i] - 1;
            if (next == row[i + 1]) {
                continue;
            }

            // 其实无需真正执行交换操作,由于进入i + 1位置的人现在已经配对到情侣了,后面不会再用到了,
            // 因此只要更新离开i+1位置的那个人的信息即可
            int swapedIndex = indexArr[next];
            indexArr[row[i+1]] = swapedIndex;
            row[swapedIndex] = row[i + 1];
            ans++;
        }

        return ans;
    }
}

1591 奇奇怪的打印机 II

看注释


class Solution {
    public boolean isPrintable(int[][] targetGrid) {
        int row_len = targetGrid.length;
        int col_len = targetGrid[0].length;

        //确定四角的范围
        boolean[] visited = new boolean[61]; //用于判断是否出现这个color
        int[] row_start = new int[61];
        int[] row_end = new int[61];
        int[] col_start = new int[61];
        int[] col_end = new int[61];
        //入度节点数目确定数组
        int[] into_degree = new int[61];
        int visited_count = 0;
        {int i = 0, j;
            for (int[] ints : targetGrid) {
                j = 0;
                for (int anInt : ints) {
                    if (visited[anInt] == false) {
                        visited[anInt] = true;
                        visited_count++;
                        row_start[anInt] = row_end[anInt] = i;
                        col_start[anInt] = col_end[anInt] = j;
                    } else { //row_start 不需要更新 col_start需要
                        row_end[anInt] = i;
                        col_start[anInt] = Math.min(col_start[anInt], j);
                        col_end[anInt] = Math.max(col_end[anInt], j);
                    }
                    j++;
                }
                i++;
            }}

        boolean[][] compare = new boolean[61][61]; //compare[i][j]  前面的i是每次的target  true是target在下 不可能对角线同时为true
        for (int target = 0; target <= 60; target++) {
            if (visited[target]) {
                for (int i = row_start[target]; i <= row_end[target]; i++) {
                    for (int j = col_start[target]; j <= col_end[target]; j++) {
                        if (target != targetGrid[i][j]) {
                            if (compare[targetGrid[i][j]][target] == true) return false;
                            if (compare[target][targetGrid[i][j]] == false) { //这样保证了 入度只加一次
                                into_degree[targetGrid[i][j]] ++;
                                compare[target][targetGrid[i][j]] = true;
                            }
                        }
                    }
                }
            }
        }

        for (int i = 1; i <= 5; i++) {
            for (int j = 1; j <= 5; j++) {
                // System.out.print((compare[i][j] == true ? 1 : 0) + " ");
            }
            // System.out.println();
        }
        //不是直接return true 还有在进行拓扑排序略及的证明
        //正常应该是用栈 因为本题规模较小 所以 我就每一轮 从头遍历
        int used_count = 0;
        // LinkedList<Integer> ans = new LinkedList<>(); //这一变量就是为了输出结果用的 不过题目没有要求给出soluiton
        for (int i = 0;  i < 61; ) {
            if (visited[i] == true && into_degree[i] == 0) {
                used_count++;
                // ans.addLast(i);
                visited[i] = false;
                for (int j = 0; j < 61; j++) {
                    if (compare[i][j] == true) into_degree[j]--;
                }
                i = 0; continue;
            } else i++;
        }
        //System.out.println(ans); //这一句可以打印出一种solution
        return used_count == visited_count;
    }
}

1585 检查字符串是否可以通过排序子字符串得到另一个字符串

官方题解写得挺好


class Solution {
    public boolean isTransformable(String s, String t) {
        int n = s.length();
        Queue<Integer>[] pos = new Queue[10];
        for (int i = 0; i < 10; ++i) {
            pos[i] = new LinkedList<Integer>();
        }
        for (int i = 0; i < n; ++i) {
            pos[s.charAt(i) - '0'].offer(i);
        }
        for (int i = 0; i < n; ++i) {
            int digit = t.charAt(i) - '0';
            if (pos[digit].isEmpty()) {
                return false;
            }
            for (int j = 0; j < digit; ++j) {
                if (!pos[j].isEmpty() && pos[j].peek() < pos[digit].peek()) {
                    return false;
                }
            }
            pos[digit].poll();
        }
        return true;
    }
}



总结

以上题目不完全用贪心算法解答,因为在力扣里面是贪心标签下的,所以记录进来,考虑到有时候贪心的效率过低,所以就没有全都用贪心做。

做了这么多跟贪心算法有关的题目,我也有自己的一点小小的总结和心得。

贪心是一个只考虑当前状态下的最优选择的算法,即总是选择当下的最优解。 一般的思路是:

  1. 建立数学模型来描述问题
  2. 把求解的问题分成若干个子问题
  3. 对每一个问题求解,得到子问题的局部最优解
  4. 把子问题的局部最优解合成原来问题的一个解

感觉看贪心的题目一般都是你要先了解题目,找到一个贪心的切入点,一般都会有一个规律,就是“选择最...的情况最好”。另外,一般也需要做一些预处理,比如排序、前缀和、差分等等。