Leetcode欢乐数202/两数之和1/四数相加454/三数之和15/四数之和18

前言

Leetcode202/1/454/15/18

一、202题(欢乐数)

题目描述
编写一个算法来判断一个数 n 是不是快乐数。
快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
示例 1:
输入:n = 19 输出:true
解释:
12 + 92 = 82;82 + 22 = 68;62 + 82 = 100;12 + 02 + 02 = 1
示例 2:
输入:n = 2 输出:false
提示:1 <= n <= 231 - 1

题解1

双指针题解

class Solution {
    public boolean isHappy(int n) {
        //快乐数:各位置平方和为1
        //无论是什么数字,过程都是一个循环单链表,类似环形链表那题
        //如果各位数字之和为1,则循环点就是1
        //如果各位数字之和永远不为1,则循环点是非1的数字
        int slow = n;
        int fast = step(n);
        while(slow!=fast){
            slow = step(slow);//每次走一格
            fast = step(step(fast));//每次走两格
        }
        return slow==1;
    }
    // 写一个方法返回n的各位数字之和
    public int step(int n){
        int ans = 0;
        int x;
        while(n!=0){
            x = n%10;
            ans += x*x;
            n = n/10;
        }
        return ans;
    }
}

算法思路

  • 2 31 − 1 2^{31}-1 2311是一个10位数,如果n是10个9,各位值的平方和数最大是81*10=810,也就是说,平方数一定位于1~810,所以最多操作810次,之后就会进入一个循环点。那么快乐数和非快乐数的区别在于,循环点是否为1。解释:快乐数的循环一定为1,比如19这个数变成100之后,就会一直是1,平方和一直是1,所以循环点是1
  • 非快乐数的循环点是非1数,比如2:2->4->16->37->58->89->145->42->20->4,循环点是4。这一连串的数字可以看成是一个循环单链表,那么这题分析到这里,就和第142题环形链表思路相同了。
  • 使用双指针,fast比slow指针多走一格。所以先要实现一个方法,step(n),表示下一步的节点值。那么slow走一格就是slow=step(slow),而fast走两步就是fast=step(step(fast))。剩下的就和第142题一样了。

题解2(哈希表)

当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了(来自卡哥语录)

class Solution {
    public boolean isHappy(int n) {
        Set<Integer> record = new HashSet<>();
        while (n != 1 && !record.contains(n)) {
            record.add(n);
            n = getNextNumber(n);
        }
        return n == 1;
    }

    private int getNextNumber(int n) {
        int res = 0;
        while (n > 0) {
            int temp = n % 10;
            res += temp * temp;
            n = n / 10;
        }
        return res;
    }
}

算法思路:这个就是利用“不断进行平方和替换原有数操作”必然出现循环节点,用set来存这个过程中的平方数。当出现某个数在set中已经包含时,退出循环。如果该循环节点是1,则是快乐数,否则不是。

二、1题(两数之和)

题目描述
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
mine:击败5.06%用户,,ԾㅂԾ,,
该解法问题比较大,已在本题结尾的注释中讲明原因,请移步解法2

class Solution {
    public int[] twoSum(int[] nums, int target) {
        // 先把数组中的元素变成map
        HashMap<Integer,Integer> result  = new HashMap<>();
        int minnum = Integer.MAX_VALUE;
        int maxnum = Integer.MIN_VALUE;
        if(target%2!=0){
            for(int i=0;i<nums.length;i++){
                minnum = minnum<nums[i]?minnum:nums[i];
                maxnum = maxnum>nums[i]?maxnum:nums[i];
                result.put(nums[i], i);
            }
        }
        else{
            //特殊判断target为偶数且数组中含两个target/2的情况
            for(int i=0;i<nums.length;i++){
                if(nums[i]==target/2&&result.containsKey(nums[i])){
                    return new int[]{result.get(target/2),i};
                }
                minnum = minnum<nums[i]?minnum:nums[i];
                maxnum = maxnum>nums[i]?maxnum:nums[i];
                result.put(nums[i], i);
            }
        }
        // 然后分解target
        for(int i=minnum;i<=maxnum&&i<=target/2;i++){
            if(result.containsKey(i)&&result.containsKey(target-i)){
                return new int[]{result.get(i),result.get(target-i)};
            }
        }
        return null;
    }
}

算法思路:把数组变成<value,index>的HashMap,并找到数组的最小值和最大值。在最小值到max(最大值,target/2)之间遍历i,同时查询HashMap是否同时含有(i,target-i),如果同时含有就返回他们的键码值即可。
易错点:如果target为偶数,可能会出现两个值为target/2的数,比如[3,3],target为6,应该返回[0,1],却返回了[1,1]。所以需要特数处理,如果target是偶数,如果map中已经有了一个target/2,则再次出现target/2时,直接返回[result.get(target/2),current_i]。

题解2

讲解
题解1思路的优化版本

public int[] twoSum(int[] nums, int target) {
    int[] res = new int[2];
    if(nums == null || nums.length == 0){
        return res;
    }
    Map<Integer, Integer> result = new HashMap<>();
    for(int i = 0; i < nums.length; i++){
        int temp = target - nums[i];   // 遍历当前元素,并在map中寻找是否有匹配的key
        if(result.containsKey(temp)){
            res[1] = i;
            res[0] = result.get(temp);
            break;
        }
        result.put(nums[i], i);    // 如果没找到匹配对,就把访问过的元素和下标加入到map中
    }
    return res;
}

