一文学懂哈希及如何手撕哈希算法(附详细例题)

一、概念理解

哈希(Hashing)是计算机科学中一个非常重要的概念,广泛应用于数据存储、数据检索、加密、校验等场景。哈希的核心思想是将输入的数据(通常是任意长度)通过哈希函数映射为固定长度的输出,这个输出值称为哈希值或哈希码。

1. 哈希的基本概念

哈希是将任意长度的数据(例如文本、数字、文件等)通过某种算法转换成一个固定长度的值。哈希值通常是一个数字或字符序列,其长度固定。

  • 哈希函数:哈希函数是将输入数据(称为“消息”或“键”)映射到一个固定大小的输出(哈希值)的数学函数。

    对于任何输入值 xx,哈希函数 H(x)H(x) 会输出一个固定长度的值。常见的哈希函数包括 MD5、SHA-1、SHA-256 等。

  • 哈希值(哈希码):是哈希函数的输出。它通常被用来在查找、验证、加密等过程中进行快速比较。

2. 哈希函数的性质

一个好的哈希函数需要满足以下几个基本性质:

  • 确定性:对于相同的输入,哈希函数的输出应该是相同的。

  • 快速计算:哈希函数应该能够快速地计算出哈希值,无论输入数据的大小如何。

  • 均匀分布:理想情况下,哈希函数应该尽量将不同的输入数据映射到哈希值的不同位置,从而避免哈希冲突。也就是说,哈希值的分布应该尽量均匀。

  • 抗碰撞性(Collision Resistance):不同的输入数据应该尽量产生不同的哈希值。如果不同输入数据产生相同的哈希值,称为哈希冲突(Collision)。好的哈希函数应该使得碰撞的概率非常低。

  • 不可逆性:哈希函数应当是单向的,即从哈希值无法反推出原始数据。这样的性质是加密学中不可或缺的一部分。

  • 抗预映像攻击:给定一个哈希值,应该很难找到一个输入数据,使得哈希值为该哈希值。

3. 常见的哈希算法

哈希算法在不同的领域有不同的应用。常见的哈希算法包括:

  • MD5(Message Digest Algorithm 5)

    • 输出:128位(16字节)的哈希值,通常以32个字符的十六进制表示。
    • 应用:早期广泛用于文件校验、密码存储等,但由于其存在较多碰撞问题,现在已不推荐用于安全敏感的应用。
  • SHA-1(Secure Hash Algorithm 1)

    • 输出:160位(20字节)的哈希值。
    • 应用:曾广泛用于数字签名、证书验证等,但现已被认为不再足够安全,已被逐渐淘汰。
  • SHA-256

    • 输出:256位(32字节)的哈希值。
    • 应用:SHA-2系列(包括SHA-256)目前被认为是相对安全的哈希算法,广泛应用于加密货币(如比特币)、数字签名、文件校验等领域。
  • CRC32

    • 输出:32位的哈希值,通常用于检查数据的完整性。
    • 应用:用于文件传输、存储系统中,检测数据是否被篡改。
  • BLAKE2

    • 输出:可定制长度的哈希值,性能优秀,安全性较高。
    • 应用:比SHA-2更加高效,广泛用于密码学、数字签名等。

4. 哈希的应用

哈希有广泛的应用,以下是一些主要的应用场景:

4.1 数据结构:哈希表

哈希表(Hash Table)是一种基于哈希算法的常用数据结构。它通过哈希函数将键映射到数组的索引位置,从而提供快速的查找、插入和删除操作。哈希表广泛应用于数据库索引、缓存系统等。

  • 哈希冲突(Collision):当不同的输入数据经过哈希函数映射到相同的位置时,就发生了哈希冲突。常用的解决哈希冲突的方法包括:
    • 链式法(Chaining):每个哈希桶使用链表存储多个元素。若哈希值相同,则将元素插入链表中。
    • 开放寻址法(Open Addressing):当发生哈希冲突时,寻找数组中的下一个空位来存储元素,直到找到为止。
4.2 数据完整性校验

哈希常用于验证数据的完整性。例如,在文件传输过程中,源和目标可以使用相同的哈希函数生成文件的哈希值,然后比较两个哈希值,若相同,则说明文件未被篡改。

  • 校验和(Checksum):数据传输协议中,常常会用哈希值来校验数据的完整性。比如,FTP、HTTP传输文件时,通常会附带文件的哈希值(如MD5或SHA-1)。
4.3 加密与数字签名

