贪心算法 1 leetcode455 分发饼干 2 le

image.png
- 贪心算法的局限性
image.png
- 具备贪心思想的问题
image.png

  1. leetcode455 分发饼干

1.1 解题思路

image.png
image.png

1.2 代码实现🔴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码class Solution {
public int findContentChildren(int[] g, int[] s) { // 饼干是s 小孩胃口值是g
Arrays.sort(g);
Arrays.sort(s);
int i = 0;
int j = 0;
while (i < s.length && j < g.length) {
if (s[i] >= g[j]) {
j++;
}
i++;
}
return j;
}
}
  1. leetcode322 零钱兑换

2.1 解题思路

本题贪心 + 回溯不能提升性能,最优解为动态规划,后续再写。
image.png
image.png

  1. leetcode45 跳跃游戏II

3.1 解题思路

image.png

3.2 代码实现1:DFS(会超时)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码class Solution {
private int minSteps = Integer.MAX_VALUE;

public int jump(int[] nums) {
dfs(nums, 0, new ArrayList<>());
return minSteps == Integer.MAX_VALUE ? 0 : minSteps;
}

private void dfs(int[] nums, int jumpedIndex, List<Integer> path) {
if (jumpedIndex == nums.length - 1) {
minSteps = Math.min(minSteps, path.size());
return;
}
for (int i = 1; i <= nums[jumpedIndex]; i++) {
if (jumpedIndex + i >= nums.length)
continue;
path.add(i);
// jumpedIndex + i,表示跳到下一步所在的位置
dfs(nums, jumpedIndex + i, path);
path.remove(path.size() - 1);
}
}
}

3.2 代码实现2:BFS(会超时)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码class Solution {
public int jump(int[] nums) {
if (nums.length == 1)
return 0;
Queue<Integer> queue = new ArrayDeque<>();
queue.offer(0);
int level = 0;
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
int jumpedIndex = queue.poll();
if (jumpedIndex == nums.length - 1)
return level;
for (int j = 1; j <= nums[jumpedIndex]; j++) {
queue.offer(jumpedIndex + j);
}
}
level++;
}
return 0;
}
}

3.3 代码实现3:贪心

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码class Solution {
// 贪心策略:每步都选择能跳到的最远距离
public int jump(int[] nums) {
if (nums.length == 1)
return 0;
int steps = 0;
int start = 0, end = 0;
while (end < nums.length - 1) { // 走到最后一个位置的时候就不用走了
int maxPos = 0;
// 每次从 [start, end] 中都选择能跳到的最远距离
for (int i = start; i <= end; i++) {
maxPos = Math.max(maxPos,i + nums[i]);
}
start = end + 1;
end = maxPos;
steps++;
}
return steps;
}
}

3.4 代码实现4:🔴贪心(只遍历一次)

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码class Solution {
// 贪心策略:每步都选择能跳到的最远距离
public int jump(int[] nums) {
if (nums.length == 1)
return 0;
int steps = 0;
int maxPos = 0, end = 0;
for (int i = 0; i < nums.length - 1; i++) {
// 仅遍历一次,计算每一个位置并求得最大位置
maxPos = Math.max(maxPos, i + nums[i]);
if (i == end) {
steps++;
end = maxPos;
}
}
return steps;
}
}
  1. leetcode55 跳跃游戏

4.1 解题思路

image.png
image.png

4.2 代码实现🔴

1
2
3
4
5
6
7
8
9
10
11
java复制代码class Solution {
public boolean canJump(int[] nums) {
int maxPos = 0;
for (int i = 0; i < nums.length; i++) {
if (maxPos < i)
return false;
maxPos = Math.max(maxPos, i + nums[i]);
}
return true;
}
}
  1. leetcode1578 避免重复字母的最小删除成本

4.1 解题思路

image.png
image.png

4.2 代码实现🔴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码class Solution {
public int minCost(String s, int[] cost) {
int res = 0;
int len = s.length();
int i = 0;
while (i < len) {
char c = s.charAt(i);
int maxCost = 0;
int sumCost = 0;
while (i < len && s.charAt(i) == c) {
maxCost = Math.max(maxCost, cost[i]);
sumCost += cost[i];
i++;
}
res += sumCost - maxCost;
}
return res;
}
}
  1. leetcode402 移掉 K 位数字

5.1 解题思路

image.png
image.png

5.2 代码实现1:贪心,时间复杂度O(k * n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
java复制代码class Solution {
public String removeKdigits(String num, int k) {
StringBuilder sb = new StringBuilder(num);
for (int i = 0; i < k; i++) {
boolean hasDeleted = false;
for (int j = 1; j < sb.length(); j++) {
// 如果前面的数字大,则删除
if (sb.charAt(j) < sb.charAt(j - 1)) {
sb.deleteCharAt(j - 1);
hasDeleted = true;
break;
}
}
// 说明序列是递增的,那么删除最后一个字符
if (!hasDeleted)
sb.deleteCharAt(sb.length() - 1);
}

// 删除前面是0的字符
int len = sb.length();
while (len != 0) {
if (sb.charAt(0) > '0')
break;
sb.deleteCharAt(0);
len = sb.length();
}
return sb.length() == 0 ? "0" : sb.toString();
}
}

5.3 代码实现2:🔴贪心 + 单调栈 (时间复杂度O(k + n),空间复杂度O(n))

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
java复制代码class Solution {
public String removeKdigits(String num, int k) {
Deque<Character> deque = new ArrayDeque<>();
for (int i = 0; i < num.length(); i++) {
char c = num.charAt(i);
while (!deque.isEmpty() && k > 0 && deque.peek() > c) {
deque.pop();
k--;
}
deque.push(c);
}
for (int i = 0; i < k; i++) {
deque.pop();
}

StringBuilder sb = new StringBuilder();
boolean isFirst = true;
while (!deque.isEmpty()) {
char c = deque.pollLast();
if (c == '0' && isFirst)
continue;
sb.append(c);
isFirst = false;
}
return sb.length() == 0 ? "0" : sb.toString();
}
}

6 leetcode409 最长回文串

6.1 解题思路

1. 可以将出现次数为偶数个的字符分居两侧构成回文串

2. 只能在回文串的中间添加一个出现次数为奇数个的字符

6.2 代码实现🔴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码class Solution {
public int longestPalindrome(String s) {
int[] counter = new int[128];
int ans = 0;
for (char c : s.toCharArray()) {
counter[c]++;
}
for (int v : counter) {
ans += v / 2 * 2;
// 中间只能有一个出现奇数次的字符
if (v % 2 == 1 && ans % 2 == 0) {
ans++;
}
}
return ans;
}
}
  1. leetcode680 验证回文字符串Ⅱ

7.1 解题思路

1. 贪心策略:只有在开头和结尾两个字符不相等的时候,才去尝试删除开头或者结尾任一个字符

7.2 代码实现:🔴贪心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
java复制代码class Solution {
// 贪心策略: 只有在开头和结尾两个字符不相等的时候,才去尝试删除开头或者结尾任一个字符
// 时间复杂度O(n)
// 空间复杂度O(1)
public boolean validPalindrome(String s) {
int left = 0;
int right = s.length() - 1;
while (left < right) {
if (s.charAt(left) == s.charAt(right)) {
left++;
right--;
} else {
// 要么删除left指向的字符,要么删除right指向的字符
// 然后再判断剩余的字符是否是回文
return validPalindrome(s, left + 1, right) ||
validPalindrome(s, left, right - 1);
}
}
return true;
}

private boolean validPalindrome(String s, int left, int right) {
while (left < right) {
if (s.charAt(left) == s.charAt(right)) {
left++;
right--;
} else {
return false;
}
}
return true;
}
}
  1. leetcode316 去除重复字母

8.1 解题思路

1. 记录字符在字符串s中的最后索引,用于判断当前字符后面是否还有将要弹出的字符

2. 维护一个单调栈

3. 使用一个boolean数组记录是否已经存在于栈中

image.png

8.2 代码实现:🔴贪心 + 单调栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
java复制代码class Solution {
public String removeDuplicateLetters(String s) {
// 1. 计算字符在字符串s中的最后索引
int[] lastIndex = new int[26];
for (int i = 0; i < s.length(); i++) {
lastIndex[s.charAt(i) - 'a'] = i;
}

// 2. 维护单调栈
Deque<Character> stack = new ArrayDeque<>();
// 用于记录字符是否已经存在于栈中
boolean[] exits = new boolean[26];

for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (exits[c - 'a'])
continue;

// 条件:
// (1). 当前字符小于栈顶字符
// (2). 栈顶字符在当前字符的后面还会出现
while (!stack.isEmpty() && stack.peek() > c
&& lastIndex[stack.peek() - 'a'] > i) {
char top = stack.pop();
exits[top - 'a'] = false;
}

stack.push(c);
exits[c - 'a'] = true;
}

// 3. 将栈中字符拼接成结果
StringBuilder res = new StringBuilder();
while (!stack.isEmpty()) {
res.append(stack.pollLast());
}
return res.toString();
}
}
  1. leetcode1047 删除字符串中的所有相邻重复项

