解一些算法题时,我们可以借助语言标准库里的方法或数据结构,它能够有效的帮助我们。比如下面的两道LeetCode上的题目,这两道题很类似,都是求交集问题,但又有很大的不同。
Q1:给定两个数组,编写一个函数来计算它们的交集。
说明:输出结果中的每个元素一定是唯一的。
我们可以不考虑输出结果的顺序。
例如输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2]
Q2:给定两个数组,编写一个函数来计算它们的交集。
说明:
输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。我们可以不考虑输出结果的顺序。
例如:输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2,2]
一、分析一下题目
我们发现题目可以说是完全一样的,都是求两个数组的交集,只不过一个是不含重复元素,另一个需要把所有的交集的数全找出来,包含重复元素。
我们或许可以想到的是如果这是两个有序数组,就好办了,只需要为两个数组分别准备一个指针,并同时比较两个数组,移动指针,根据相对应的题目要求存储结果。那既然这不是有序数组,我们也可以用较快的排序算法或者使用语言标准库里的排序方法给它排好序。然后再解答。
比如对上面第二题的解答方法
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
Arrays.sort(nums1);
Arrays.sort(nums2);
int i = 0, j = 0;
List<Integer> intersection = new ArrayList<Integer>();
while (i < nums1.length && j < nums2.length) {
if (nums1[i] < nums2[j]) {
i++;
}
else if (nums1[i] > nums2[j]) {
j++;
}
else {
intersection.add(nums1[i]);
i++;
j++;
}
}
int[] result = new int[intersection.size()];
for (int k = 0; k < intersection.size(); k++) {
result[k] = intersection.get(k);
}
return result;
}
二、这种方法很容易理解,效率也比较高,但是这不是我这里要说的重点。
这两道题我们透过本质来看的话,第一题可以看成在一个数组里找是否含有某个元素,第二题可以看出这个元素在这个数组出现了几次。
所以第一题其实可以用我们的set容器,set容器有个特点,不存储重复的元素,所以当我们遍历一遍数组把它放入set容器中时,它的重复元素就自动被筛掉了。之后我们就可以对第二个数组里的元素在第一个数组中进行查找就好了。找到的元素就是我们的最后的解。
话不多说,上一坛代码
public class InserSection {
public int[] intersection(int[] nums1, int[] nums2) {
//1.定义一个set容器
Set<Integer> record=new HashSet();
List<Integer> res=new ArrayList<>(); //保存交集的元素
//2.数组1放入到set容器中
for(int i=0;i<nums1.length;i++)
record.add(nums1[i]);
//3.查找数组2在数组1存在的元素,放入list集合中
int j=0;
for(int i=0;i<nums2.length;i++){
if (record.contains(nums2[i])) {
res.add(nums2[i]);
record.remove(nums2[i]);
}
}
//4.由于结果需要输出一个数组,定义一个结果数组保存结果
int resultArray[]=new int[res.size()];
for(int i=0;i<res.size();i++)
resultArray[i]=res.get(i);
return resultArray;
}
三、那么对于第二题很明显就不能用set容器了,因为我们需要的结果包含重复的元素。就需要另外一种容器了,是什么呢?
因为我们需要对某个元素进行统计出现的次数。没错,所以我们需要借助map容器,这个容器的特点就是键值对<key,value>的方式存储元素。我们让它的键key为元素值,值value就是这个键对应元素出现的次数。
话不多说,再来一坛代码
public int[] intersect(int[] nums1, int[] nums2) {
//1.定义一个Map<key,value>,key表示数组的元素,value表示该元素出现的次数
Map<Integer,Integer> record=new HashMap<>();
List<Integer> res=new ArrayList<>(); //保存交集的元素
//2.初始化map集合元素
for (int i = 0; i < nums1.length; i++) {
//值作为键,次数作为值
Integer prev=record.get(nums1[i]);
//如果键为i的值为空,那么次数为1,否则累加
record.put(nums1[i],prev==null?1:prev+1);
}
//3.遍历第二个数组
for (int i = 0; i < nums2.length; i++) {
Integer prev=record.get(nums2[i]); //获取第i个元素出现的次数
if(prev!=null){
res.add(nums2[i]); //将元素放入到结果集合res中
if (prev==1) //并当元素只剩余一次的时候移除容器map中
record.remove(nums2[i]);
else
record.put(nums2[i],prev-1); //将元素的出现的次数减1
}
}
//4.将结果从res集合转移到结果数组
int[] resultArray=new int[res.size()];
for(int i=0;i<res.size();i++){
resultArray[i]=res.get(i);
}
return resultArray;
}
四、小结一下
虽然我们上面的解法效率可能不如其他的解法好,但是我想说的是,我们可以当我们对语言标准库里的方法和一些数据结构的特点比较了解和熟悉,它会对我们解答一些算法题有很大的帮助。可能在这两道题目比较简单,所以不是很明显的突出它的优点。不过在后面我还会继续分享关于这部分的内容。