Leetcode 567 242 以及Integer类中equals与“==“的区别

大家好,我是编程小白一名,今天无意中刷到Leetcode567题,谈一下做题中发现的问题,希望能给读者提供一丢丢帮助或启发。(全文阅读约20分钟)


567. 字符串的排列

给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false 。换句话说,s1 的排列之一是 s2 的 子串。

题目给的例子有两个:
示例1:输入:s1 = "ab" s2 = "eidbaooo" 输出:true
示例2:输入:s1= "ab" s2 = "eidboaoo" 输出:false

看到这个题目时,我想起此前有刷到过类似的题目,题目如下:

242. 有效的字母异位词

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。

题目给的例子有两个:
示例1:输入: s = "anagram", t = "nagaram" 输出: true
示例2:输入: s = "rat", t = "car" 输出: false

提示:

  • 1 <= s.length, t.length <= 5 * 104
  • s 和 t 仅包含小写字母

可以理解题目242实际上是题目567的一种情况,即为两个字符串长度相同的情况,那我们可以先从题目242入手。

看到题目242的第一感觉,是遍历并使用哈希表进行,可以建立两个HashMap,将两个字符串的字符存储到HashMap中最后进行HashMap的比较。思路很简单,直接上代码

class Solution {
    public boolean isAnagram(String s, String t) {
        int len1 = s.length(),len2 = t.length();
        if (len1 != len2) {
            return false;
        }
        Map<Character,Integer> maps =new HashMap<>();
        Map<Character,Integer> mapt =new HashMap<>();
        for(int i=0;i<s.length();i++){
            maps.put(s.charAt(i),maps.getOrDefault(s.charAt(i),0)+1);
            mapt.put(t.charAt(i),mapt.getOrDefault(t.charAt(i),0)+1);
        }
        return maps.equals(mapt);
    }
}

 可惜效果很一般,感觉有点小题大做,无需使用HashMap,考虑改变思路,由于题目提示字符串中只有小写字母,考虑使用数组,我想到了两种方式,第一种是直接将字符串转化为字符数组,后将两个字符数组排序,比较,代码如下:

class Solution {
    public boolean isAnagram(String s, String t) {
        if (s.length() != t.length()) {
            return false;
        }
        char[] str1 = s.toCharArray();
        char[] str2 = t.toCharArray();
        Arrays.sort(str1);
        Arrays.sort(str2);
        return Arrays.equals(str1, str2);
    }
}

 第二种是用一个int型数组存储各字符出现次数,最后比较两个数组的情况,代码如下:

class Solution {
    public boolean isAnagram(String s, String t) {
        int len1 = s.length(),len2 = t.length();
        if (len1 != len2) {
            return false;
        }
        int []arr1 = new int [26], arr2 = new int [26];
        for(int i = 0;i<len1;i++){
            arr1[s.charAt(i)-'a']++;
            arr2[t.charAt(i)-'a']++;
        }
        return Arrays.equals(arr1,arr2);
    }
}


题目242我们就解决到这里,题目并不是很复杂,代码也是清晰易懂,我们看到题目567。

像之前所说,题目567相比242可以说是拓展,题目567的解法我们可以从题目242入手,首先我们采取第二种数组的方式进行分析,我们可以设置一左一右两个伪指针,对s2进行从左到右的遍历(当然若在中途找到题目所需的答案则可以直接return true),同时保证遍历时的左右指针的下表差刚好是s1的长度,这样看来,遍历时的两个指针形成的空间就像是一个窗口,而我们的做法是不断移动窗口以找到我们需要的内容,这种算法也就是所谓的“滑动窗口”。话不多说,直接上代码:

class Solution {
    public boolean checkInclusion(String s1, String s2) {
        int len1 = s1.length(),len2 = s2.length();
        if (len1 > len2) {
            return false;
        }
        int []arr1 = new int [26], arr2 = new int [26];
        for(int i = 0;i<len1;i++){
            arr1[s1.charAt(i)-'a']++;
            arr2[s2.charAt(i)-'a']++;
        }
        int left = 0,right =len1-1;
        while(right<len2){
            if(Arrays.equals(arr1,arr2))return true;
            right++;
            if(right!=len2){
                arr2[s2.charAt(right)-'a']++;
            }
            arr2[s2.charAt(left)-'a']--;
            left++;
        }
        return false;
    }
}

当然,我们前面提到,这道题应该有多种做法,下面尝试一下使用HashMap的做法:

HashMap的做法与使用数组的做法差别不大,无非是将数组存储的信息转化为HashMap。我们可以设置两个HashMap,一个为s1对应的need,一个是s2对应的window,在使用HashMap的时候,当window的字符个数与need的字符个数相同,则可以直接return true。由于使用HashMap的原因,我们可以每次都去遍历两个表的情况来确定两表的字符个数,而这里我想要以一种新的思路来解题,用一个变量cnt来记录HashMap的情况,每次当遍历s2中的元素c时,window中c元素个数等于need中c元素个数的时候进行cnt++,当cnt等于need.size()时,可以说明已经满足了need中的元素已全部在window中,从而return true。

代码如下:

(C++)

class Solution {
public:
    bool checkInclusion(string t, string s) {
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;
    int left = 0, right = 0;
    int valid = 0;
    while (right < s.size()) {
        char c = s[right];
        right++;
        // 进行窗口内数据的一系列更新
        if (need.count(c)) {
            window[c]++;
            if (window[c] == need[c])
                valid++;
        }
        // 判断左侧窗口是否要收缩
        while (right - left >= t.size()) {
            // 在这里判断是否找到了合法的子串
            if (valid == need.size())
                return true;
            char d = s[left];
            left++;
            // 进行窗口内数据的一系列更新
            if (need.count(d)) {
                if (window[d] == need[d])
                    valid--;
                window[d]--;
            }
        }
    }
    // 未找到符合条件的子串
    return false;
    }
};

