算法题 找不同

389. 找不同

问题描述

给定两个字符串 st,它们只包含小写字母。

字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。

请找出在 t 中被添加的字母。

示例

输入: s = "abcd", t = "abcde"
输出: "e"
解释: 'e' 是那个被添加的字母

输入: s = "", t = "y"
输出: "y"
解释: s为空,t中'y'是被添加的字母

输入: s = "a", t = "aa"
输出: "a"
解释: 添加了一个'a'

算法思路

方法一:字符频次统计

  1. 统计字符串 s 中每个字符的出现次数
  2. 遍历字符串 t,对每个字符在频次统计中减1
  3. 当某个字符的频次变为-1时,该字符就是被添加的字符

方法二:ASCII码求和

  1. 计算字符串 t 中所有字符的ASCII码之和
  2. 减去字符串 s 中所有字符的ASCII码之和
  3. 差值对应的字符就是被添加的字符

方法三:异或运算(最优解)

  1. 利用异或运算的性质:a ^ a = 0, a ^ 0 = a
  2. st 中所有字符进行异或运算
  3. 相同字符会相互抵消,最终结果就是被添加的字符

代码实现

方法一:字符频次统计

class Solution {
    /**
     * 使用字符频次统计找不同字符
     * 
     * @param s 原字符串
     * @param t 添加一个字符后的字符串
     * @return 被添加的字符
     * 时间复杂度: O(n)
     * 空间复杂度: O(1) - 固定26大小数组
     */
    public char findTheDifference(String s, String t) {
        // 创建频次数组
        int[] charCount = new int[26];
        
        // 统计s中每个字符的出现次数
        for (char c : s.toCharArray()) {
            charCount[c - 'a']++;
        }
        
        // 遍历t,减少对应字符的计数
        for (char c : t.toCharArray()) {
            charCount[c - 'a']--;
            // 如果计数变为-1,说明该字符在t中比在s中多出现一次
            if (charCount[c - 'a'] < 0) {
                return c;
            }
        }
        
        // 理论上不会执行到这里
        return ' ';
    }
}

方法二:ASCII码求和

class Solution {
    /**
     * 使用ASCII码求和找不同字符
     * 
     * @param s 原字符串
     * @param t 添加一个字符后的字符串
     * @return 被添加的字符
     * 时间复杂度: O(n)
     * 空间复杂度: O(1)
     */
    public char findTheDifference(String s, String t) {
        int sum = 0;
        
        // 计算t中所有字符的ASCII码之和
        for (char c : t.toCharArray()) {
            sum += c;
        }
        
        // 减去s中所有字符的ASCII码之和
        for (char c : s.toCharArray()) {
            sum -= c;
        }
        
        // 剩余的值就是被添加字符的ASCII码
        return (char) sum;
    }
}

方法三:异或运算(推荐)

class Solution {
    /**
     * 使用异或运算找不同字符(最优解)
     * 
     * @param s 原字符串
     * @param t 添加一个字符后的字符串
     * @return 被添加的字符
     * 时间复杂度: O(n)
     * 空间复杂度: O(1)
     */
    public char findTheDifference(String s, String t) {
        char result = 0;
        
        // 对s中所有字符进行异或
        for (char c : s.toCharArray()) {
            result ^= c;
        }
        
        // 对t中所有字符进行异或
        for (char c : t.toCharArray()) {
            result ^= c;
        }
        
        // 相同字符相互抵消,最终结果就是被添加的字符
        return result;
    }
}

方法四:单循环异或优化

class Solution {
    /**
     * 单循环异或优化版
     * 
     * @param s 原字符串
     * @param t 添加一个字符后的字符串
     * @return 被添加的字符
     * 时间复杂度: O(n)
     * 空间复杂度: O(1)
     */
    public char findTheDifference(String s, String t) {
        char result = 0;
        int i = 0;
        
        // 同时遍历s和t(s比t少一个字符)
        for (; i < s.length(); i++) {
            result ^= s.charAt(i);
            result ^= t.charAt(i);
        }
        
        // 处理t中最后一个字符
        result ^= t.charAt(i);
        
        return result;
    }
}

