代码随想录算法训练营第七天|454.四数相加II 、383. 赎金信 、15. 三数之和、18. 四数之和
454.四数相加II
问题简述:在四个数组中各选出一个元素,使其加和为0,返回能满足的四元组个数。
思考:我下面写的用到了两个map。但实际可以只用一个map,然后直接遍历数组即可。
算法思路:将两个数组元素加和的所有可能存入map1作为key,加和key出现的次数作为value。再将剩下两个数组进行同样操作存入map2。遍历map1的value,查看map2是否存在-value,如果存在,则将两个对应的key相乘存入计数器count。
时间复杂度 :O(n^2)
空间复杂度 :O(n)
import java.util.HashMap;
import java.util.Map;
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4){
Map<Integer, Integer> map1 = getMap(nums1, nums2);
Map<Integer, Integer> map2 = getMap(nums3, nums4);
//记录次数
int count = 0;
for (Integer key : map1.keySet()) {
if(map2.containsKey(-key)){
count += map1.get(key) * map2.get(-key);
}
}
return count;
}
//将两个数组的加和存入map
public Map getMap(int[] nums1, int[] nums2){
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums1.length; i++) {
for (int j = 0; j < nums2.length; j++) {
//如果加和不存在则存入map,如果加和存在则增加出现次数value
if(map.containsKey(nums1[i] + nums2[j])){
map.put(nums1[i] + nums2[j], map.get(nums1[i] + nums2[j]) + 1);
}else {
map.put(nums1[i] + nums2[j], 1);
}
}
}
return map;
}
}
383. 赎金信
问题简述:求一个字符串中的字符是否完全来自另一个字符串。
思考:这道题和昨天判断字符串顺序比较相似,较为简单。用到了数组,和昨天的区别是需要最后判断数组元素大于等于0,而不是等于0。
算法思路:用数组的0~25分别存储magazine中小写字母a~z出现的次数,再减去ransomNote中小写字母出现次数,最后看是否各个字母数量都为正。
时间复杂度: O(n)
空间复杂度: O(1)
class Solution {
public boolean canConstruct(String ransomNote, String magazine) {
int[] hash = new int[26];
//将magazine字母存入数组
for (int i = 0; i < magazine.length(); i++) {
hash[magazine.charAt(i)-'a']++;
}
//数组剪去ransomNote中字母出现次数
for (int i = 0; i < ransomNote.length(); i++) {
hash[ransomNote.charAt(i)-'a']--;
}
//遍历数组看是否各个字母数量都为正
for (int i : hash){
if(i < 0){
return false;
}
}
return true;
}
}
15. 三数之和
问题简述:给定一个数组,返回三个不同位置的加和为0的所有三元组,且三元素不得重复。
思考:这道和下一道题感觉是目前最难的题,用到了双指针,自己写加上看别人写的最后完成,需要注意的点超级超级多,以后要反复学习。
算法思路:先将数组排序,依次遍历每个元素作为a,将每个元素后的left指针指向b,rigth指针指向c。每次遍历时,如果 a > 0 则直接返回(去枝操作,为了提高运行效率)。同时每次遍历完a后进行去重操作,防止a这个数字遍历了多次。当a确定时,查看此时a、b、c的和是否为0,如果为0直接存入结果,并对b、c进行去重;如果大于0则c左移使加合变小,如果小于0则b右移使加合变大。最终返回三元组的集合。
时间复杂度 O(n^2)
空间复杂度 O(1)
import java.util.*;
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> listOfLists = new ArrayList<>();
Arrays.sort(nums);
//i遍历a, left:b, right:c
for (int i = 0; i < nums.length; i++) {
//如果后面的都为正数了,则不用继续了
if (nums[i] > 0) {
return listOfLists;
}
//保证a不重复,注意是要比较i和i - 1。因为三元组中允许有重复元素,在去重时,如果a有重复,第一次a出现时候,后面是存在与a相同的元素的,这样保证三元组中可以有重复元素
if (i > 0 && nums[i] == nums[i - 1]) {
continue;//跳过这轮循环
}
//取b为a后的的数,保证a、b索引不一样
int left = i + 1;
int right = nums.length - 1;
while (left < right){
if (nums[i] + nums[left] + nums[right] == 0){
listOfLists.add(Arrays.asList(nums[i], nums[left], nums[right]));
//进行b、c去重,即如果出现重复元素,将left和right向中间移动。因为我们在第一次遇到left和right指向元素时,比较的范围是包含left和right重复元素的,所以接下来遇到直接去重
while (left < right && nums[left + 1] == nums[left]) left++;
while (left < right && nums[right - 1] == nums[right]) right--;
//成立的三元组添加后,b和c一定都会改变
right--;
left++;
//加和大于0,c需要减小
}else if (nums[i] + nums[left] + nums[right] > 0){
right--;
//加和小于0,b需要增大
}else if (nums[i] + nums[left] + nums[right] < 0){
left++;
}
}
}
return listOfLists;
}
}
18. 四数之和
问题简述:给定一个数组,返回四个不同位置的加和为target的所有四元组,且四元素不得重复。
思考:三数之和的plus版,将三数之和的对a的一重遍历变为对a和b的二重遍历,值得注意的点还有很多,注意要去枝,去枝不到位可能会超时和报错。还有就是用sum表示了十个数字加和,不用也会超时。
算法思路:先将数组排序,依次二重遍历每个元素作为a和b,且b的初始位置为a的下一个元素,将b元素后的left指针指向c,rigth指针指向d。每次遍历时,如果 a > 0 且a还大于target,则直接返回(去枝操作,为了提高运行效率)。同时每次遍历完a后进行去重操作,防止a这个数字遍历了多次。当a确定时,此时遍历b,同样进行一次剪枝操作,如果 a + b > 0 且a还大于target,则跳出循环。最后判断四数之和是否为target,如果为target则直接将四元组存入集合,并对c、d进行去重;如果大于target则d左移使加合变小,如果小于target则c右移使加合变大。最终返回四元组的集合。
时间复杂度 O(n^3)
空间复杂度 O(1)
import java.util.*;
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> listOfLists = new ArrayList<>();
Arrays.sort(nums);
//i遍历a, j:b, left:c, right:d
for (int i = 0; i < nums.length; i++) {
//剪枝,注意和上一题的区别,负数可能越加越小
if (nums[i] > 0 && nums[i] >target){
return listOfLists;
}
//为a去重
if (i > 0 && nums[i] == nums[i - 1]){
continue;
}
for (int j = i + 1; j < nums.length; j++) {
//剪枝
if (nums[i] + nums[j] > 0 && nums[i] + nums[j] >target){
//注意这次剪枝不能直接返回,因为下次nums[i]可能再次变小
break;
}
//去重b,这里问题卡了很久,注意i要大于j+1,否则b就和a比大小了,实际上应该是下一个b和前一个b比大小
if (j > i + 1 && nums[j] == nums[j - 1]){
continue;
}
int left = j + 1;
int right = nums.length - 1;
while (left < right){
int sum = nums[i] + nums[j] + nums[left] + nums[right];
if (sum == target){
listOfLists.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
//进行b、c去重,即如果出现重复元素,将left和right向中间移动
while (left < right && nums[left + 1] == nums[left]) left++;
while (left < right && nums[right - 1] == nums[right]) right--;
left++;
right--;
//加和大于0,c需要减小
}else if (sum > target){
right--;
//加和小于0,b需要增大
}else if (sum < target){
left++;
}
}
}
}
return listOfLists;
}
}
感想
感觉今天的很有难度,特别是后两个,不过明天就是字符串了,坚持!