9.1 解题思路

1. 使用栈
image.png
2. 使用快慢指针,0-slow表示不需要删除的字符,fast指针用来遍历整个字符串

image.png
image.png

9.2 代码实现1:🔴使用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码class Solution {
public String removeDuplicates(String s) {
Deque<Character> stack = new ArrayDeque<>();
for (char c : s.toCharArray()) {
if (!stack.isEmpty() && stack.peek() == c) {
stack.pop();
} else {
stack.push(c);
}
}
StringBuilder sb = new StringBuilder();
while (!stack.isEmpty()){
sb.append(stack.pollLast());
}
return sb.toString();
}
}

9.3 代码实现2:🔴使用快慢指针(空间复杂度降为O(1))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码class Solution {
public String removeDuplicates(String s) {
char[] chars = s.toCharArray();
int slow = -1;
int fast = 0;
while (fast < s.length()) {
if (slow >= 0 && chars[fast] == chars[slow]) {
slow--;
} else {
slow++;
chars[slow] = chars[fast];
}
fast++;
}
return new String(chars, 0, slow + 1);
}
}
  1. leetcode1209 删除字符串中的所有相邻重复项II

10.1 解题思路

1. 使用栈
image.png
image.png
2. 使用快慢指针
image.png

10.2 代码实现1:🔴使用栈(时间复杂度:O(N),空间复杂度:O(N))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
java复制代码class Solution {
class Pair {
char ch;
int count;

public Pair(char ch, int count) {
this.ch = ch;
this.count = count;
}
}

public String removeDuplicates(String s, int k) {
Deque<Pair> stack = new ArrayDeque<>();
for (int i = 0; i < s.length(); i++) {
if (stack.isEmpty() || s.charAt(i) != stack.peek().ch) {
stack.push(new Pair(s.charAt(i), 1));
} else {
stack.peek().count++;
if (stack.peek().count == k) {
stack.pop();
}
}
}
StringBuilder sb = new StringBuilder();
while (!stack.isEmpty()) {
Pair p = stack.pollLast();
for (int i = 0; i < p.count; i++) {
sb.append(p.ch);
}
}
return sb.toString();
}
}

10.3 代码实现2:🔴使用快慢指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码class Solution {
public String removeDuplicates(String s, int k) {
char[] chars = s.toCharArray();
Deque<Integer> count = new ArrayDeque<>();
int slow = 0;
for (int fast = 0; fast < chars.length; slow++, fast++) {
if (slow != fast) {
chars[slow] = chars[fast];
}
if (slow == 0 || chars[slow] != chars[slow - 1]) {
count.push(1);
} else {
int incremented = count.pop() + 1;
if (incremented == k) {
slow -= k;
} else {
count.push(incremented);
}
}
}
return new String(chars, 0, slow);
}
}
  1. leetcode976 三角形的最大周长

11.1 解题思路

不失一般性,我们假设三角形的边长a,b,c满足a≤b≤c,那么这三条边组成面积不为零的三角形的充分必要条件为a+b>c

11.2 代码实现🔴

1
2
3
4
5
6
7
8
9
10
11
java复制代码class Solution {
public int largestPerimeter(int[] nums) {
Arrays.sort(nums);
for (int i = nums.length - 1; i >= 2; i--) {
if (nums[i - 2] + nums[i - 1] > nums[i]) {
return nums[i - 2] + nums[i - 1] + nums[i];
}
}
return 0;
}
}
  1. leetcode674 最长连续递增序列

12.1 解题思路

image.png
image.png

