贪心

128 阅读3分钟

贪心算法介绍

  1. 最自然智慧的算法
  2. 用一种局部最功利的标准,总是做出在当前看来是最好的选择
  3. 难点在于证明局部最功利的标准可以得到全局最优解
  4. 对于贪心算法的学习主要以增加阅历和经验为主

解题方法

  1. 实现一个不依靠贪心策略的解法X,可以用最暴力的尝试
  2. 脑补出贪心策略A、贪心策略B、贪心策略C...
  3. 用解法X和对数器,用实验的方式得知哪个贪心策略正确
  4. 不要去纠结贪心策略的证明

相关题目

最小字典序

给定一个由字符串组成的数组strs, 必须把所有的字符串拼接起来, 返回所有可能的拼接结果中,字典序最小的结果

// strs中所有字符串全排列,返回所有可能的结果
public static TreeSet<String> process(String[] strs) {
   TreeSet<String> ans = new TreeSet<>();
   if (strs.length == 0) {
      ans.add("");
      return ans;
   }
   for (int i = 0; i < strs.length; i++) {
      String first = strs[i];
      String[] nexts = removeIndexString(strs, i);
      TreeSet<String> next = process(nexts);
      for (String cur : next) {
         ans.add(first + cur);
      }
   }
   return ans;
}

// {"abc", "cks", "bct"}
// 0 1 2
// removeIndexString(arr , 1) -> {"abc", "bct"}
public static String[] removeIndexString(String[] arr, int index) {
   int N = arr.length;
   String[] ans = new String[N - 1];
   int ansIndex = 0;
   for (int i = 0; i < N; i++) {
      if (i != index) {
         ans[ansIndex++] = arr[i];
      }
   }
   return ans;
}

public static class MyComparator implements Comparator<String> {
   @Override
   public int compare(String a, String b) {
      return (a + b).compareTo(b + a);
   }
}

最少几盏灯

给定一个字符串str,只由‘X’和‘.’两种字符构成。 ‘X’表示墙,不能放灯,也不需要点亮。‘.’表示居民点,可以放灯,需要点亮 如果灯放在i位置,可以让i-1,i和i+1三个位置被点亮 返回如果点亮str中所有需要点亮的位置,至少需要几盏灯

// 1. 当前是X 直接跳过
// 2. 当前是点 如果下一个是X,点亮;如果下一个是点,就点亮下一个
public static int minLight2(String road) {
   char[] str = road.toCharArray();
   int i = 0;
   int light = 0;
   while(i < str.length) {
      if(str[i] == 'X') {
         i = i + 1;
      } else {
         light++;
         if(i + 1 == str.length) { // 防止 i + 1 越界
            break;
         } else {
            if(str[i + 1] == 'X') {
               i = i + 2;
            } else {
               i = i + 3; // 如果下一个是点,那么无论下下个是点还是 X ,都不用考虑
            }
         }
      }
   }
   return light;
}

切金条最小代价

一块金条切成两半,是需要花费和长度数值一样的铜板的。 比如长度为20的金条,不管怎么切,都要花费20个铜板。 一群人想整分整块金条,怎么分最省铜板?

例如,给定数组{10,20,30},代表一共三个人,整块金条长度为60,金条要分成10,20,30三个部分。 如果先把长度60的金条分成10和50,花费60; 再把长度50的金条分成20和30,花费50;一共花费110铜板。 但如果先把长度60的金条分成30和30,花费60;再把长度30金条分成10和20, 花费30;一共花费90铜板。 输入一个数组,返回分割的最小代价。

// 暴力
// 等待合并的数都在arr里,pre之前的合并行为产生了多少总代价
// arr中只剩一个数字的时候,停止合并,返回最小的总代价
public static int process(int[] arr, int pre) {
   if(arr.length == 1) { // 之所以要有pre,因为没pre,basecase不好写
      return pre;
   }
   int ans = Integer.MAX_VALUE;
   for (int i = 0; i < arr.length; i++) { // arr中任意两个数合并为开始,计算最小代价
      for (int j = i+1; j < arr.length; j++) {
         ans = Math.min(ans,process(mergeTowNum(arr,i,j),pre + arr[i] + arr[j]));
      }
   }
   return ans;
}

public static int[] mergeTowNum(int[] arr,int i,int j) {
   int ansi = 0;
   int[] ans = new int[arr.length - 1];
   for (int k = 0; k < arr.length; k++) {
      if(k != i && k!= j) {
         ans[ansi++] = arr[k];
      }
   }
   ans[ansi] = arr[i] + arr[j];
   return ans;
}

// 用哈夫曼编码的思想,贪心
public static int lessMoney2(int[] arr) {
   PriorityQueue<Integer> heap = new PriorityQueue<>();
   for (int i = 0; i < arr.length; i++) {
      heap.add(arr[i]);
   }
   int sum = 0;
   int cur = 0;
   while(heap.size() > 1) {
      cur = heap.poll() + heap.poll();
      heap.add(cur);
      sum += cur;
   }
   return sum;
}

宣讲最大场次数

一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。 给你每一个项目开始的时间和结束的时间 你来安排宣讲的日程,要求会议室进行的宣讲的场次最多。 返回最多的宣讲场次。

// 暴力!所有情况都尝试!
public static int bestArrange1(Program[] programs) {
   if (programs == null || programs.length == 0) {
      return 0;
   }
   return process(programs, 0, 0);
}

// 还剩下的会议都放在programs里
// done之前已经安排了多少会议的数量
// timeLine目前来到的时间点是什么

// 目前来到timeLine的时间点,已经安排了done多的会议,剩下的会议programs可以自由安排
// 返回能安排的最多会议数量
public static int process(Program[] programs, int done, int timeLine) {
   if(programs.length == 0) {
      return done;
   }
   int max = done;
   for (int i = 0; i < programs.length; i++) {
      if(programs[i].start >= timeLine) {
         max = Math.max(max,process(copyButExcept(programs,i),done + 1,programs[i].end));
      }
   }
   return max;
}

public static Program[] copyButExcept(Program[] programs, int i) {
   Program[] ans = new Program[programs.length - 1];
   int index = 0;
   for (int k = 0; k < programs.length; k++) {
      if (k != i) {
         ans[index++] = programs[k];
      }
   }
   return ans;
}

// 会议的开始时间和结束时间,都是数值,不会 < 0
public static int bestArrange2(Program[] programs) {
   Arrays.sort(programs,new ProgrammComparator());
   int timeLine = 0;
   int result = 0;
   for (int i = 0; i < programs.length; i++) {
      if(timeLine <= programs[i].start) {
         result++;
         timeLine = programs[i].end;
      }
   }
   return result;
}