力扣-贪心算法题解(简单、中等、困难)
贪心-简单难度
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
- 把所有项目信息,即 capital 和 profit ,保存在一个项目节点中
- 根据 capital 的大小,把项目放入小根堆
- 把小于资金 m 的 capital 取出放入根据 profit 大小的大根堆
- 从大根堆弹出的数就是可以做的利润最大的项目(大根堆是按利润从高到低排序,所以取出的是利润最大的项目) 最终,做完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;
}
}
总结
以上题目不完全用贪心算法解答,因为在力扣里面是贪心标签下的,所以记录进来,考虑到有时候贪心的效率过低,所以就没有全都用贪心做。
做了这么多跟贪心算法有关的题目,我也有自己的一点小小的总结和心得。
贪心是一个只考虑当前状态下的最优选择的算法,即总是选择当下的最优解。 一般的思路是:
- 建立数学模型来描述问题
- 把求解的问题分成若干个子问题
- 对每一个问题求解,得到子问题的局部最优解
- 把子问题的局部最优解合成原来问题的一个解
感觉看贪心的题目一般都是你要先了解题目,找到一个贪心的切入点,一般都会有一个规律,就是“选择最...的情况最好”。另外,一般也需要做一些预处理,比如排序、前缀和、差分等等。