java算法day6

  • 快乐数
  • 四数相加
  • 三数之和
  • 赎金信
  • 四数之和

快乐数

关于一些题目的疑问:每位数求平方和到底会不会导致数越来越大。答案是不会的。一开始我个人还总感觉会。
直接看到这个图。就懂了。
在这里插入图片描述
所以说,当你的数越来越大。求平方和反而会变小。所以这也是有可能出现循环的原因。

所以快乐数,给你一个数不断的求其平方和,是有可能出现下图中的环的可能。

思路:
按题目的思路进行计算,但是最关键的还是题目中的四个字:无限循环。
按题中的意思得到算法思路:
解法1:
输入的数字为n。
从n中,取出每一位求平方和,得到一个新数值。现在就相当于n转变成了这个新数值。然后继续对这个数字进行相同的处理。重复这个过程,直到n=1或者判断出有循环出现的数。也就是这个图:
请添加图片描述

如何快速判断出有循环出现的数?用哈希表。在每次得到新数值的时候,就把这个数字加入哈希表。之后每次得到新数字的时候进行判断即可,判断用containsKey()就行。

写的技巧:
1.之前每得到一个新数字我老不想改变原输入数字,以后在这种循环迭代,有值变换迁移的,就可以直接用这个输入的数字进行迭代,这个n我纠结了好久。

2.以题目的条件作为终止循环条件也是一种技巧。

class Solution {
    public boolean isHappy(int n) {
        HashMap<Integer,Boolean> hmap = new HashMap<>(); //创建哈希表
        while(n!=1 && !hmap.containsKey(n)){//终止条件:要么n最终变为了1,要么n变为已经出现过的数字,此时判断有无限循环。
            hmap.put(n,true);//新元素加入哈希表,记录出现过
            int sum = 0; //计算每一位的求和
            while(n!=0){
                int digit = n%10; //取最后一位
                sum += digit * digit; //求平方然后加起来
                n/=10; //把最后一位舍掉
            }
            n = sum; //迭代新的n的值
        }
		//跳出循环的时候n要么是重复数字,要么是1.所以是1就是true,重复数字就是false。
        return n == 1;
    }
}

解法2:

看到环,难免就会想到一个做法:判断环的方法:快慢指针。

这个解法容易想,但是难就难在这不是链表。所以想完成个快慢指针,一定要模拟出链表的感觉。

这个解法我推荐看代码来理解:本质上就是跑步套圈的原理。用的类似快慢指针的思想。慢的一次走一步,快的一次走两步。慢的起点从n开始。快的起点从n的下一个状态。就是getNext(n)开始。
通过二者不断的在循环中调用getNext(n)一直往后面走。最终要么当fast为1,要么slow为1,要么slow的值等于fast的值然后停下来。

class Solution {

     public int getNext(int n) { //这个函数平方和。也就是n的下一个状态。
        int totalSum = 0;
        while (n > 0) { //当n不为0时,表示每位数还没有取完。
            int d = n % 10;  //取最后一位
            n = n / 10;      //除10舍掉最后一位
            totalSum += d * d;  //计算最后一位的平方和进行求和
        }
        return totalSum;  //最后返回所有位的平方和
    }

    public boolean isHappy(int n) { 
        int slowRunner = n;  //slow起始在n
        int fastRunner = getNext(n);  //fast起始在n的下一个状态。
        while (fastRunner != 1 && slowRunner!=1 && slowRunner != fastRunner) { //开始往后面走,由于fast一次走两步也可能错过1。所以fast和slow都要进行判断。
            slowRunner = getNext(slowRunner); //slow每次走一步
            fastRunner = getNext(getNext(fastRunner));  //fast每次走两步,这里就是往后计算两次状态,然后赋值,就完成了走两步
        }
        return fastRunner == 1; //一旦上面的终止条件之一出现,那么就进行判断,等于1那就返回true,不等于1那就说明碰到环了,返回false。
    }
}



四数相加

自己做的版本:
思路:四个数组转两个数组,优化到O(n^2)。并且和两数之和的思想一样,用Hash进行查找优化。先二重for循环并用hash表统计前两个数组中的所有相加的结果。后面两个数组,也是二重for循环计算后两个数组中相加的所有结果,在这个过程中,取相加结果的负数,去哈希表中找有没有满足相加为0的组合。有就把对应的hash的value值累加了。

并且题目的意思是统计个数,所以哈希表:
HashMap<Integer,Integer> key为前两个数相加后的值,value为该值出现的次数。后面查找哈希表的时候,只需把value加上就完成了次数的累加。

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        HashMap<Integer,Integer> hmap1 = new HashMap<>();
        //哈希表,用于存储前两个数组构建的相加后的数值统计。

        int count = 0;  //存储结果。
        
        for(int i = 0;i<nums1.length;i++){
            for(int j = 0;j<nums2.length;j++){
                int sum1 = nums1[i]+nums2[j]; //目标就是构建相加后的数的哈希表。
                if(hmap1.containsKey(sum1)){//先判断这个元素本身哈希表有没有
                    int value = hmap1.get(sum1);//有就把原来的值取出来。然后再put一个value+1进去。
                    hmap1.put(sum1,value+1);
                }else{
                    hmap1.put(sum1,1);//如果本身这个哈希表就没有,那就put一个1进去。
                }
              
            }
        }
		//后两个数组元素进行相加组合,然后求哈希表中有没有与之相加后为0的元素
        for(int i = 0;i<nums3.length;i++){
            for(int j = 0;j<nums4.length;j++){
                int sum = nums3[i]+nums4[j];
                int target = -sum; //找相加后为0的目标元素
                if(hmap1.containsKey(target)){ //去哈希表里面找有没有
                    count += hmap1.get(target); //有就取出来,然后统计次数累加。没有就无事发生。
                }
            }
        }

        return count; //返回结果。
    }
}

这个题相当于练习哈希表的使用了。

1.注意HashMap没有默认初始化,所以如果有一个key,它本身不在哈希表中,直接访问就报空指针异常。这个时候想处理就只能提前用containsKey判断元素是否在表中,然后再进行后续的处理。比如把这个值put进去。

2.HashMap想改变value的值。只能够通过put操作,put操作是覆盖。要想实现value值+1,那就先通过get操作得到value,然后把value+1再put进去。


三数之和

这个题,难点在判重。所以重点学会判重。
解法:双指针。
具体看这个图,就懂怎么做了。

请添加图片描述
首先明确一个点,这些操作都是利用排序好的基础上完成的。所以第一步应该是排序。

i在最外层循环,然后每轮循环中先固定i,然后后面进行双指针操作。left指向i+1,right指向nums.length-1。三数之和是三个数字相加等于0,这个时候就可以模拟出一个结果。当求和结果过大的时候,right应该往右移动一格,这样结果就能减小。当求和结果过小的时候,left往右移动一格,这样结果就能够增大。这样就不断能向求和结果等于0靠近。

剪枝操作:由于是排过序了,所以当nums[i]>0的时候,后面就不存在结果集了。后面的都比nums[i]大。

去重操作:最怕的就是数组越界问题。
所以对于i的判重:必须要i>0,即在第一格的时候不用判重,所以就可以放心的写,nums[i]==nums[i-1]的时候,就可以continue进入下一轮。

left的判重,left和left+1位置的元素比较,这样比较简单,主要也是担心数组越界的问题,所以left判重就要多一个条件,left<right。

right的判重同理,right与right-1进行比较。这样比较简单,加一个left<right就可以解决数组越界的问题。

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-2;i++){//右边不足两个元素的时候就可以停了
            if(nums[i] > 0){  //剪枝,一旦第一个元素都大于0了,那后面的也必要进行了,因为是有序数组
                return result;
            }

            if(i > 0 && nums[i]==nums[i-1]){
                continue; //剪枝。第一个位置就没必要剪枝,从第二个位置起才需要进行剪枝。而且这样和前面比也不用担心空指针异常。
            }
			//双指针逻辑
            int left = i+1; //left从i+1开始 
            int right = nums.length-1; //right从数组末尾开始。
            while(left < right){ //left和right不碰到一起即可。注意千万不能取等,取等可能会导致多出一组结果,比如-4,2,2。如果left和right同时指向同一个元素,而且值为2,那就有可能把这个错误的结果加入结果集。
                int sum = nums[i]+nums[left]+nums[right]; //求和
                if(sum<0){
                    left++; //值小了,left右移,使得值大一点
                }else if(sum >0){
                    right--; //值大了,right往左,使得值小一点。
                }else{
                //接入结果集
                    result.add(Arrays.asList(nums[i],nums[left],nums[right]));
                    while(left<right && nums[right]==nums[right-1]){ //left判重
                        right--;
                    }
                    while(left<right && nums[left]==nums[left+1]){ //right判重
                        left++;
                    }
                    //由于left是和右边一个比较,所以上面不断移动的后果是left移动到最右边的一个重复数字,但这个数字仍然是收集过的,所以就应该left再往右边移动一格。这个++完全不用担心越界,因为一旦越界根本下一个循环都进不去。下面的right同理。
                    right--;
                    left++;
                }
            }
        }
        return result;
    }
}