哈希算法在加密学中用于生成消息摘要,并结合公钥或私钥进行数字签名。数字签名可以验证消息的完整性和发送者身份。

  • 数字签名:发送方用私钥对消息的哈希值进行加密,接收方通过公钥解密并与接收到的消息计算哈希值比对,从而验证消息是否被篡改。
4.4 密码存储

在密码存储中,直接存储用户密码是不安全的,因此通常存储的是密码的哈希值。这样,即便数据库被攻击者获取,密码本身仍然保持安全。

  • 盐(Salt):为了防止通过预计算哈希值表(如彩虹表)进行攻击,通常会在密码的哈希计算中加入一个随机生成的值(盐)。每个用户的盐值不同,从而保证即使两个用户的密码相同,哈希值也不同。
4.5 区块链

在区块链中,哈希函数被用于确保数据不可篡改。每个区块的哈希值包含前一个区块的哈希值,形成一个链条,任何篡改都会导致整个链的哈希值变化,从而被网络中的节点察觉。

4.6 数据去重与唯一性检查

哈希函数可以用于去重和唯一性检查。例如,在大规模数据处理、数据库去重等场景中,可以通过计算数据的哈希值来判断数据是否重复。

5. 常见的哈希相关问题

  • 哈希冲突:尽管哈希函数可以将不同的输入映射到固定大小的输出,但由于输出的空间有限,不同的输入可能会生成相同的哈希值(碰撞)。这对于哈希表、加密等应用来说是一个严重问题。解决哈希冲突的方法包括使用更复杂的哈希算法、增加哈希表的大小或采用链式存储等。

  • 哈希碰撞攻击:攻击者可能通过巧妙的输入设计,使得两个不同的输入数据产生相同的哈希值,从而进行欺骗或篡改。这在使用不安全的哈希函数(如MD5、SHA-1)时较为容易发生。

6. 总结

哈希是一种非常重要的技术,广泛应用于计算机科学中的数据存储、加密、安全验证等领域。一个好的哈希函数需要具备快速计算、抗碰撞、不可逆等特性,而哈希算法的选择则依赖于具体的应用场景。在实际应用中,了解哈希的工作原理,掌握常见哈希算法的特性,能够帮助你在多个技术领域中更好地使用哈希。

二、手撕算法题

当然!以下是用 Java 编写的相应解答,展示了哈希在各种常见算法问题中的应用。

1. 查找和匹配

两数之和

给定一个整数数组和一个目标值,找出数组中两个数之和等于目标值的索引。我们可以使用哈希表来加速查找过程,避免使用暴力算法。

import java.util.HashMap;

public class TwoSum {
    public static int[] twoSum(int[] nums, int target) {
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            int complement = target - nums[i];
            if (map.containsKey(complement)) {
                return new int[] { map.get(complement), i };
            }
            map.put(nums[i], i);
        }
        throw new IllegalArgumentException("No solution");
    }

    public static void main(String[] args) {
        int[] nums = {2, 7, 11, 15};
        int target = 9;
        int[] result = twoSum(nums, target);
        System.out.println("Indices: " + result[0] + ", " + result[1]);
    }
}

2. 去重

去重数组中的元素

使用哈希集合(HashSet)来去除重复的元素,可以在 O(n) 时间内完成。

import java.util.HashSet;

public class RemoveDuplicates {
    public static int removeDuplicates(int[] nums) {
        HashSet<Integer> set = new HashSet<>();
        int index = 0;
        for (int num : nums) {
            if (set.add(num)) {
                nums[index++] = num;
            }
        }
        return index;
    }

    public static void main(String[] args) {
        int[] nums = {1, 1, 2, 2, 3, 4};
        int length = removeDuplicates(nums);
        for (int i = 0; i < length; i++) {
            System.out.print(nums[i] + " ");
        }
    }
}

3. 计数和频率统计

最常见的元素

我们可以使用 HashMap 来记录每个元素的频率,最后找出出现最多的元素。

import java.util.HashMap;
import java.util.Map;

public class MajorityElement {
    public static int majorityElement(int[] nums) {
        HashMap<Integer, Integer> countMap = new HashMap<>();
        for (int num : nums) {
            countMap.put(num, countMap.getOrDefault(num, 0) + 1);
        }

        int majorityElement = nums[0];
        int majorityCount = 0;
        for (Map.Entry<Integer, Integer> entry : countMap.entrySet()) {
            if (entry.getValue() > majorityCount) {
                majorityElement = entry.getKey();
                majorityCount = entry.getValue();
            }
        }
        return majorityElement;
    }

    public static void main(String[] args) {
        int[] nums = {3, 2, 3};
        System.out.println("Majority Element: " + majorityElement(nums));
    }
}

4. 字符串和子串问题

无重复字符的最长子串