算法思路:同样也是使用HashMap的数据结构。遍历数组,边遍历边讲数组以<value,index>的格式加入map中。假设当前正在遍历数组的第i个元素,需要判断:是否存在key值为(target-i)的元素,如果存在则返回ans=[result.get(target-i),i]。
注释:这个思路就更为清晰,省去了判断两个target/2在数组中的情况。没想到这个思路的原因在于,一看到这个题我的第一反应是O( n 2 n^2 n2)的暴力解法,之后才思考用map求解,但还是带了一些暴力解法的思想,虽然复杂度比O( n 2 n^2 n2)要低,但是如果数组是[-1e9,1e9],target是0,我的复杂度是2e9,暴力解法是4,这样看来解法1问题很大。

三、454题(四数相加)

题目描述
给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        int res = 0;
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        //统计两个数组中的元素之和,同时统计出现的次数,放入map
        for (int a : nums1) {
            for (int b : nums2) {
                map.put(a+b, map.getOrDefault(a+b, 0) + 1);
            }
        }
        //统计剩余的两个元素的和,在map中找是否存在相加为0的情况,同时记录次数
        for (int c : nums3) {
            for (int d : nums4) {
                res += map.getOrDefault(-(c+d), 0);
            }
        }
        return res;
    }
}

算法思路

  • a+b+c+d,即a+b=-(c+d)。由于只需要统计出现的次数,所以可以遍历(a,b),用map<key,value>记录,其中key存(a+b),value存key出现的次数。
  • 再遍历(c,d),在map中找是否存在-(c+d)即可。

四、15题(三数之和)

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

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(nums);
	// 找出a + b + c = 0
        // a = nums[i], b = nums[left], c = nums[right]
        for (int i = 0; i < nums.length; i++) {
	    // 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
            if (nums[i] > 0) { 
                return result;
            }

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

            int left = i + 1;
            int right = nums.length - 1;
            while (right > left) {
                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;
    }
}

算法思路双指针

  • [i,left,right]是三元组,i用来遍历数组,固定为三元组的第一个元素。[left,right]是双指针,寻找三元组的第二、三位元素。
  • 对数组从小到大排序,left=i+1,right=nums.length-1。当[i,left,right]和小于0时,left++,大于0时right–,直到left==right。
  • 由于题目要求三元组不能重复,如nums[] = [-1,0,1,2,-1,-4],可以是[-1,0,1],也可以是[0,1,-1],但这两个重复了。所以这题最重要的,也是最难的点在于如何去除重复三元组

去重

  • i遍历的去重:如果nums[i] == nums[i+1],则continue循环。
解释:如[-1,-1,2,3],当i=0时,我们有这些三元组[-1,-1,2],[-1,-1,3],[-1,2,3]
当i=1时,我们有三元组[-1,2,3]
所以如果nums[i] = nums[i-1],那么这次找到的三元组一定是包含在上一次找到的三元组里的。
  • [left,right]去重:如果nums[left+1] = nums[left]或者nums[right] = nums[right-1]就要continue循环。
解释:因为想找nums[left]+nums[right] = -nums[i]的[left,right]二元组,
只要有一个元素相同,那么就会出现重复三元组,
因为另一个元素必定也相同才满足和为-nums[i]。
比如[-5,2,2,3,3],nums[0]=5,left=1,right=4。
left要移到2,right要移到3才能做到完全去重。

五、18题(四数之和)

题目描述
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
和第15题思路相同

题解1

mine

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        Arrays.sort(nums);
        List<List<Integer>> ans = new ArrayList<List<Integer>>();
        //[i,j,left,right] 循环遍历(i,j) //双指针[left,right]
        int left,right;
        for(int i=0;i<=nums.length-4;i++){
            //是i-1>=0,因为i从0开始
            if(i-1>=0&&nums[i]==nums[i-1]){
                continue;
            }
            //注意是j=i+1,不是j=1
            for(int j=i+1;j<=nums.length-3;j++){
                if(j-1>i&&nums[j]==nums[j-1]){
                    continue;
                }
                left = j+1;
                right = nums.length-1;
                while(left<right){
                    if(left-1>j&&nums[left]==nums[left-1]){
                        left++;
                        continue;
                    }
                    if(right+1<nums.length&&nums[right]==nums[right+1]){
                        right--;
                        continue;
                    }
                    if((long)nums[i]+nums[j]+nums[left]+nums[right]==target){
                        List<Integer> list = new ArrayList<Integer>();
                        list.add(nums[i]);
                        list.add(nums[j]);
                        list.add(nums[left]);
                        list.add(nums[right]);
                        ans.add(list);
                        left++;
                        right--;
                    }else if((long)nums[i]+nums[j]+nums[left]+nums[right]<target){
                        left++;
                    }else{
                        right--;
                    }

                }
            }
        }
        return ans;
    }
}

易错点

  • 去重:跳出条件,比前不比后。即当前index=j,是比较nums[j]==nums[j-1],而不是nums[j]==nums[j+1]。不然[2,2,2,2,2]target=8时会返回空。
  • 去重前提条件:比较nums[j]==nums[j-1],前提条件是要判断j-1是否合法。
  • 数字和范围:4个int型数字相加,很有可能大于Integer.MAX_VALUE或小于Integer.MIN_VALUE,所以要转为long类型,如下所示:(long)nums[i]+nums[j]+nums[left]+nums[right]==target

题解2

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;
    }
}

注释:算法是一样的,只是细节上的区别。
result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));

总结

202的关键点是想到这个操作一定有循环。1并不难。454简单。15有难度。掌握第15题后18也就掌握了。

  • 34
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值