算法分析

  • 时间复杂度:所有方法都是 O(N),N 为字符串 s 的长度
  • 空间复杂度
    • 方法一:O(1),固定26大小数组
    • 方法二、三、四:O(1),只使用常数额外空间
  • 方法对比
    • 方法一:直观易懂,但需要额外数组空间
    • 方法二:简洁,但可能有整数溢出风险(实际不会,因为字符ASCII码范围小)
    • 方法三:最优解,利用位运算性质,高效且优雅
    • 方法四:方法三的优化版,减少了一次循环

算法过程

输入:s = "abcd", t = "abcde"

执行过程(方法一:频次统计)

  1. 统计s频次:a:1, b:1, c:1, d:1
  2. 遍历t:
    • ‘a’: 频次[0]-- → 0
    • ‘b’: 频次[1]-- → 0
    • ‘c’: 频次[2]-- → 0
    • ‘d’: 频次[3]-- → 0
    • ‘e’: 频次[4]-- → -1 < 0 → 返回’e’

执行过程(方法二:ASCII求和)

  1. t的ASCII和:97+98+99+100+101 = 495
  2. s的ASCII和:97+98+99+100 = 394
  3. 差值:495-394 = 101 → 对应字符’e’

执行过程(方法三:异或运算)

  1. 初始化result = 0
  2. 异或s:0979899100 = 979899^100
  3. 异或t:(979899100)979899100101
  4. 相同字符抵消:97^97=0, 98^98=0, 99^99=0, 100^100=0
  5. 最终:0^101 = 101 → 对应字符’e’

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 测试用例1:标准示例
    System.out.println("Test 1: " + solution.findTheDifference("abcd", "abcde")); 
    // 输出: e
    
    // 测试用例2:空字符串
    System.out.println("Test 2: " + solution.findTheDifference("", "y")); 
    // 输出: y
    
    // 测试用例3:添加相同字符
    System.out.println("Test 3: " + solution.findTheDifference("a", "aa")); 
    // 输出: a
    
    // 测试用例4:添加在开头
    System.out.println("Test 4: " + solution.findTheDifference("bcd", "abcd")); 
    // 输出: a
    
    // 测试用例5:添加在中间
    System.out.println("Test 5: " + solution.findTheDifference("acd", "abcd")); 
    // 输出: b
    
    // 测试用例6:长字符串
    System.out.println("Test 6: " + solution.findTheDifference("abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyza")); 
    // 输出: a
    
    // 测试用例7:单字符添加
    System.out.println("Test 7: " + solution.findTheDifference("xyz", "xyza")); 
    // 输出: a
    
    // 测试用例8:重复字符中添加
    System.out.println("Test 8: " + solution.findTheDifference("aabbcc", "aabbbcc")); 
    // 输出: b
    
    // 测试用例9:添加z
    System.out.println("Test 9: " + solution.findTheDifference("abcdef", "abcdefz")); 
    // 输出: z
    
    // 测试用例10:添加x
    System.out.println("Test 10: " + solution.findTheDifference("abcdefghijklmnopqrstuvwyz", "abcdefghijklmnopqrstuvwxyz")); 
    // 输出: x
}

关键点

  1. 问题本质

    • 找出两个字符串中不同的那个字符
    • 由于只有一个字符不同,可以利用各种数学和位运算性质
  2. 异或运算优势

    • 相同字符异或结果为0
    • 0与任何字符异或结果为该字符本身
    • 运算顺序不影响结果(异或满足交换律和结合律)
  3. 边界处理

    • s为空时,t中唯一字符就是答案
    • 添加的字符可以是任意位置,包括开头、中间、结尾
  4. 字符集假设

    • 题目说明"只包含小写字母",所以方法一的26大小数组足够
    • 其他方法对任意字符集都适用

常见问题

  1. 为什么异或运算是最优解?

    • 空间复杂度最低(O(1))
    • 时间复杂度最优(O(n))
    • 代码简洁优雅
    • 无溢出风险
    • 利用了问题的数学特性
  2. ASCII求和法有溢出风险吗?

    • 理论上有,但实际不会发生。因为小写字母ASCII码97-122。
  3. 如果添加了多个字符怎么办?

    • 本题明确只添加一个字符
    • 如果要找多个不同字符,需要使用频次统计或其他方法
  4. 异或运算的数学原理是什么?

    • 异或运算满足:a ^ a = 0(自反性),a ^ 0 = a(恒等性),a ^ b = b ^ a(交换律),(a ^ b) ^ c = a ^ (b ^ c)(结合律)
    • 利用这些性质,相同字符会相互抵消,只剩下不同的字符
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值