代码随想录算法训练营第六天 | 242.有效的字母异位词 349. 两个数组的交集 202. 快乐数 1. 两数之和

今天的题涉及到了集合 分别用数组 set 和map 对集合很生疏 要时刻记住一旦出现判断一个元素是否出现过 就要想到用哈希

集合的一些基本知识

这里推荐看以下博客 讲的很全

List:List就是数组啊 链表那些 主要记住有序 可以重复

Set:集合无序 不允许重复 通过计算添加元素的hashcode来判断存储位置

Set的常用方法包括add()用来添加元素、remove()用来删除元素、contains()用来判断是否包含某个元素、isEmpty()用来判断集合是否为空、size()用来获取集合中元素的数量等等。

Map:存储键值对 key-value

添加元素:使用put()方法向Map中添加元素,语法:map.put(key, value),其中key为键对象,value为值对象。

获取元素:使用get()方法通过键来获取对应的值,语法:map.get(key),其中key为键对象。
获取所有的key用keySet()方法 会返回一个set
删除元素:使用remove()方法通过键来删除对应的键值对,语法:map.remove(key),其中key为键对象。

判断是否包含键或值:使用containsKey()containsValue()方法来判断Map中是否包含指定的键或值,语法:map.containsKey(key)map.containsValue(value),其中key为键对象,value为值对象。

这里要再强调一下 集合中的泛型里面只能添加对象或者包装类 也就是里面只能放引用数据类型(Integer等)

有效的字母异位词

题目链接

st 中每个字符出现的次数都相同,则称 st 互为字母异位词。

思路

由于一共我们只有26个字母,用数组来作为存储出现的次数是很快捷的方式,下标就按字母在在字母表的顺序

定义一个数组叫做record用来上记录字符串s里字符出现的次数。

需要把字符映射到数组也就是哈希表的索引下标上,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25。

再遍历 字符串s的时候,只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。 这样就将字符串s中字符出现的次数,统计出来了。

那看一下如何检查字符串t中是否出现了这些字符,同样在遍历字符串t的时候,对t中出现的字符映射哈希表索引上的数值再做-1的操作。

那么最后检查一下,record数组如果有的元素不为零0,说明字符串s和t一定是谁多了字符或者谁少了字符,return false。

最后如果record数组所有元素都为零0,说明字符串s和t是字母异位词,return true。

时间复杂度为O(n),空间上因为定义是的一个常量大小的辅助数组,所以空间复杂度为O(1)。

代码

class Solution {
    public boolean isAnagram(String s, String t) {
        int[] count = new int[26];
        for (int j = 0; j < s.length(); j++) {
            count[s.charAt(j) - 'a']++; //charAt方法是根据给的索引返回对应的char

        }
        for (int j = 0; j < t.length(); j++) {
            count[t.charAt(j) - 'a']--;
        }
        for (int cnt : count) {
            if (cnt != 0) { //只要不是0 是正数或者负数一定代表他们不是异位词
                return false;
            }
        }
        return true;
    }
}

由于本人写题是忘了foreach迭代 这里再介绍一下:

for-each循环常用于遍历数组或集合, 格式是for(元素类型 变量名 : 数组或集合) 其中,元素类型代表数组或集合中元素的类型,变量名代表每次迭代获取到的当前元素。 例如Set(String) 那就是for(String str : Set) 能直接获取到里面的元素

总结

这道题不难想到用数组来存放次数 关于集合用到数组一般较为容易 常用于数组的下表非常好确定的情况

两个数组的交集

题目链接

本题使用set 可以想象数学中的集合 不允许重复

思路

首先找交集 是不是还是相当于找一个元素是不是在别的集合中出现 所以还是用哈希 其次为什么要用set 是因为题目要求没有重复元素 因此正好我们可以利用set的去重特性

代码