使用滑动窗口技术,并用哈希表记录窗口内的字符,更新子串的最大长度。

import java.util.HashMap;

public class LongestSubstring {
    public static int lengthOfLongestSubstring(String s) {
        HashMap<Character, Integer> map = new HashMap<>();
        int left = 0, maxLength = 0;
        
        for (int right = 0; right < s.length(); right++) {
            if (map.containsKey(s.charAt(right))) {
                left = Math.max(left, map.get(s.charAt(right)) + 1);
            }
            map.put(s.charAt(right), right);
            maxLength = Math.max(maxLength, right - left + 1);
        }
        
        return maxLength;
    }

    public static void main(String[] args) {
        String s = "abcabcbb";
        System.out.println("Longest Substring Length: " + lengthOfLongestSubstring(s));
    }
}

5. 动态规划和哈希表的结合

爬楼梯问题

用动态规划和哈希表来优化存储并解决问题。

import java.util.HashMap;

public class ClimbStairs {
    public static int climbStairs(int n) {
        HashMap<Integer, Integer> dp = new HashMap<>();
        dp.put(0, 1);  // 1 way to stay at the ground
        dp.put(1, 1);  // 1 way to step 1 stair
        
        for (int i = 2; i <= n; i++) {
            dp.put(i, dp.get(i - 1) + dp.get(i - 2));
        }
        
        return dp.get(n);
    }

    public static void main(String[] args) {
        int n = 5;
        System.out.println("Ways to climb " + n + " stairs: " + climbStairs(n));
    }
}

6. 字谜问题

字谜判断

使用哈希表来统计字符频率,判断两个字符串是否为字谜。

import java.util.HashMap;

public class CheckInclusion {
    public static boolean checkInclusion(String s1, String s2) {
        if (s1.length() > s2.length()) return false;
        
        HashMap<Character, Integer> countMap = new HashMap<>();
        for (char c : s1.toCharArray()) {
            countMap.put(c, countMap.getOrDefault(c, 0) + 1);
        }

        int left = 0, right = 0, count = countMap.size();
        
        while (right < s2.length()) {
            char rightChar = s2.charAt(right);
            if (countMap.containsKey(rightChar)) {
                countMap.put(rightChar, countMap.get(rightChar) - 1);
                if (countMap.get(rightChar) == 0) count--;
            }
            
            while (count == 0) {
                if (right - left + 1 == s1.length()) return true;
                char leftChar = s2.charAt(left);
                if (countMap.containsKey(leftChar)) {
                    countMap.put(leftChar, countMap.get(leftChar) + 1);
                    if (countMap.get(leftChar) > 0) count++;
                }
                left++;
            }
            
            right++;
        }
        
        return false;
    }

    public static void main(String[] args) {
        String s1 = "ab";
        String s2 = "eidbaooo";
        System.out.println("Is Inclusion: " + checkInclusion(s1, s2));
    }
}

总结

在 Java 中,哈希表(如 HashMapHashSet)提供了高效的查找、去重、计数等操作。在解决算法题时,合理利用哈希能够显著提高效率,尤其是当你面对需要频繁查找或存储某些数据时。

  • 对于查找问题,可以利用哈希表进行快速查找。
  • 对于去重问题,使用哈希集合可以在 O(n) 时间内去除重复元素。
  • 对于频率统计,使用哈希表统计元素的出现次数并可以快速找出最多的元素。
  • 对于子串问题,哈希表常与滑动窗口技术结合使用,能够有效地优化字符串处理。
