理论:
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