目录
第一题
题目来源
题目内容
解决方法
方法一:二分+贪心
方法二:二分+DP
第二题
题目来源
题目内容
解决方法
方法一:双指针
方法二:暴力搜索
方法三:排序
第三题
题目来源
题目内容
解决方法
方法一:回溯算法
方法二:队列
方法三:递归
方法四:迭代
2560. 打家劫舍 IV - 力扣(LeetCode)
根据题目描述,我们可以使用二分查找和贪心思路来解决这个问题。
首先,我们定义一个函数check用于判断给定的窃取能力是否满足条件。在该函数中,我们使用贪心策略。我们遍历房屋金额数组nums,对于每个房屋,如果其金额小于等于给定的窃取能力,我们将计数器count加1,并且跳过下一个房屋(因为相邻房屋不能被同时窃取)。最后,如果count大于等于k,说明当前的窃取能力满足条件,返回true;否则,返回false。
接下来,我们要找到满足条件的最小窃取能力。我们可以进行二分查找。我们初始化左边界left为0,右边界right为数组中的最大值。然后,我们不断进行二分查找,直到left和right相邻为止。在每次查找中,我们计算中间值mid,并调用check函数来判断给定的窃取能力是否满足条件。如果满足条件,说明我们可以尝试更小的窃取能力,所以我们将right更新为mid;否则,说明窃取能力太小,我们需要尝试更大的值,所以我们将left更新为mid。最后,返回right作为结果。
public class Solution {
public int minCapability(int[] nums, int k) {
int left = 0, right = 0;
for (int x : nums) {
right = Math.max(right, x);
}
while (left + 1 < right) {
int mid = (left + right) >>> 1;
if (check(nums, k, mid)) {
right = mid;
} else {
left = mid;
}
}
return right;
}
private boolean check(int[] nums, int k, int mx) {
int count = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] <= mx) {
count++;
i++;
}
}
return count >= k;
}
}
复杂度分析:
LeetCode运行结果:
首先,我们初始化窃取能力的范围,将 left 设置为 0,将 right 设置为 nums 数组中的最大值。
然后,在每次循环中,我们计算 mid 作为窃取能力的候选值,使用二分查找的思想,将窃取能力的范围从 (left, right] 缩小到 (left, mid] 或 (mid, right]。
接着,我们调用 check 方法判断以 mid 作为窃取能力是否满足条件。在 check 方法中,我们使用两个变量 f0 和 f1 来表示前一间房屋被窃取和当前房屋被窃取的情况下,窃贼已窃取的房屋数目。我们遍历给定的 nums 数组,根据房屋的价值和窃取能力来更新 f0 和 f1。如果房屋的价值大于窃取能力,则窃贼必须从前一间房屋开始才能窃取当前房屋,因此更新 f0 为 f1(即当前房屋不被窃取)。如果房屋的价值不大于窃取能力,则窃贼可以选择窃取当前房屋或不窃取,取决于哪种情况下窃贼窃取的房屋数目更多,即选择较大的值更新 f1。最后,我们判断 f1 是否大于等于 k,如果是则返回 true,表示窃取能力满足条件,否则返回 false。
class Solution {
public int minCapability(int[] nums, int k) {
int left = 0, right = 0;
for (int x : nums) {
right = Math.max(right, x);
}
while (left + 1 < right) { // 开区间写法
int mid = (left + right) >>> 1;
if (check(nums, k, mid)) {
right = mid;
} else {
left = mid;
}
}
return right;
}
private boolean check(int[] nums, int k, int mx) {
int f0 = 0, f1 = 0;
for (int x : nums) {
if (x > mx) {
f0 = f1;
} else {
int tmp = f1;
f1 = Math.max(f1, f0 + 1);
f0 = tmp;
}
}
return f1 >= k;
}
}
复杂度分析:
LeetCode运行结果:
16. 最接近的三数之和 - 力扣(LeetCode)
这个问题可以使用双指针来解决。首先,对数组进行排序。
然后,我们可以使用三个指针i,left和right来表示三个整数的位置。我们固定指针i,并使用左右指针left和right去寻找与target最接近的和。具体的算法如下:
import java.util.Arrays;
class Solution {
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int n = nums.length;
int closestSum = nums[0] + nums[1] + nums[2]; // 初始化closestSum
for (int i = 0; i < n - 2; i++) {
int left = i + 1;
int right = n - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == target) {
return sum;
}
if (Math.abs(sum - target) < Math.abs(closestSum - target)) {
closestSum = sum;
}
if (sum > target) {
right--;
} else {
left++;
}
}
}
return closestSum;
}
}
复杂度分析:
综上所述,该算法的时间复杂度为O(N^2),空间复杂度为O(1)。
LeetCode运行结果:
除了使用双指针的方法外,我们还可以考虑使用暴力搜索的方法来解决这个问题。具体的算法如下:
class Solution {
public int threeSumClosest(int[] nums, int target) {
int n = nums.length;
int closestSum = Integer.MAX_VALUE; // 初始化closestSum
for (int i = 0; i < n - 2; i++) {
for (int j = i + 1; j < n - 1; j++) {
for (int k = j + 1; k < n; k++) {
int sum = nums[i] + nums[j] + nums[k];
if (sum == target) {
return sum;
}
if (Math.abs(sum - target) < Math.abs(closestSum - target)) {
closestSum = sum;
}
}
}
}
return closestSum;
}
}
复杂度分析:
综上所述,使用暴力搜索的方法的时间复杂度为O(N^3),空间复杂度为O(1)。如果数组规模较小,这种方法也是可行的。但是当数组规模较大时,双指针的方法效率更高。
LeetCode运行结果:
除了双指针和暴力搜索的方法,还可以考虑利用排序来优化算法。具体思路如下:
class Solution {
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums); // 排序数组
int n = nums.length;
int closestSum = nums[0] + nums[1] + nums[2]; // 初始化closestSum
for (int i = 0; i < n - 2; i++) {
int left = i + 1;
int right = n - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == target) {
return sum;
}
if (Math.abs(sum - target) < Math.abs(closestSum - target)) {
closestSum = sum;
}
if (sum > target) {
right--;
} else {
left++;
}
}
}
return closestSum;
}
}
复杂度分析:
综上所述,利用排序来优化的方法的时间复杂度为O(N^2logN),空间复杂度为O(1)。该方法在处理较大规模的问题时效率更高。
LeetCode运行结果:
17. 电话号码的字母组合 - 力扣(LeetCode)
这个问题可以使用回溯算法来解决。回溯算法是一种通过不断尝试所有可能的解决方案来求解问题的方法。
我们可以将数字和字母的映射关系存储在一个哈希表中,然后定义一个递归函数来生成所有可能的字母组合。具体步骤如下:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class Solution {
private Map letters = new HashMap<>();
public List letterCombinations(String digits) {
List combinations = new ArrayList<>();
if (digits == null || digits.length() == 0) {
return combinations;
}
letters.put('2', "abc");
letters.put('3', "def");
letters.put('4', "ghi");
letters.put('5', "jkl");
letters.put('6', "mno");
letters.put('7', "pqrs");
letters.put('8', "tuv");
letters.put('9', "wxyz");
backtrack(combinations, digits, 0, new StringBuilder());
return combinations;
}
private void backtrack(List combinations, String digits, int index, StringBuilder current) {
if (index == digits.length()) {
combinations.add(current.toString());
return;
}
char digit = digits.charAt(index);
String letter = letters.get(digit);
for (int i = 0; i < letter.length(); i++) {
current.append(letter.charAt(i));
backtrack(combinations, digits, index + 1, current);
current.deleteCharAt(current.length() - 1);
}
}
}
复杂度分析:
因此,使用回溯算法可以解决这个问题,并且时间复杂度和空间复杂度都是较优的。
LeetCode运行结果:
除了回溯算法之外,还可以使用队列(或者广度优先搜索)来解决这个问题。
我们可以将数字和字母的映射关系存储在一个哈希表中,然后从左到右遍历输入字符串的每个数字。对于每个数字,将其对应的字母集合中的每个字母与之前已经生成的所有组合进行拼接,形成新的组合,并将新的组合加入队列中。重复这个过程直到遍历完所有数字。
具体步骤如下:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
class Solution {
private Map letters = new HashMap<>();
public List letterCombinations(String digits) {
List combinations = new ArrayList<>();
if (digits == null || digits.length() == 0) {
return combinations;
}
letters.put('2', "abc");
letters.put('3', "def");
letters.put('4', "ghi");
letters.put('5', "jkl");
letters.put('6', "mno");
letters.put('7', "pqrs");
letters.put('8', "tuv");
letters.put('9', "wxyz");
Queue queue = new LinkedList<>();
queue.offer("");
for (int i = 0; i < digits.length(); i++) {
char digit = digits.charAt(i);
String letter = letters.get(digit);
int size = queue.size();
while (size > 0) {
String curr = queue.poll();
for (int j = 0; j < letter.length(); j++) {
queue.offer(curr + letter.charAt(j));
}
size--;
}
}
while (!queue.isEmpty()) {
combinations.add(queue.poll());
}
return combinations;
}
}
复杂度分析:
这种解法相比回溯算法而言,更利于处理大量输入,因为它不需要递归调用,而是使用队列进行遍历和拼接。
LeetCode运行结果:
除了回溯算法、队列,我们还可以使用递归来解决这个问题。
我们可以将问题划分为子问题,每次递归都处理当前数字对应的字母集合中的一个字母,并将递归结果与其他数字的组合进行拼接,形成最终的结果。具体步骤如下:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class Solution {
private Map letters = new HashMap<>();
public List letterCombinations(String digits) {
List combinations = new ArrayList<>();
if (digits == null || digits.length() == 0) {
return combinations;
}
letters.put('2', "abc");
letters.put('3', "def");
letters.put('4', "ghi");
letters.put('5', "jkl");
letters.put('6', "mno");
letters.put('7', "pqrs");
letters.put('8', "tuv");
letters.put('9', "wxyz");
backtrack(0, digits, new StringBuilder(), combinations);
return combinations;
}
private void backtrack(int index, String digits, StringBuilder current, List combinations) {
if (index == digits.length()) {
combinations.add(current.toString());
return;
}
char digit = digits.charAt(index);
String letter = letters.get(digit);
for (char c : letter.toCharArray()) {
current.append(c);
backtrack(index + 1, digits, current, combinations);
current.deleteCharAt(current.length() - 1);
}
}
}
复杂度分析:
这种解法利用了递归的思想,将问题划分为子问题,并通过不断拼接字母来构建最终结果。递归方法简洁直观,但在处理大量输入时可能会导致栈溢出。因此,在实际应用中,需要注意输入规模的限制。
LeetCode运行结果:
除了前面提到的回溯算法、队列和递归,还可以使用迭代的方法来解决这个问题。
迭代的思路是,从左到右依次处理输入字符串中的每个数字,并利用一个临时列表来保存当前位置之前的所有字母组合。对于每个新的数字,将其对应的字母与临时列表中的每个字母组合进行拼接,形成新的字母组合,并更新临时列表。重复这个过程,直到处理完所有数字,最终得到的临时列表中即为结果。
具体步骤如下:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class Solution {
private Map letters = new HashMap<>();
public List letterCombinations(String digits) {
List combinations = new ArrayList<>();
if (digits == null || digits.length() == 0) {
return combinations;
}
letters.put('2', "abc");
letters.put('3', "def");
letters.put('4', "ghi");
letters.put('5', "jkl");
letters.put('6', "mno");
letters.put('7', "pqrs");
letters.put('8', "tuv");
letters.put('9', "wxyz");
combinations.add("");
for (int i = 0; i < digits.length(); i++) {
String letter = letters.get(digits.charAt(i));
int size = combinations.size();
for (int j = 0; j < size; j++) {
String prev = combinations.remove(0);
for (char c : letter.toCharArray()) {
combinations.add(prev + c);
}
}
}
return combinations;
}
}
复杂度分析:
迭代方法是一种非常高效的解法,它避免了递归调用栈的开销,适用于处理大量输入的情况。
LeetCode运行结果: