DS | 哈希表 Hash Table(240216)

   理论:                

1、概念

哈希函数:
        在记录的存储位置和它的关键字之间建立对应关系f,使得每个关键字key对应一个存储位置f(key),若查找集合中存在这个记录,则必定位于f(key)的位置上。
        我们将这种对应关系f称为散列函数(哈希函数)。

哈希表:
        根据关键码的值直接进行访问的数据结构

哈希冲突:
        由于哈希函数的输出空间是有限的,而输入空间是无限的,所以必然会出现多个不同的输入值映射到同一个哈希值的情况,这就是哈希冲突。

2、常见的哈希结构

        数组/集合(set)/映射(map)
        c#语言中:用Array、HashTable、Dictionary、HashSet实现哈希表结构

HashSet<T>:
定义:
        HashSet<T> 类表示一个集合,用于存储一组唯一的元素。
        在 C# 中,集合通常使用 HashSet<T> 类来表示,它实现了哈希集合的数据结构,用于存储一组唯一的元素。HashSet<T> 提供了高效的去重功能,可以快速地判断元素是否存在于集合中。它在 System.Collections.Generic 命名空间中。
用法:
        使用 HashSet<T> 的构造函数来创建一个新的集合实例;
        使用 Add() 方法向集合中添加元素;
        使用 Contains() 方法检查集合中是否包含某个元素;
        使用 Remove() 方法从集合中移除指定的元素;

using System;
using System.Collections.Generic;

class Program {
    static void Main(string[] args) {
        HashSet<int> hashSet = new HashSet<int>();

        // 向集合中添加元素
        hashSet.Add(1);
        hashSet.Add(2);
        hashSet.Add(3);

        // 检查集合中是否包含某个元素
        Console.WriteLine("HashSet contains 2: " + hashSet.Contains(2)); // 输出:True

        // 从集合中移除指定的元素
        hashSet.Remove(2);
        Console.WriteLine("HashSet contains 2 after removal: " + hashSet.Contains(2)); // 输出:False
    }
}

Dictionary<TKey, TValue>:
定义:
        Dictionary<TKey, TValue> 类表示一个映射(键值对集合),用于存储键值对。
        在 C# 中,映射通常使用 Dictionary<TKey, TValue> 类来表示,它实现了哈希表的数据结构,用于存储键值对。Dictionary<TKey, TValue> 提供了高效的查找、插入和删除操作,可以根据键快速地查找对应的值。它也在 System.Collections.Generic 命名空间中。
用法:
        使用 Dictionary<TKey, TValue> 的构造函数来创建一个新的字典实例。
        使用索引器或者 Add() 方法向字典中添加键值对。
        使用索引器或者 TryGetValue() 方法获取指定键的值。
        使用 ContainsKey() 方法检查字典中是否包含某个键。
        使用 Remove() 方法移除指定键的键值对。

using System;
using System.Collections.Generic;

class Program {
    static void Main(string[] args) {
        Dictionary<string, int> dict = new Dictionary<string, int>();

        // 向字典中添加键值对
        dict.Add("apple", 10);
        dict.Add("banana", 20);

        // 获取指定键的值
        int value;
        if (dict.TryGetValue("apple", out value)) {
            Console.WriteLine("Value of 'apple': " + value); // 输出:Value of 'apple': 10
        }

        // 移除指定键的键值对
        dict.Remove("apple");
        Console.WriteLine("Dictionary contains 'apple' after removal: " + dict.ContainsKey("apple")); // 输出:Dictionary contains 'apple' after removal: False
    }
}

   练习:                

242.有效的字母异位词
1、题目

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

2、解题
public class Solution {
    public bool IsAnagram(string s, string t) {
        if(s.Length != t.Length)
        {
            return false;
        }

        int[] count = new int[26];
        for(int i = 0; i < s.Length; i ++)
        {
            count[s[i] - 'a'] ++;
        }
        for(int j = 0; j < t.Length; j ++)
        {
            count[t[j] - 'a'] --;
        }

        for(int k = 0; k < count.Length; k ++)
        {
            if(count[k] != 0)
            {
                return false;
            }
        }

        return true;
    }
}
3、总结

字母异位词:26个字母的个数相等

349. 两个数组的交集
1、题目

给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。

示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]

2、解题
public class Solution {
    public int[] Intersection(int[] nums1, int[] nums2) {
        HashSet<int> set1 = new HashSet<int>(nums1);
        HashSet<int> inter = new HashSet<int>();

        foreach(int num in nums2)
        {
            if(set1.Contains(num))
            {
                inter.Add(num);
            }
        }

        return inter.ToArray();
    }
}
3、总结

202. 快乐数
1、题目

编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
        对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
        然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
        如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

2、解题
public class Solution {
     public int sum(int n)
        {
            int sum = 0;
            while(n > 0)
            {
                int m = n % 10;
                sum += m * m;
                n /= 10;
            }
            return sum;
        }
    public bool IsHappy(int n) {
        HashSet<int> record = new HashSet<int>();
        while(n != 1 && !record.Contains(n))
        {
            record.Add(n);
            n = sum(n); 
        }
        
        return n == 1;
    }
}
3、总结

