1002. Find Common Characters

本文解析LeetCode 1002题“查找常用字符”,分享了一种使用26个字母的int数组来统计字符频率的方法,并对比了博主与作者的不同实现方式。此外,还介绍了另一种利用HashMap解决此问题的思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Given an array A of strings made only from lowercase letters, return a list of all characters that show up in all strings within the list (including duplicates). For example, if a character occurs 3 times in all strings but not 4 times, you need to include that character three times in the final answer.
You may return the answer in any order.

这道题LeetCode显示的是easy,但是我想了大概一个小时,最终放弃了,看了别人的答案。。。
看了一个博主,谈到了申请了一个代表26个字母的int数组,我也想到了,但是无法想象如果有100个这样的数组,如何比较呢???
结果博主每一个字符串数组中同样申请一个临时的26个字母的int数组,然后遍历给定的字符串数组,在对这个字符串的每一个字母遍历后,拿新申请的数组与开始申请的数组对应元素(相同下标)进行比较,如果哪个数字小就用谁!!! 这样子每次只用比较两个int数组,没有我想象的最多100个数组同时比较的情况,这个脑回路我怎么没有额。。。

**

附上博主的链接:LeetCode126周赛-1002. 查找常用字符-Java实现

**

搞懂了博主的思路后,我自己打了一遍代码,后来发现了自己的两个薄弱点:
1、Foreach语句
题目要对数组赋初值,应该只能用for循环语句,如下:

for(int i = 0;i < 26;i++){ 
			chars[i] = Integer.MAX_VALUE; 
		} 

但是,我为了让代码简洁,竟然自作聪明的用了Foreach语句,如下:

for(int i:chars) {
			i = Integer.MAX_VALUE;
		}

其实,看了Foreach语句的介绍,我就明白我错了

for(int x : f)     //定义一个int类型的变量x,继而将每一个f的元素赋值给x

2、算法简洁性

记录content字符数组中的每个字母的出现次数,通过charsTemp数组记录(该数组0-25依次代表a-z)

我的思路:

for(int j = 0; j < content.length ; j++) {            //取出每个字母
		for(int k = 97 ; k<= 122 ; k++) {     //依次与a-z做对比,如果相同,contentChars数组中对应字母位置+1
			  char c = (char)k;
			  if(c == content[j]) {
				  charsTemp[k-97]++;
				  break;
			  }
	     }
}

博主的思路:

for(char c : content){    
	charsTemp[c-97]++; 
} 

在讨论中看到了另外一种解法:HashMap

public List<String> commonChars(String[] A) {
        // List to store results
        List<String> result = new ArrayList<String>();
        
      
		//跟踪每个单词的字母数的计数器
        Map<Character, Integer>  counter = null;
        
    
		//hashmap保持所有单词中每个字母的最小计数
        Map<Character, Integer> min = new HashMap<Character, Integer>();
        
		//因为字母都是小写的,所以循环使用最小哈希映射26次(a-z),字符作为键,Integer.MAX_VALUE作为值。
        for(int i = 0; i < 26; i++) {
            char ch = (char) ('a' + i);
            min.put(ch, Integer.MAX_VALUE);
        }
        
        // 遍历每个单词
        for(String word : A) {
            
            // 为每个单词创建HashMap
            counter = new HashMap<Character, Integer>();
            
            // 查找当前字符串中的字符数
            for(char ch : word.toCharArray()) {
                counter.put(ch, counter.getOrDefault(ch, 0) + 1);  
			//getOrDefault()方法:当HashMap集合中有这个key时,就使用这个key值,如果没有就使用默认值defaultValue
            }
            
            /* 
			遍历所有26个字符。这个循环背后的思想是,对于[a-z]中的每个字符,
			我们将找到全局最小值(该字符的)和本地字符计数的最小值,
			并相应地更新最小值。所以如果一个字符不存在,新的最小值将变为零
			*/
            for(Map.Entry<Character, Integer> entry : min.entrySet()) {
                char alphabet = entry.getKey();
                int currentValue = counter.containsKey(alphabet) ? counter.get(alphabet) : 0;
                int currentMinValue = entry.getValue();
                 min.put(alphabet, Math.min(currentMinValue, currentValue));
            }
        }
        
        // 遍历Min HashMap并将字符添加到结果中
        // depending on the occurrence
        for(Map.Entry<Character, Integer> entry : min.entrySet()) {
            int count = entry.getValue();
            while(count-- > 0) {
                result.add("" + entry.getKey());
            }
        }
        return result;
    }