12.2 代码实现(贪心 + 双指针)🔴

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码class Solution {
public int findLengthOfLCIS(int[] nums) {
int res = 1;
for (int start = 0, end = 1; end < nums.length; end++) {
if (nums[end] <= nums[end - 1]) {
start = end;
continue;
}
res = Math.max(res, end - start + 1);
}
return res;
}
}
  1. leetcode738 单调递增的数字

13.1 解题思路

1. 先遍历找到第一个非递增的序列
image.png

2. 回退check直至其为递增序列
image.png

3. 将最后改变高位之后的数字全部换成9

image.png

13.2 代码实现🔴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码class Solution {
public int monotoneIncreasingDigits(int n) {
char[] strN = String.valueOf(n).toCharArray();
int i = 1;
// 1. 找到第一个递减的位
while (i < strN.length && strN[i - 1] <= strN[i])
i++;
if (i < strN.length) {
// 2. 不断将前一个数字 -1,直到前一个数字小于等于后一个数字
while (i > 0 && strN[i - 1] > strN[i]) {
strN[i - 1] -= 1;
i--;
}
// 3. 将 i 后面的数字都设置为9
i++;
while (i < strN.length) {
strN[i++] = '9';
}
return Integer.parseInt(new String(strN));
} else {
return n;
}
}
}
  1. leetcode134 加油站

14.1 解题思路

总结:如果x到不了y + 1(但能到y),那么从x到y的任一点出发都不可能到达y+1。因为从其中任一点出发的话,相当于从0开始加油,而如果从x出发到该点则不一定是从0开始加油,可能还有剩余的油。既然不从0开始都到不了y+1,那么从0开始就更不可能到达y+1了。

image.png
image.png

14.2 代码实现🔴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int totalGas = 0;
int currGas = 0;
int startStation = 0;
for (int i = 0; i < gas.length; i++) {
totalGas += gas[i] - cost[i];
currGas += gas[i] - cost[i];
if (currGas < 0) {
startStation = i + 1;
currGas = 0;
}
}
return totalGas >= 0 ? startStation : -1;
}
}
  1. leetcode767 重构字符串

15.1 解题思路

image.png
image.png

15.2 代码实现🔴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
java复制代码class Solution {
public String reorganizeString(String s) {
// 1. 统计每个字符出现的次数
char[] chars = s.toCharArray();
int n = chars.length;
int[] count = new int[26];
for (char c : chars) {
count[c - 'a']++;
if (count[c - 'a'] > (n + 1) / 2)
return "";
}
// 2. 拿到出现次数最多的字符
int maxCountIndex = 0;
for (int i = 0; i < 26; i++) {
if (count[i] > count[maxCountIndex])
maxCountIndex = i;
}
// 3. 先将出现次数最多的字符放在偶数索引上
char[] res = new char[n];
int index = 0;
while (count[maxCountIndex] > 0) {
res[index] = (char) (maxCountIndex + 'a');
index += 2;
count[maxCountIndex]--;
}
// 4. 将其他的字符放在其他的位置
for (int i = 0; i < 26; i++) {
while (count[i] > 0) {
if (index >= n) {
index = 1;
}
res[index] = (char) (i + 'a');
index += 2;
count[i]--;
}
}
return new String(res);
}
}
  1. leetcode621 任务调度器

16.1 解题思路

1. 完成所有任务所需要的最短时间 = 待命的任务数 + 所有的任务数

image.png
2. 次数最多的任务数只出现了一个的情况
image.png
3. 次数最多的任务数出现了多个的情况
image.png
4. 出现次数最多的任务数大于冷却时间

image.png

16.2 代码实现🔴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码class Solution {
public int leastInterval(char[] tasks, int n) {
int[] counter = new int[26];
int maxAppearCount = 0; // 出现次数最多的任务 最大出现次数
int maxTaskCount = 0; // 出现次数最多的任务 最大任务数量
for (char task : tasks) {
counter[task - 'A']++;
if (counter[task - 'A'] == maxAppearCount) {
maxTaskCount++;
} else if (counter[task - 'A'] > maxAppearCount) {
maxAppearCount = counter[task - 'A'];
maxTaskCount = 1;
}
}

int partCount = maxAppearCount - 1; // 空余部分个数
int partLength = n - (maxTaskCount - 1); // 空余部分的长度
int emptySlots = partCount * partLength; // 空闲槽位数
int availableTasks = tasks.length - maxAppearCount * maxTaskCount; // 剩余可使用任务数
int idles = Math.max(0, emptySlots - availableTasks); // 待命任务数
return tasks.length + idles; // 完成所有任务所需要的最短时间 = 待命的任务数 + 所有的任务数
}
}
  1. leetcode670 最大交换

17.1 解题思路

贪心策略:拿高位后面比高位大的值进行交换,而且越大越好

17.2 代码实现1:暴力

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码class Solution {
public int maximumSwap(int num) {
char[] chars = String.valueOf(num).toCharArray();
int max = num;
for (int i = 0; i < chars.length; i++) {
for (int j = i + 1; j < chars.length; j++) {
swap(chars, i, j);
max = Math.max(max, Integer.parseInt(new String(chars)));
swap(chars, i, j);
}
}
return max;
}

private void swap(char[] chars, int i, int j) {
char temp = chars[i];
chars[i] = chars[j];
chars[j] = temp;
}
}

17.3 代码实现2:🔴贪心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
java复制代码class Solution {
public int maximumSwap(int num) {
char[] chars = String.valueOf(num).toCharArray();

// 记录每个数字出现的最后一次出现的下标
int[] last = new int[10];
for (int i = 0; i < chars.length; i++) {
last[chars[i] - '0'] = i;
}

// 从高位向低位扫描,找到当前位置右边的最大的数字并交换
for (int i = 0; i < chars.length; i++) {
// 找最大,所以倒着找
for (int d = 9; d > chars[i] - '0'; d--) {
if (last[d] > i) {
char temp = chars[i];
chars[i] = chars[last[d]];
chars[last[d]] = temp;
// 只允许交换一次,因此直接返回
return Integer.parseInt(new String(chars));
}
}
}
return num;
}
}
  1. leetcode861 翻转矩阵后的得分

18.1 解题思路

image.png
image.png

18.2 代码实现🔴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
java复制代码class Solution {
public int matrixScore(int[][] grid) {
int rows = grid.length;
int cols = grid[0].length;
// 使得每一行都从 1 开头
for (int row = 0; row < rows; row++) {
if (grid[row][0] == 0) {
// 行翻转
for (int col = 0; col < cols; col++) {
grid[row][col] ^= 1;
}
}
}
int res = 0;
// 1 的数量越多,得到的数值越大
for (int col = 0; col < cols; col++) {
int count = 0;
// 统计第 col 列有多少个1
for (int row = 0; row < rows; row++) {
count += grid[row][col];
}
int maxOneCount = Math.max(count, rows - count);
res += maxOneCount * (1 << (cols - col - 1));
}
return res;
}
}
  1. leetcode1029 两地调度

19.1 解题思路

最优方案:选出price_A - price_B最小的N个人,让他们飞往A市,其余的飞往B市

19.2 代码实现:🔴贪心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码class Solution {
public int twoCitySchedCost(int[][] costs) {
Arrays.sort(costs, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return (o1[0] - o1[1]) - (o2[0] - o2[1]);
}
});

int n = costs.length / 2;
int total = 0;
for (int i = 0; i < n; i++) {
total += costs[i][0] + costs[i + n][1];
}
return total;
}
}
  1. leetcode330 按要求补齐数组

20.1 解题思路

1. 贪心思想:每次加入nums的数都要在满足条件的前提下尽可能的大以覆盖更大的范围。
2. 对于正整数x,如果区间[1, x - 1]内的所有数字都已经被覆盖,且x在数组中,则区间[1, 2x - 1]内的数字也都被覆盖。
image.png
image.png

20.2 代码实现🔴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码class Solution {
public int minPatches(int[] nums, int n) {
int res = 0;
// 贪心的保证 [1, x - 1] 这个区间中所有数字会被覆盖
long x = 1;
int index = 0;
while (x <= n) {
if (index < nums.length && nums[index] <= x) {
// 因为根据贪心思想,我们总保证区间小于x的所有值会被覆盖掉
// [1, x + x - 1] [1, x + nums[index] - 1]
x += nums[index];
index++;
} else {
res++; // 把 x 放入数组中
// 对于正整数 x,如果区间 [1, x - 1] 内的所有数字都已经被覆盖。
// 且 x 在数组中,则区间 [1, 2x - 1] 内的所有数字也都被覆盖
x *= 2;
}
}
return res;
}
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%