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

今日任务

●  哈希表理论基础

●  242.有效的字母异位词

●  349. 两个数组的交集

●  202. 快乐数

●  1. 两数之和

详细布置

哈希表理论基础

建议:大家要了解哈希表的内部实现原理,哈希函数,哈希碰撞,以及常见哈希表的区别,数组,set 和map。

什么时候想到用哈希法,当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。 这句话很重要,大家在做哈希表题目都要思考这句话。

文章讲解:代码随想录

总结:

数据结构
  • Java 中 HashMap 主要由数组和链表(在 Java 8 后还有红黑树)组成。数组被称为哈希桶(hashTable),用于存储键值对的链表或红黑树的头节点引用
哈希函数
  • 通过哈希函数计算键的哈希值,然后对数组长度取模,得到在数组中的索引位置,以此来决定键值对应该存储在哪个哈希桶中
扩容机制
  • 当 HashMap 中的元素数量超过负载因子(默认 0.75)与当前容量的乘积时,会触发扩容操作。扩容时会创建一个新的、更大的数组,并将原有数据重新哈希到新数组中
键值存储与查找
  • 存储键值对时,将其放入对应的哈希桶中,如果发生哈希冲突(多个键映射到同一个索引位置),则在链表中依次添加节点(在 Java 8 后,如果链表长度超过一定阈值会转换为红黑树)
  • 查找键值对时,通过相同的哈希函数计算键的索引位置,然后在对应的链表或红黑树中进行查找
线程不安全
  • HashMap 在并发环境下可能会出现数据丢失、数据覆盖等问题,不适合多线程环境下直接使用,如果需要在多线程环境中使用,可以使用 ConcurrentHashMap 等线程安全的类。
        线程安全问题的本质原因:

                1>结构的并发修改问题

                        同时进行扩容操作

        HashMap 在进行扩容时,需要重新计算已有元素的哈希值,并将它们迁移到新的数组位置。如果多个线程同时进行扩容操作,可能会导致数据丢失或者迁移错误。

                        节点插入和链接的混乱

        在正常情况下,当向 HashMap 中插入一个新的键值对时,需要根据哈希值找到对应的链表位置,并将新节点插入到链表中。如果多个线程同时进行插入操作,可能会导致节点链接的顺序错误,或者出现循环链表的情况。

                 2>数据不一致问题

                        读写冲突

        一个线程正在读取 HashMap 的数据,而另一个线程正在对其进行写入操作(例如添加、删除或修改键值对)。在没有适当的同步机制的情况下,读取线程可能会获取到不一致的数据状态。

                        哈希表状态的不一致

        HashMap 的内部状态包括哈希桶数组的大小、每个链表的结构以及元素的分布等。在多线程环境下,这些状态可能会被多个线程同时修改,导致哈希表的状态变得不可预测和不一致。

                3>没有同步机制来保证原子性和可见性

                        原子性操作的缺失

        许多对 HashMap 的操作,如 put ()、remove () 等,实际上是由多个步骤组成的复合操作。在多线程环境下,这些操作需要保证原子性,即要么全部完成,要么完全不做,以避免中间状态被其他线程看到。

                        可见性问题

        由于每个线程都有自己的本地缓存(工作内存),线程对 HashMap 的修改可能不会立即被其他线程看到。这就可能导致其他线程读取到过期的数据。

242.有效的字母异位词

建议: 这道题目,大家可以感受到 数组 用来做哈希表 给我们带来的遍历之处。

题目链接:. - 力扣(LeetCode)

题目链接/文章讲解/视频讲解: 代码随想录

暴力解:
public boolean isAnagram(String s, String t) {
        boolean res = false;
        char[] charArr_s = s.toCharArray();
        //仅包含小写字符
        Map<Character,Integer> map = new HashMap<>(26);
        for(Character c:charArr_s){
            map.put(c,map.getOrDefault(c,0)+1);
        }
        char[] charArr_t = t.toCharArray();
        for(Character c:charArr_t){
            if(map.containsKey(c)){
                int cnt = map.get(c)-1;
                if(cnt <= 0){
                    map.remove(c);
                }else{
                    map.put(c,cnt);
                }
            }else{
                return res;
            }
        }
        return map.size() == 0;
    }

字典解

public boolean isAnagram(String s, String t) {
        //为什么可以用数组来做字典:
        // "s 和 t 仅包含小写字母" 和 "进阶: 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?" 已经提示了
        int[] arr = new int[26];
        for(int i=0;i<s.length();i++){
            arr[s.charAt(i)-'a']++;
        }
        for(int i=0;i<t.length();i++){
            arr[t.charAt(i)-'a']--;
        }
        for(int a:arr){
            if(a!=0){
                return false;
            }
        }
        return true;
    }

349. 两个数组的交集

建议:本题就开始考虑 什么时候用set 什么时候用数组,本题其实是使用set的好题,但是后来力扣改了题目描述和 测试用例,添加了 0 <= nums1[i], nums2[i] <= 1000 条件,所以使用数组也可以了,不过建议大家忽略这个条件。 尝试去使用set。