求各位数之和的方法;
while时想清楚结束循环的条件
        本题为下一个数是1或已经出现过,跳出后再判断具体是哪一种情况即可返回

1. 两数之和
1、题目

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。

示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

2、解题
3、总结

 454.四数相加II
1、题目

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

示例 1:
输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

2、解题
public class Solution {
    public int FourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        Dictionary<int, int> sumab = new Dictionary<int, int>();
        int count = 0;

        foreach(int num1 in nums1)
        {
            foreach(int num2 in nums2)
            {
                int sumAB = num1 + num2;
                if(! sumab.ContainsKey(sumAB))
                {
                    sumab[sumAB] = 1;
                }
                else
                {
                    sumab[sumAB] ++;
                }
            }
        }

        foreach(int num3 in nums3)
        {
            foreach(int num4 in nums4)
            {
                int sumCD = num3 + num4;
                if(sumab.ContainsKey(-sumCD))
                {
                    count += sumab[-sumCD];
                }
            }
        }

        return count;
    }
}
3、总结

需要统计和出现的次数,使用映射结构;
不要用四个嵌套for循环;

 383. 赎金信
1、题目

给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。
如果可以,返回 true ;否则返回 false 。
magazine 中的每个字符只能在 ransomNote 中使用一次。

示例 1:
输入:ransomNote = "a", magazine = "b"
输出:false

2、解题
public class Solution {
    public bool CanConstruct(string ransomNote, string magazine) {
        if(ransomNote.Length > magazine.Length)
        {return false;}

        int[] count = new int[26];

        for(int i = 0; i < magazine.Length; i ++)
        {
            count[magazine[i] - 'a'] ++;
        }

        for(int j = 0; j < ransomNote.Length; j ++ )
        {
            count[ransomNote[j] - 'a'] --;
        }

        foreach(int k in count)
        {
            if(k < 0)
            {
                return false;
            }
        }

        return true;
    }
}
3、总结

字母异位词的变体

 15. 三数之和
1、题目

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。

请你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

2、解题

双指针解法:

public class Solution {
    public IList<IList<int>> ThreeSum(int[] nums) {
        IList<IList<int>> result = new List<IList<int>>();

        if(nums == null || nums.Length < 3)
        {
            return result;
        }

        Array.Sort(nums);

        for(int i = 0; i < nums.Length - 2; i ++)
        {
            if(i > 0 && nums[i] == nums[i - 1])
            {
                continue;
            }

            int target = -nums[i];
            int left = i + 1;
            int right = nums.Length - 1;

            while(left < right)
            {
                int sum = nums[left] + nums[right];

                //sum == target:加入解集,双指针移动
                if(sum == target)
                {
                    result.Add(new List<int>{nums[i], nums[left], nums[right]});
                    
                    //去重操作:只是跳过重复元素,尚未得到第一个不重复指针
                    while(left < right && nums[left] == nums[left + 1])
                    {
                        left ++;
                    }
                    while(left < right && nums[right] == nums[right - 1])
                    {
                        right --;
                    }
                    //得到第一个不重复指针
                    left ++;
                    right --;
                }
                //sum < target: 左指针移动
                else if(sum < target)
                {
                    left ++;
                }
                //sum > target: 右指针移动
                else
                {
                    right --;
                }             
            }
        }

        return result;
    }
}

哈希解法

3、总结

双指针解法:
排序数组:
        首先,对给定的数组进行排序。排序是为了方便后续双指针的移动操作,同时也能够更容易地避免重复的解。
外层遍历:
        在排序后的数组中,使用一个外层循环来遍历每个可能作为三元组的第一个元素。遍历范围为数组的前 n-2 个元素,这是因为三数之和需要至少三个元素,所以至少需要留出后面两个元素的位置。
双指针遍历:
        在外层循环中,设置两个指针,一个指向当前元素的下一个位置,另一个指向数组末尾。这两个指针分别代表了当前元素之后的数组部分,利用它们的移动来寻找满足三数之和为零的组合。
判断三数之和:
        对于当前固定的第一个元素,移动左右指针,计算当前三个元素的和。如果和等于零,则找到了一组解;如果和小于零,则左指针向右移动以增大和;如果和大于零,则右指针向左移动以减小和。
去重处理:
        在移动指针的过程中,需要注意去除重复的解。为了避免重复解,当找到一组解后,需要跳过左右指针指向的重复元素。        
        (外层遍历中跳过重复的第一个元素;出现解时左右指针一起移动时两边都要跳过重复指针;解不对时单独调整左或右指针时不需去重)
结束条件:
        外层循环遍历完所有可能的第一个元素后,算法结束,返回所有找到的解。

哈希解法:

关于IList:

 18. 四数之和
1、题目

给你一个由 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
你可以按 任意顺序 返回答案 。

示例 1:
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

提示:
1 <= nums.length <= 200
-109 <= nums[i] <= 109
-109 <= target <= 109

2、解题
3、总结
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值