package com.luxsan.service; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.hankcs.hanlp.HanLP; import com.hankcs.hanlp.seg.common.Term; import com.luxsan.common.core.utils.MessageUtils; import com.luxsan.domain.ValidationResult; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import net.sourceforge.tess4j.Tesseract; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.text.PDFTextStripper; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; @RequiredArgsConstructor @Service public class PdfJsonCompareService { private final Map<String, Pattern> patternCache = new ConcurrentHashMap<>(); private final ObjectMapper objectMapper = new ObjectMapper(); private Tesseract tesseract; @PostConstruct public void initOcrEngine() { tesseract = new Tesseract(); try { //语言包路径和支持语言 tesseract.setDatapath("D:\\maven_use\\lingxi-lhc\\lingxi-ai-extend\\lingxi-ai-comparison\\src\\main\\resources\\tessdata"); tesseract.setLanguage("eng+chi_sim"); tesseract.setPageSegMode(1); // 自动页面分割 tesseract.setOcrEngineMode(1); // LSTM引擎 } catch (Exception e) { throw new RuntimeException("OCR引擎初始化失败: " + e.getMessage(), e); } } /** * 支持PDF和图片 */ public String extractContent(MultipartFile file) { String contentType = file.getContentType(); String fileName = file.getOriginalFilename().toLowerCase(); if (contentType == null) { return "不支持的文件类型: " + contentType; } if (fileName.endsWith(".pdf")) { return readPdfText(file); } return extractImageText(file); } /** * 读取PDF文本内容 * * @param file * @return */ public String readPdfText(MultipartFile file) { try (PDDocument doc = PDDocument.load(file.getInputStream())) { PDFTextStripper stripper = new PDFTextStripper(); String rawText = stripper.getText(doc); return rawText.replaceAll("\\s+", " ").trim(); //统一空白符 } catch (Exception e) { return MessageUtils.message("file.red.pdf.error"); } } /** * OCR识别图片内容 */ private String extractImageText(MultipartFile file) { try { // 创建临时文件 Path tempFile = Files.createTempFile("ocr_", getFileExtension(file.getOriginalFilename())); Files.copy(file.getInputStream(), tempFile, StandardCopyOption.REPLACE_EXISTING); // 执行OCR识别 File imageFile = tempFile.toFile(); String result = tesseract.doOCR(imageFile) .replaceAll("\\s+", " ").trim(); System.out.println("读取的内容"+result); // 清理临时文件 Files.deleteIfExists(tempFile); return result; } catch (Exception e) { return "OCR处理失败: " + e.getMessage(); } } private String getFileExtension(String filename) { if (filename == null) return ".tmp"; int dotIndex = filename.lastIndexOf('.'); return (dotIndex == -1) ? ".tmp" : filename.substring(dotIndex); } public JsonNode parseJson(String jsonContent) throws Exception { return this.objectMapper.readTree(jsonContent); } public List<ValidationResult> compareContent(String pdfText, JsonNode jsonConfig) { List<ValidationResult> results = new ArrayList<>(); //处理JSO 结构(支持单个对象或数组) JsonNode dataNode; if (jsonConfig.isArray() && jsonConfig.size() > 0) { dataNode = jsonConfig.get(0); } else if (jsonConfig.isObject()) { dataNode = jsonConfig; } else { results.add(new ValidationResult("ERROR", "JSON格式错误", "期望一个对象或包含对象的数组", "实际格式不匹配", false)); return results; } // 定义地址字段列表 Set<String> addressFields = new HashSet<>(Arrays.asList("SHIPTOCITY", "SHIPTOSTATE", "SHIPTOZIP", "SOLDTOCITY", "SOLDTOSTATE", "SOLDTOZIP")); //字段直接匹配 checkNonAddressFields(pdfText, dataNode, results, addressFields); //连续匹配 checkAddressFields(pdfText, dataNode, results, addressFields); return results; } /** * 检查 JSON 中非地址字段是否严格存在于 PDF 文本中 */ private void checkNonAddressFields(String pdfText, JsonNode jsonConfig, List<ValidationResult> results, Set<String> addressFields) { Iterator<Map.Entry<String, JsonNode>> fields = jsonConfig.fields(); while (fields.hasNext()) { Map.Entry<String, JsonNode> entry = fields.next(); String fieldName = entry.getKey(); JsonNode valueNode = entry.getValue(); if (valueNode.isValueNode() && !addressFields.contains(fieldName)) { String expectedValue = valueNode.asText().trim(); if (expectedValue.isEmpty()) continue; // 使用正则表达式进行严格匹配 String regex = "\\b" + Pattern.quote(expectedValue) + "\\b"; Pattern pattern = getCachedPattern(regex); Matcher matcher = pattern.matcher(pdfText); boolean found = matcher.find(); results.add(new ValidationResult( "FIELD", fieldName, expectedValue, found ? "Found" : "Not Found", found )); } } } /** * 获取或创建缓存中的 Pattern 对象 */ private Pattern getCachedPattern(String regex) { return patternCache.computeIfAbsent(regex, Pattern::compile); } /** * 检查 JSON 中地址字段是否严格存在于 PDF 文本中 */ private void checkAddressFields(String pdfText, JsonNode jsonConfig, List<ValidationResult> results, Set<String> addressFields) { List<Term> terms = HanLP.segment(pdfText); List<String> addressParts = new ArrayList<>(); for (Term term : terms) { String word = term.word; if (word.matches("\\d{5,7}")) { addressParts.add(word); } else if (term.nature.toString().startsWith("ns")) { addressParts.add(word); } } // 遍历 JSON 配置中的地址字段 Iterator<Map.Entry<String, JsonNode>> fields = jsonConfig.fields(); while (fields.hasNext()) { Map.Entry<String, JsonNode> entry = fields.next(); String fieldName = entry.getKey(); JsonNode valueNode = entry.getValue(); if (valueNode.isValueNode() && addressFields.contains(fieldName)) { String expectedValue = valueNode.asText().trim(); if (expectedValue.isEmpty()) continue; boolean found = false; for (String part : addressParts) { if (part.equals(expectedValue)) { found = true; break; } } results.add(new ValidationResult( "FIELD", fieldName, expectedValue, found ? "Found" : "Not Found", found )); } } } } 如果是这样的话 我的pdf是没问题的 然后就是我的图片 可以读取内容但是没有比较和校验
最新发布
07-08
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值