(Java)

class Solution {
    public boolean checkInclusion(String s1, String s2) {
        if (s2.length() < s1.length()) return false;
        Map<Character,Integer> need = new HashMap<>();
        Map<Character,Integer> window = new HashMap<>();
        for(int i=0;i<s1.length();i++){
            need.put(s1.charAt(i),need.getOrDefault(s1.charAt(i),0)+1);
            //s1字符个数记录到need中
        }

        int right = 0;
        int left = 0;
        int cnt = 0;
        while(right<s2.length()){
            //移动右指针,滑动窗口,使窗口变大
            char cr = s2.charAt(right++);
            if(need.containsKey(cr)){
                window.put(cr,window.getOrDefault(cr,0)+1);
                if(need.get(cr).equals(window.get(cr)))cnt++;
                //当窗口中移入的c字符个数与need字符个数一致时,cnt+1
            }
            while(right-left>=s1.length()){
                //当窗口大于所需要的长度,则进行缩小
                if(cnt==need.size())return true;//找到答案
                char ch = s2.charAt(left++);
                if(need.containsKey(ch)){
                    if(need.get(ch).equals(window.get(ch)))cnt--;
                    //当窗口中移出的c字符个数与need字符个数一致时,cnt-1
                    window.put(ch,window.getOrDefault(ch,0)-1);
                }
            }
        }
        return false;
    }
}

Java版本这里面有一个需要尤其注意的点,当在执行判断语句:

if(need.get(ch).equals(window.get(ch)))

我们要注意,这里不能使用

if(need.get(ch)==window.get(ch))

不然在执行第102个示例的时候会报错,因为第102个示例中,每个字母出现的次数非常多,我试着在idea中对s1进行了计数,结果如下:

 我很疑惑,于是上网查询资料,发现了问题,我们可以先看下面的例子:

 大家可以先想一下结果是什么,以下是结果:

 我感到很奇怪,于是查询了相关资料,总结了以下几点:

1.“==”是Java提供的比较运算符,用来比较两个变量指向的内存地址是否相同。当比较的类型是基础数据类型时,比较的是它们的值,而当比较的对象是对象类型时,则是比较它们的内存地址。而equals()是Object类提供的一个方法.Object中equals()方法的默认实现就是返回两个对象==的比较结果.这点我们可以从equals()方法的源码中看到。但是equals()方法是可以被重写的,所以我们在具体使用的时候需要重点关注equals()方法有没有被重写。

2.Object类的equals()方法:

Integer类的equals()方法:

intValue()方法:

可以看见 实际上Integer.equals()方法也就是两个类的值。

3. 为什么 c==d 输出结果为false呢?

    Integer a =12;
    Integer b =12;
    Integer c = 128;
    Integer d = 128;

 前面我们提到,“==”运算符比较的类型为对象时,比较的是地址,那么是不是c与d的地址不一样呢。可是如果这样想,a与b的地址应该是一样的才会返回true鸭,我继续从源码分析,我从Integer类中发现了这样一段话:

 我的英语水平极差,简单翻译一下,大概是说,当不用new一个新的Integer实例时,通常使用这个方法来返回一个示例,而不是下面的 构造函数:Integer(int),这个方法通常用来返回范围为:-128~127的数值,当然也可以返回这个范围外的数值的实例。
也就是说,当我们进行 Integer a = 12; 的创建实例时,我们可以看到,是直接返回调用Integer(i)方法的Integer 实例的。
而当我们进行 Integer c = 128;的创建实例时,由于其值在这个范围外(-128~127)时,是会返回IntegerCache.cache[i + (-IntegerCache.low)];(这又是个啥?)查看源码如下:

private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

这实在是难以理解,我通过浏览大佬的说明,我终于明白原来当数值在(-128~127)范围内时,也就是说在-128到127之间的值会缓存到IntegerCache.cache中,所以在Integer x=在-128到127之间时,返回的是同一个对象,所以出现了上文的情况。从上面的源码可以看出,valueOf()在返回之前,会进行判断,判断当前 i的值是否在 -128到127之间。如果存在,则直接返回引用,不再重新开辟内存空间。如果不存在,就创建一个新的对象。利用缓存,这样做既能提高程序执行效率,还能节约内存。

Integer a= 12; Integer b = 12; 因为 IntegerCache中已经存在此对象,直接返回引用,引用相等并且都指向缓存中的数据,所以这时候a == b返回true。

Integer c = 128; Integer d = 128;因为c,d的值大于127,不在[-128, 127]范围内,所以虚拟机会在堆中重新new一个 Integer对象来存放128,创建两个对象就会产生两个这样的空间。两个空间的地址不同,返回到栈中的引用的值也就不同,所以这时候i1 == i2返回false。

源码yyds,但我真的是个菜鸡!

好了,今天的分享就到这了,非常感谢你能看到这里!如果能为你提供哪怕一丁点帮助,都是我的荣幸!有任何不足和错误之处,欢迎指正。

 再次感谢大家!晚安!

参考文档:

Java中Integer.valueOf()解读_shboli的博客-CSDN博客_integer.valueof()作用
我写了套框架,把滑动窗口算法变成了默写题 

  • 8
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值