import java.util.HashSet;
import java.util.Set;

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        if (nums1 == null || nums1.length == 0 || nums2 == null || nums2.length == 0) {
            return new int[0];
        }
        Set<Integer> set1 = new HashSet<>();
        Set<Integer> resSet = new HashSet<>();
        //遍历数组1
        for (int i : nums1) {
            set1.add(i);
        }
        //遍历数组2的过程中判断哈希表中是否存在该元素
        for (int i : nums2) {
            if (set1.contains(i)) {
                resSet.add(i);
            }
        }
      
        //方法1:将结果集合转为数组

        return resSet.stream().mapToInt(x -> x).toArray();
        
        //方法2:另外申请一个数组存放setRes中的元素,最后返回数组
        int[] arr = new int[resSet.size()];
        int j = 0;
        for(int i : resSet){
            arr[j++] = i;
        }
        
        return arr;
    }
}

关于set和数组

set和数组各自都有什么优缺点呢

直接使用set 不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算的。

HashSet

HashSet是基于哈希表的Set实现,其内部实际上是使用HashMap来存储元素的。

优点
  • 处理冲突HashSet(通过HashMap)有效地处理哈希冲突,使用链表或红黑树来存储相同哈希值的元素。
  • 常数时间操作:理想情况下,HashSet提供常数时间的添加、删除、包含等操作性能,这使得它非常适合需要快速查找的场景。
  • 动态扩容HashSet可以根据元素的增加自动扩容,无需手动管理底层数据结构的大小。
缺点
  • 空间效率:由于使用了额外的数据结构(如链表或红黑树),以及为了减少哈希冲突而采取的扩容机制,HashSet可能会使用比纯数组更多的内存。
  • 性能下降:在极端情况下(如大量元素具有相同的哈希值时),HashSet的性能可能会接近O(n)
基于数组的哈希表

在一些简单的情况下,我们可能会直接使用数组来实现哈希表,通过计算元素哈希值的模运算确定其在数组中的位置。

优点
  • 内存效率:直接使用数组作为底层数据结构,可能比HashSet更节省内存,特别是在元素数量较少时。
  • 简单直接:对于简单的用途,基于数组的哈希表实现起来较为直接和简单,容易理解。
缺点
  • 处理冲突:基于数组的哈希表需要自己实现冲突处理机制,如开放寻址或链表法,这可能增加实现的复杂性。
  • 固定大小:数组的大小是固定的,这意味着哈希表的容量是有限的,一旦达到容量限制,性能会急剧下降,且需要手动或通过复杂的逻辑来实现扩容。
  • 性能变化:当哈希表填满时,性能会下降,查找、插入和删除操作的时间复杂度可能增加到O(n),特别是在没有有效冲突解决机制的情况下。

总之,HashSet提供了一个高度优化且易于使用的哈希表实现,适用于大多数需要使用集合的场景,特别是当元素的添加、删除和查找操作频繁时。而基于数组的哈希表可能在某些特定场景下更有优势,尤其是在元素数量较少、内存使用受限或者可以容忍简单冲突处理机制时。在选择使用哪种实现时,应考虑具体的应用需求和限制。

快乐数

题目链接

思路

这道题最开始做的时候 就不知道应该什么时候结束循环 后来才明白原来题目说的无限循环指的是sum会重复出现 这样才会无限循环 那一看到这个出现二字 很容易想到又要用哈希表了 这里我们选择用HashSet避免重复

代码

class Solution {
    public boolean isHappy(int n) {
        Set<Integer> record = new HashSet<>();
      //n等于1 证明是快乐数
        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;
    }
}

两数之和

题目链接

思路

本质上还是找元素是否在集合中出现 遍历数组 那么target减去该元素的值就是我们要找的数 如果集合中有 那证明可以找到

因为题目要求返回下标 用map是最合适的 返回key即可

代码

class Solution {
        public int[] twoSum(int[] nums, int target) {
            int[] result = new int[2];
            Map<Integer, Integer> map = new HashMap<>();
            for (int i = 0; i < nums.length; i++) {
                if (map.containsKey(target - nums[i])) {
                    result[0] = i;
                    result[1] = map.get(target - nums[i]);
                }
                map.put(nums[i], i);
            }
            return result;
        }
    }

最开始我想的是一趟遍历把元素先都加到map里 然后再进行第二次遍历 这样的时间耗时很显然没有上述代码好 上述代码是边添加边判断 而且同样能把所有元素的组合给判断一次 每一个map中的元素 都和他之前的进行匹配

过程一

过程二

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值