<think>嗯,用户想了解华为OD的技术面试题。首先,我需要确认华为OD是什么。华为OD通常指的是华为的“Outstanding Developer”计划,也就是他们的人才招聘项目,主要面向软件开发、算法、测试等岗位的候选人。 接下来,用户需要的是技术面试题的相关信息。作为面试准备,了解常见的题型和考察重点很重要。不过,华为的具体面试题可能受保密协议限制,不能公开详细题目。但可以整理些常见的考点和题型,帮助用户进行准备。 首先,技术面试通常分为几个环节:在线测评、技术笔试、技术面试(可能有几轮)、综合面试等。每个环节考察的内容不同。用户可能更关注技术面试部分,尤其是编程题和系统设计题。 在技术面试中,常见的题型包括算法与数据结构、操作系统、网络、数据库、设计模式、项目经验等。对于华为OD来说,可能更侧重算法和编程能力,因为OD项目注重开发能力。 算法题方面,LeetCode中等难度的题目比较常见,比如动态规划、树、图、字符串处理等。例如,最长回文子串、二叉树的中序遍历、两数之和等题目。此外,可能涉及些优化问题,比如时间复杂度或空间复杂度的优化。 系统设计题可能考察如何设计个分布式系统、缓存机制、数据库分库分表等。这部分需要候选人具备定的实际项目经验和系统架构能力。 此外,项目经验也是面试中的重要部分。面试官会询问候选人过去做过的项目,技术难点,解决方案,以及个人在项目中的贡献。需要准备好具体的案例,用STAR法则(情境、任务、行动、结果)来回答。 还有可能涉及计算机网络和操作系统的基础知识,比如TCP/IP协议栈、进程与线程的区别、内存管理、死锁等。数据库方面的事务隔离级别、索引原理等也是常考点。 另外,华为的企业文化也是面试中可能被考察的部分,比如对华为的了解、为什么选择华为等。虽然不属于技术问题,但提前准备会有帮助。 需要注意的是,华为OD的技术面试可能更注重实际编码能力和问题解决能力,因此在准备时要多练习写代码,尤其是在白板或纸上写出无语法错误的代码。同时,注意代码的边界条件处理和异常情况处理。 总结下来,用户可能需要的是面试准备的方向和建议,而不是具体的题目。因此,回答时应重点介绍常见的考察领域、题型结构,以及备考策略,帮助用户系统地准备华为OD的技术面试。</think>华为OD(Outstanding Developer)技术面试通常分为多个环节,重点考察算法能力、编程基础、系统设计及项目经验。以下为面试准备的核心要点及常见题型解析: --- ### **、面试流程概述** 1. **在线测评**:逻辑推理、性格测试、基础编码题。 2. **技术笔试**:3-4道编程题(中等难度,限时2小时)。 3. **技术面试(2-3轮)**:代码 + 技术深度问答。 4. **综合面试**:项目经验、职业规划、软技能评估。 --- ### **二、技术面试高频考点** #### **1. 算法与数据结构** - **必考题型**: - **动态规划**:背包问题、最长子序列(例如:最长递增子序列)。 - **树与图**:二叉树遍历(非递归实现)、最近公共祖先(LCA)、拓扑排序。 - **字符串处理**:KMP算法、回文子串、字符串匹配。 - **数组与链表**:合并区间、链表反转、环检测。 - **例题**: ```text 输入个整数数组,调整数组中数字的顺序,使所有奇数位于偶数前面,且保持奇数和偶数的相对顺序不变。 ``` **关键点**:双指针法或稳定排序思想。 #### **2. 操作系统** - **高频问题**: - 进程与线程的区别,协程的适用场景。 - 死锁的四个必要条件及避免策略。 - 虚拟内存与页面置换算法(如LRU实现)。 #### **3. 计算机网络** - **核心问题**: - TCP三次握/四次挥,TIME_WAIT状态的作用。 - HTTP与HTTPS的区别,SSL/TLS握流程。 - 浏览器输入URL后发生了什么? #### **4. 数据库** - **重点考察**: - 事务的ACID特性,隔离级别及脏读/幻读问题。 - B+树索引原理,联合索引的最左匹配原则。 - SQL优化:EXPLAIN命令解析执行计划。 --- ### **三、代码环节技巧** 1. **代码规范**: - 变量命名清晰,避免全局变量。 - 边界条件处理(如空输入、负数、溢出)。 2. **复杂度分析**: - 主动说明时间/空间复杂度,提出优化思路。 - 示例:两数之和问题,从$$O(n^2)$$暴力法优化到$$O(n)$$哈希表。 --- ### **四、系统设计题示例** **题目**:设计个分布式ID生成器(要求高可用、全局唯、趋势递增)。 **回答框架**: 1. **需求分析**:唯性、性能、容灾。 2. **方案选型**: - 雪花算法(Snowflake):$$ \text{ID} = \text{时间戳} + \text{机器ID} + \text{序列号} $$。 - Redis原子操作INCR。 3. **容错设计**:机器ID动态分配、时钟回拨处理。 --- ### **五、项目经验提问** - **STAR法则**: - **Situation**:项目背景与目标。 - **Task**:你的具体职责。 - **Action**:技术选型与难点突破。 - **Result**:量化成果(如性能提升30%)。 --- ### **六、备考建议** 1. **刷题平台**:LeetCode(精选华为题库)、牛客网。 2. **模拟面试**:使用白板练习写代码,录音复盘表达逻辑。 3. **技术深度**:深入理解1-2个技术栈(如Redis源码、JVM原理)。 --- 通过系统性准备算法、底层原理及项目表述,可显著提升通过率。建议每天至少完成3道中等难度算法题,并针对薄弱领域专项突破。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值