题目链接/文章讲解/视频讲解:代码随想录

暴力解:
public int[] intersection(int[] nums1, int[] nums2) {
        Set<Integer> set = new HashSet(nums1.length);
        for(Integer n : nums1){
            set.add(n);
        }
        Set<Integer> res = new HashSet();
        for(Integer n : nums2){
            if(set.contains(n)){
                res.add(n);
            }
        }
        //将结果集合转为数组
        return res.stream().mapToInt(x -> x).toArray();
    }
利用限制求解:
  • 1 <= nums1.length, nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 1000

这里限制 length 也限制了 值的大小, 都 <=1000 ,那么就提供了, 数组与值的转化土壤,即可以利用这个特性来快速标记数组某元素是否存在的状态

public int[] intersection(int[] nums1, int[] nums2) {
        int[] flag = new int[1001];
        for(int i = 0; i < nums1.length; i++)
            if(flag[nums1[i]] == 0)
                flag[nums1[i]] = 1;
        int[] ans = new int[1001];
        int idx = 0;
        for(int i = 0; i < nums2.length; i++)
            if(flag[nums2[i]] == 1){
                ans[idx++] = nums2[i];
                flag[nums2[i]] = 2;
            }
        return Arrays.copyOf(ans, idx);
    }

202. 快乐数

建议:这道题目也是set的应用,其实和上一题差不多,就是 套在快乐数一个壳子

题目链接:. - 力扣(LeetCode)

题目链接/文章讲解:代码随想录

利用字符串求解
public boolean isHappy(int n) {
        Set<Integer> set = new HashSet<>();
        //如果n之前已经出现了,说明会出现死循环,即不是快乐数了,这时就需要返回了
        while(n!=1 && !set.contains(n)){
            set.add(n);
            //重置n的值
            n = getSum(n);
        }
        return n == 1;
    }

    public int getSum(int n){
        int sum = 0;
        String str = String.valueOf(n);
        for(int i=0;i<str.length();i++){
            int tmp = Integer.valueOf(String.valueOf(str.charAt(i)));
            sum += tmp * tmp;
        }
        return sum;
    }
利用每次n/10 来累加各位置的和
public boolean isHappy(int n) {
        Set<Integer> set = new HashSet<>();
        //如果n之前已经出现了,说明会出现死循环,即不是快乐数了,这时就需要返回了
        while(n!=1 && !set.contains(n)){
            set.add(n);
            //重置n的值
            n = getSum(n);
        }
        return n == 1;
    }

    public int getSum(int n){
        int sum = 0;
        while(n>0){
            //每次取最后一位
            int tmp = n%10;
            sum += tmp * tmp;
            //重置n的值
            n = n/10;
        }
        return sum;
    }
核心:

        缺少对数字处理的常识

        从个位后高位依次取数

                获取最后一位数组,用n%10,取余即可,忽略最后一位数字,则用n/10 取整即可

                tmp = n%10; 每次取个位

                n = n /10; 数字忽略个位,缩小10倍,位数整体减1

        从高位后往个位依次取数

                用栈依次入栈之后再取即可

int sum = 0;
Stack<Integer> stack = new Stack();
while (n>0){
     stack.push(n%10);
     n = n/10;
}
//依次获取最高位数字
stack.pop();

                也可以直接用字符串的方式取(相对占用内存较多)

        数字的位运算
  1. 高效的乘除 2 运算: n乘以 2 可写为: n<<1 ; n除以 2 可写为: n>>1
  2. 快速判断奇偶性: 与数字 1 进行按位与(&)操作,如果结果为 0,则该数为偶数;如果结果为 1,则该数为奇数; 7 & 1 = 1 为奇数;8 & 1 = 0 为偶数

1. 两数之和

建议:本题虽然是 力扣第一题,但是还是挺难的,也是 代码随想录中 数组,set之后,使用map解决哈希问题的第一题。

建议大家先看视频讲解,然后尝试自己写代码,在看文章讲解,加深印象。

题目链接:. - 力扣(LeetCode)

题目链接/文章讲解/视频讲解:代码随想录

public int[] twoSum(int[] nums, int target) {
        int[] res = new int[2];
        //记录 值是key 即对应的索引的位置 为val
        Map<Integer,Integer> map = new HashMap<>();
        for(int i=0;i<nums.length;i++){
            if(map.containsKey(target-nums[i])){
                res[0] = i;
                res[1] = map.get(target-nums[i]);
                break;
            }
            map.put(nums[i],i);
        }
        return res;
    }
  • 为什么会想到用哈希表
    • 记录已表里的数组,避免重复遍历
  • 哈希表为什么用map
    • 因为返回的结果是索引位置,所以同步记录 值 及 索引
  • 本题map是用来存什么的
    • key: 数组元素值  val: 元素再数组的中偏移量(索引)
  • 11
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值