赎金信

还是比较简单,数组模拟哈希直接解决战斗。

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        int[] num = new int[26]; //哈希,用于统计magazine里每个字符的个数
        for(char x : magazine.toCharArray()){//增强for遍历字符串。但是这里用toCharArray()将字符串转为字符数组。
            num[x-'a']++; //统计字符
        }

        for(char x : ransomNote.toCharArray()){//增强for遍历同理
            num[x-'a']--; //用哈希表去做减法。
            if(num[x-'a']<0){ //一旦有某个字符的值小于0,说明字符不够用了
                return false; //所以返回false。
            }
        }

        return true; //如果能走到这,那就返回true。
    }
}

四数之和

这个题就是在三数之和的基础上边一下

在外面再给他套一层k,但是本题也有本题的特点,由于题意改成四数相加之和为target。所以本题的剪枝和就会产生变化。我觉得这是本题最大的变化。

这里重点就看看剪枝
为什么剪枝会是nums[k]>target && target>=0。首先target有正有负。光写nums[k]>target就忘记考虑一些情况:看这个例子,-4 -2 0 0 ,target =-6。如果k指向-4大于-6这里直接return了,不就少了这组结果。
所以这是忽略了负数+负数会变得更小 。所以之前这种情况,是在满足target>0的情况才能这样剪枝。

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> result =  new ArrayList<>(); //存结果集
        Arrays.sort(nums); //排序

        for(int k = 0;k<nums.length-3;k++){ //这里到后面不足三个就可以停了
            
            if(nums[k]>target && target>=0){ //剪枝。上面解释过了
                return result;
            }

            if(k>0 && nums[k]==nums[k-1]){ //这里是k的判重,和三数之和的i的判重逻辑逻辑一样
                continue; //这里千万别去在k++了,因为k是最外层,默认有++
            }

            for(int i=k+1;i<nums.length-2;i++){ //这里到后面不足两个就停止。
               
                if(i>k+1 && nums[i]==nums[i-1]){//i的判重。这里能想到i>k+1很重要,这个和上面k>0性质是一样,不能在这一轮i的起点,才能进行判重。
                    continue;
                }
				//下面的逻辑纯粹就是三数之和
                int left = i+1; //left取在i+1
                int right = nums.length-1; //right取在数组末尾
                while(left<right){ //这里就是个双指针的流程
                    long sum = (long)nums[k]+nums[i]+nums[left]+nums[right]; //这里必须要弄个long。因为四个int相加有可能发生上移除。所以要用long来转,这里只要第一个转long即可,后面的计算从左到右都会进行自动类型转换。
                    if(sum >target){ //值大了。那就right往左走,调小一点
                        right--;
                    }else if(sum<target){ //值小了,left往右边走,调大一点
                        left++;
                    }else{//相等的逻辑
						                        result.add(Arrays.asList(nums[k],nums[i],nums[left],nums[right])); //先结果收集
                        while(left<right && nums[left]==nums[left+1]){ //left判重
                            left++;
                        }
                        while(left<right && nums[right]==nums[right-1]){ //right判重
                            right--;
                        }
                        left++;//由于left是和右边一个比较,所以上面不断移动的后果是left移动到最右边的一个重复数字,但这个数字仍然是收集过的,所以就应该left再往右边移动一格。这个++完全不用担心越界,因为一旦越界根本下一个循环都进不去。下面的right同理。
                        right--;
                    }
                }


            }



        }

        return result; //返回结果集。
    }
}
  • 8
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值