抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

在之前刷力扣的过程中,我每刷道题都是先看题解然后再去边写边看,很多内容根本没消化,在进度上欺骗自己,感觉之前刷的都忘了,能力没有什么提升,现在还是一道都不会做。所以这次报了卡哥的算法训练营,希望能够按计划地有效刷题。本次刷题,我用的是 Java 语言解题,有余力的话也可能会加上 C++ 的题解。以下是算法训练营第七天的刷题记录和思考笔记。

一、454 四数相加 II

题目

给你四个整数数组 nums1nums2nums3nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

  • 0 <= i, j, k, l < n
  • nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
1
2
3
4
5
6
输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

解题思路

这道题目是四个独立的数组,只要找到 A[i] + B[j] + C[k] + D[l] = 0 就可以,不用考虑有重复的四个元素相加等于 0 的情况。使用哈希法的 map 可以巧妙解决的问题,提高程序执行效率,降低时间复杂度,当然使用哈希法会提高空间复杂度,但一般来说我们都是舍空间换时间。

先统计前两个数组中的元素之和,同时统计出现的次数,放入 map。再统计剩余的两个元素的和,在 map 中找是否存在相加为 0 的情况,同时记录次数。

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
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
Map<Integer, Integer> map = new HashMap<>();
int temp;
int result = 0;
for (int i : nums1) {
for (int j : nums2) {
temp = i + j;
if (map.containsKey(temp)) {
map.put(temp, map.get(temp) + 1);
} else {
map.put(temp, 1);
}
}
}
for (int i : nums3) {
for (int j : nums4) {
temp = i + j;
if (map.containsKey(0 - temp)) {
result += map.get(0 - temp);
}
}
}
return result;
}
}

二、383 赎金信

题目

给你两个字符串:ransomNotemagazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。如果可以,返回 true ;否则返回 falsemagazine 中的每个字符只能在 ransomNote 中使用一次。

1
2
输入:ransomNote = "a", magazine = "b"
输出:false

解题思路

这道题目和 242 有效的字母异位词很像,有效的字母异位词相当于求字符串 a 和 b 是否可以相互组成 ,而这道题目是求字符串 a 能否组成字符串 b,而不用管字符串 b 能不能组成字符串 a。

这里需要注意两点。第一点为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思说明杂志里面的字母不可重复使用。第二点你可以假设两个字符串均只含有小写字母,说明只有小写字母,那可以采用空间换取时间的哈希策略, 用一个长度为 26 的数组还记录 magazine 里字母出现的次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public boolean canConstruct(String ransomNote, String magazine) {
int[] record = new int[26];
for (char c : magazine.toCharArray()) {
record[c - 'a']++;
}
for (char c : ransomNote.toCharArray()) {
record[c - 'a']--;
}
// 如果数组中存在负数,说明ransomNote字符串总存在magazine中没有的字符
for (int i : record) {
if (i < 0) {
return false;
}
}
return true;
}
}

在本题的情况下,使用 map 的空间消耗要比数组大一些的,因为 map 要维护红黑树或者哈希表,而且还要做哈希函数,是更费时的。所以对于数据量大的字符串,使用数组更加简单直接有效。

三、15 三数之和

题目

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。注意:答案中不可以包含重复的三元组。

1
2
3
4
5
6
7
8
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

解题思路

找出 a + b + c = 0,即 a = nums[i], b = nums[j], c = -(a + b),可以使用哈希法来确定 0-(a+b) 是否在数组里出现过,但是题目中说的不可以包含重复的三元组,需要再去重,这样很容易超时。

这道题目使用双指针法要比哈希法高效一些。

首先将 nums 数组排序,然后有一层 for 循环,i 从下标 0 的地方开始,同时定一个下标 left 定义在 i+1 的位置上,定义下标 right 在数组结尾的位置上。数组中 a = nums[i],b = nums[left],c = nums[right],要使得 a + b + c = 0

如果 nums[i] + nums[left] + nums[right] > 0 就说明此时三数之和大了,因为数组是经过排序后的,所以 right 下标就应该向左移动,这样才能让三数之和小一些。

如果 nums[i] + nums[left] + nums[right] < 0 说明此时三数之和小了,left 就向右移动,才能让三数之和大一些,直到 leftright 相遇为止。

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
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
if (nums[i] > 0) { // 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
return result;
}
if (i > 0 && nums[i] == nums[i - 1]) {
continue; // 去重 a
}
int left = i + 1;
int right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum > 0) {
right--;
} else if (sum < 0) {
left++;
} else {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 去重逻辑应该放在找到一个三元组之后,对 b 和 c 去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
right--;
left++;
}
}
}
return result;
}
}

之前做过的 1 两数之和 就不能用双指针法,因为要求返回的是索引下标,而双指针法一定要排序,一旦排序之后原数组的索引就被改变了。如果两数之和要求返回的是数值的话,就可以使用双指针法了。

四、18 四数之和

题目

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abcd 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按任意顺序返回答案 。

1
2
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

解题思路

本题思路整体和上一题 15 三数之和一样的,都是双指针法,但难在去重和剪枝。

四数之和的双指针解法是在三数之和的基础上再套一层 for 循环。nums[k] + nums[i] 为确定值,依然是循环内有 leftright 下标作为双指针,找出 nums[k] + nums[i] + nums[left] + nums[right] == target 的情况。

需要注意的细节:因为这个题目时自定义 target,所以不能像三数之和那样进行剪枝操作。i 进行剪枝操作的时候要将 ki 看成是一个整体。i 进行去重的时候,应该是 i > k + 1。因为数字之和比较大,所以数据类型应该选择 long,而且要对和进行强转,因为 Java 中字面量默认时 int 类型。

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
40
41
42
43
44
45
46
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);

for (int i = 0; i < nums.length; i++) {

// nums[i] > target 直接返回, 剪枝操作
if (nums[i] > 0 && nums[i] > target) {
return result;
}

if (i > 0 && nums[i - 1] == nums[i]) { // 对nums[i]去重
continue;
}

for (int j = i + 1; j < nums.length; j++) {

if (j > i + 1 && nums[j - 1] == nums[j]) { // 对nums[j]去重
continue;
}

int left = j + 1;
int right = nums.length - 1;
while (right > left) {
// nums[k] + nums[i] + nums[left] + nums[right] > target int会溢出
long sum = (long) nums[i] + nums[j] + nums[left] + nums[right];
if (sum > target) {
right--;
} else if (sum < target) {
left++;
} else {
result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
// 对nums[left]和nums[right]去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;

left++;
right--;
}
}
}
}
return result;
}
}

五、哈希表总结

  1. 数组作为哈希表:数组就是简单的哈希表,但是数组的大小是受限的。如果题中要求只有小写字母,那么就可以考虑用数组。
  2. set 作为哈希表:数组的大小是有限的,受到系统栈空间(不是数据结构的栈)的限制。如果数组空间够大,但哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。所以此时一样的做映射的话,就可以使用 set 了。
  3. map 作为哈希表:map 是一种<key, value>的结构,如果不仅要判断 X 是否存在而且还要记录 X 的下标位置,可以用 key 保存数值,用 value 在保存数值所在的下标,所以使用 map 最为合适。虽然 map 是万能的,但使用 map 的空间消耗要比数组大一些,因为 map 要维护红黑树或者符号表,而且还要做哈希函数的运算。

评论



Copyright © 2020 - 2022 Zhihao Zhuang. All rights reserved

本站访客数: 人,
总访问量: