四数相加
看完题后的思路
首先想到了暴力解法,四重for循环,时间复杂度为0(n^4).接着往下想,四数相加不正好是(两数相加)+(两数相加)吗,可以依次求出两数相加的结果存入hash表中 和-[下标1,下标2],然后再进行两数相加.
思路
上面的思路中,有两点疑问:
- (1+2)+(3+4)=(1+3)+(2+4)吗? 1,2,3,4代表数组
是相等的,
如上图,挑选(0,0,0,1),如果挑选数组是(1+2)+(3+4),反映到值上就是 (1-2)+(-1+2),如果是(1+3)+(2+4),反映到值上就是(1-1)+(-1+2),四个家加数并没有改变,改变只是加数的顺序
代码
// 454 四数相加
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
HashMap<Integer, List<int[]>> sum1_2 = towSumOfArra(nums1, nums2);
HashMap<Integer, List<int[]>> sum3_4 = towSumOfArra(nums3, nums4);
//开始第二层求和
return twoSum02(sum1_2, sum3_4);
}
public HashMap<Integer, List<int[]>> towSumOfArra(int[] nums1,int nums2[]){
HashMap<Integer, List<int[]>> map = new HashMap<>();
for (int i = 0; i <nums1.length; i++) {
for (int j = 0; j < nums2.length; j++) {
int sum=nums1[i]+nums2[j];
List<int[]> list = map.getOrDefault(sum, new ArrayList<>());
list.add(new int[]{i,j});
map.put(sum,list);
}
}
return map;
}
public int twoSum02( HashMap<Integer, List<int[]>> map1, HashMap<Integer, List<int[]>> map2) {
int res=0;
for (Map.Entry<Integer, List<int[]>> entry : map1.entrySet()) {
Integer key = entry.getKey();
if (map2.containsKey(-1*key)){
res+=(map1.get(key).size()*map2.get(-1*key).size());
}
}
return res;
}
}
复杂度
暴力:
时间复杂度 0(n^4)
空间复杂度 0(1)
hash表法
时间复杂度 0(n^2) (两两求和时)
空间复杂度0(n^2)
难点 收获
错误的将数组两两组合,再相加,导致结果重复
// 454 四数相加
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
HashMap<Integer, List<int[]>> sum1_2 = towSumOfArra(nums1, nums2);
HashMap<Integer, List<int[]>> sum1_3 = towSumOfArra(nums1, nums3);
HashMap<Integer, List<int[]>> sum1_4 = towSumOfArra(nums1, nums4);
HashMap<Integer, List<int[]>> sum2_3 = towSumOfArra(nums2, nums3);
HashMap<Integer, List<int[]>> sum2_4 = towSumOfArra(nums2, nums4);
HashMap<Integer, List<int[]>> sum3_4 = towSumOfArra(nums3, nums4);
//开始第二层求和
return twoSum02(sum1_2, sum3_4)+twoSum02(sum1_3,sum2_4)+twoSum02(sum1_4,sum2_3);
}
public HashMap<Integer, List<int[]>> towSumOfArra(int[] nums1,int nums2[]){
HashMap<Integer, List<int[]>> map = new HashMap<>();
for (int i = 0; i <nums1.length; i++) {
for (int j = 0; j < nums2.length; j++) {
int sum=nums1[i]+nums2[j];
List<int[]> list = map.getOrDefault(sum, new ArrayList<>());
list.add(new int[]{i,j});
map.put(sum,list);
}
}
return map;
}
public int twoSum02( HashMap<Integer, List<int[]>> map1, HashMap<Integer, List<int[]>> map2) {
int res=0;
for (Map.Entry<Integer, List<int[]>> entry : map1.entrySet()) {
Integer key = entry.getKey();
if (map2.containsKey(-1*key)){
res+=(map1.get(key).size()*map2.get(-1*key).size());
}
}
return res;
}
三刷时再看一遍
三数之和
看完题后的思路
想到了排序,使用双指针指向正数最小与负数最小,希望正数最小+负数最小能够递增,这样第三个指针从最大处递减
但是两个指针移动相同的方向,都是递增,所以无法一遍筛选出所有的情况
思路
排完序,i从0开始,left从i-1开始,right从数组末尾开始,通过left与right的移动来判断包含i的,且i是最小的元素的加和中有没有符合条件的
为什么left要从i+1开始?
因为i前面的的已经判断完,其作为最小的被加数的情况已经判断完
问题: 如何去重
去重的逻辑
三元组不能重复,三元组内的元素可以重复
对于三个数 a,b,c 分别对应nums[minItermIndex] nums[left] nums[right],如果nums[minItermIndex]==nums[minItermIndex-1],则重复,因为以a为最小元素的和在minItermIndex-1已经找遍了.当找到a+b+c=0时,此时不能跳出循环,因为可能还有例如 -2 -1+3与-2+0-2,所以left与right也要去重
while (left<right&&nums[left]==nums[left+1]){
left++;
}
while (left<right&&nums[right]==nums[right-1]){
right--;
}
left++;
right--;
}
代码
public List<List<Integer>> threeSum(int[] nums) {
int minItermIndex=0;// 最小一项的下标
Arrays.sort(nums);
ArrayList<List<Integer>> lists = new ArrayList<>();
for (; minItermIndex < nums.length-2; minItermIndex++) {
// 去重 如果
if (minItermIndex>0&&nums[minItermIndex-1]==nums[minItermIndex]){
continue;
}
int left=minItermIndex+1,right=nums.length-1;
int minIterm = nums[minItermIndex];
while (left<right){
int sum = minIterm + nums[left] + nums[right];
if (sum<0){
left++;
}else if (sum==0){
ArrayList<Integer> list = new ArrayList<>();
list.add(nums[minItermIndex]);
list.add(nums[left]);
list.add(nums[right]);
lists.add(list);
//不能 break; 因为可能还有 等于0的组合
//去重02 当最小元素定下来,left不能与上一种相同 0-1+1 与 0+0-0
while (left<right&&nums[left]==nums[left+1]){
left++;
}
while (left<right&&nums[right]==nums[right-1]){
right--;
}
left++;
right--;
}else {
right--;
}
}
}
return lists;
}
复杂度
时间复杂度 0(n^2) 每一轮双指针开销是0(1),一共n轮
空间复杂度 0(1)
收获
- 学到了一种双指针双向单调的情形,排序数组,双指针指向两端
- 欢动窗口中,也是一种双指针双向单调的情形,left+1,窗口减小,right+1,窗口增加
三刷还要再来一遍
四数之和
四数之和与三数之和本质上一样,只粘出代码
// 四数之和
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums);
ArrayList<List<Integer>> lists = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
// 去重
if (i>0&&nums[i]==nums[i-1]){
continue;
}
// 剪枝
// 剪枝
if ((target<=0&&nums[i]>0)){
break;
}
for (int j = i+1; j <nums.length ; j++) {
if (j>i+1&&nums[j]==nums[j-1]){
continue;
}
long sum_ij = nums[i] + nums[j];
long diff = target - sum_ij;
int left=j+1,right=nums.length-1;
while (left<right){
int sum =nums[left] + nums[right];
if (sum<diff){
left++;
}else if (sum==diff){
ArrayList<Integer> list = new ArrayList<>();
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[left]);
list.add(nums[right]);
lists.add(list);
//不能 break; 因为可能还有 等于0的组合
//去重02 当最小元素定下来,left不能与上一种相同 0-1+1 与 0+0-0
while (left<right&&nums[left]==nums[left+1]){
left++;
}
while (left<right&&nums[right]==nums[right-1]){
right--;
}
left++;
right--;
}else {
right--;
}
}
}
}
return lists;
}
三刷再来一遍
赎金信
太简单了,粘贴代码如下
// 赎金信
public boolean canConstruct(String ransomNote, String magazine) {
if (ransomNote.length()>magazine.length()){
return false;
}
int ransomNoteMap[]=new int[26];
int magazineMap[]=new int[26];
for (int i = 0; i < ransomNote.length(); i++) {
char c = ransomNote.charAt(i);
ransomNoteMap[c-'a']++;
}
for (int i = 0; i < magazine.length(); i++) {
char c = magazine.charAt(i);
magazineMap[c-'a']++;
}
for (int i = 0; i < 26; i++) {
if (ransomNoteMap[i]>magazineMap[i]){
return false;
}
}
return true;
}