关于算法14,15解决一些问题

复制代码
public List<Integer> findAnagrams(String ss, String pp) {  
  List<Integer> ret = new ArrayList<>();  
  char[] s = ss.toCharArray();  
  char[] p = pp.toCharArray();  
  int[]hash1 = new int[26];  
  for (char ch : p) {  
      hash1[ch - 'a']++;  
  }  
  int[]hash2 = new int[26];  
  int m=p.length;  
  for(int left=0,right=0,count=0;right<s.length;right++){  
      char in=s[right];  
      if(++hash2[in-'a']<=hash1[in-'a'])count++;  
          //进窗口+维护count  
          if(right-left+1>m)//判断要不要滑出窗口  
          {  
              char out=s[left++];  
              if(hash2[out-'a']--<=hash1[out-'a'])count--;  
              //出窗口+维护窗口  
          }  
      if(count==m)ret.add(left);  
  }  
  return ret;  
}  
  
  
  
public List<Integer> findSubstring(String s, String[] words) {  
  //与上一题不同地方1. 哈希表,hashmap<string,int>2. left 和right 指针移动的步长是单词长度3. 滑动窗口的次数不同  
    //需要练习容器的使用!超级不会(用ai问,练中学,将算法转为代码)  
    List<Integer> ret = new ArrayList<>();  
    //hash1用来保存字典中所有单词的频次  
    Map<String, Integer> hash1 = new HashMap<>();  
    for (String str : words) {hash1.put(str, hash1.getOrDefault(str, 0) + 1);}  
    int len=words[0].length();//单词长度  
    int m=words.length;//单词个数  
    for(int i=0;i<len;i++){//窗口执行次数  
        Map<String, Integer> hash2 = new HashMap<>();//保存窗口内所有单词的频次  
        for(int left=0,right=0,count=0;right+len<=s.length();right+=len){  
            //进窗口+维护count  
            String in=s.substring(right,right+len);  
            //这个方法我不会啊,存在则返回in+1,不存在就0+1,问ai  
            hash2.put(in, hash2.getOrDefault(in, 0) + 1);  
            if(hash2.get(in)<=hash1.getOrDefault(in, 0) + 1){//如果是get方法会报错用存在返回值  
                count++;  
            }  
            if(right-left+1>m*len){  
                String out=s.substring(left,left+len);  
                if(hash2.get(out)<=hash1.getOrDefault(out,0) + 1){  
                    count--;  
                }  
                hash2.put(out, hash2.getOrDefault(out,0) + 1);  
                left+=len;  
            }  
            if(count==m)ret.add(left);  
        }  
    }  
    return ret;  
}

关于这两天的题,lsq提出几个很笨的问题

1hash的数组充当hash表怎么做到的

2map和set的区别还有treemap treeset hashmap hashset 这六个有什么区别

3在new对象的时候 List<Integer> ret = new ArrayList<>();和 List<Integer> ret = new ArrayList<Interger>();区别

4了解一下容器的内置方法


一、int[] hash是怎么"充当哈希表"的?

以你的 **findAnagrams**​ 为例:

复制代码
int[] hash1 = new int[26];
for (char ch : p) {
    hash1[ch - 'a']++;
}

1️⃣ 本质思想

这是一个 "用数组实现的哈希表"

字符 映射方式 数组下标
'a' 'a' - 'a' 0
'b' 'b' - 'a' 1
... ... ...
'z' 'z' - 'a' 25

👉 字符 → 数字(索引) → 直接访问

2️⃣ 为什么算哈希表?

哈希表的本质是:

key → value 的快速映射

这里:

  • key:字符 'c'
  • hash 函数:ch - 'a'
  • value:出现次数

O(1) 查找

不需要 HashMap的开销

3️⃣ 什么时候能用这种方式?

✅ 字符集固定(如小写字母、ASCII)

❌ 不适合字符串、对象、动态 key

对比一下👇

方式 示例
数组哈希 int[] count = new int[26]
HashMap Map<Character, Integer>
HashSet Set<Character>

所以hash数组是存储出现的次数,是key


一、先给结论 ✅

**int[] hash既不是 HashMap,也不是 HashSet。**​

它更像是"手写版 HashMap(只有 value 的简化版)"

但它 绝对不是 HashSet


二、为什么它不是 HashSet

1️⃣ HashSet是什么?

复制代码
Set<Character> set = new HashSet<>();
set.add('a');
set.contains('a');

HashSet的特点是:

特性 HashSet
存什么 单个元素
是否允许重复
是否有"次数"概念
是否关心频率

👉 HashSet 只关心:有没有


2️⃣ 你的 int[] hash在干嘛?

复制代码
int[] hash1 = new int[26];
hash1['a' - 'a']++; // a 出现了几次

它在干的事是:

✅ 统计 出现次数

✅ 一个字符 → 一个数字(频率)

关心数量,不只是"有没有"

👉 这和 HashSet的语义 完全不一样


三、那它是不是"没有 key"?

你说得很关键的一句话 👇

"因为它没有 key 值"

其实它有 key,只是你看不见而已 😄

真正的对应关系

字符 key value
'a' 0 hash[0]
'b' 1 hash[1]
'c' 2 hash[2]

👉 **数组下标 = key(隐式)**​

👉 数组值 = value

所以它其实是:

"用数组实现的 HashMap<Character, Integer>"


四、三种东西放在一起对比(重点)

方式 key value 能否统计次数
int[] hash 下标(隐式) 出现次数
HashSet
HashMap<Char, Int> 字符 次数

举个直观例子(统计 "aab")

✅ int[]
复制代码
[2, 1, 0, 0, ...]
✅ HashMap
复制代码
{'a':2, 'b':1}
❌ HashSet
复制代码
{'a', 'b'}

👉 HashSet 连"2 次 a"都记不住


五、那为什么不用 HashSet 做异位词?

假设我们这样写:

复制代码
Set<Character> set = new HashSet<>();
set.add('a');
set.add('a');

结果只剩:

复制代码
{'a'}

❌ 根本没法判断是不是异位词

✅ 必须知道 每个字符出现几次

所以 必须用"计数结构"


六、什么时候用谁?(经验总结)

场景 用谁
字符 / 数字频率统计 int[]/ HashMap
判断是否出现过 HashSet
键值对映射 HashMap
去重 HashSet
字符集固定(a-z) int[26](最快)

七、一句话终极记忆 🧠

HashSet 管"有没有",HashMap 管"是多少",数组是手写版 HashMap。

二、Mapvs Set(先搞清楚"是谁")

1️⃣ 最根本区别

类型 存什么 是否键值对
Map key → value ✅ 是
Set 单个元素 ❌ 否

一句话记住:

Map 管配对,Set 管去重


三、HashMap / TreeMap / HashSet / TreeSet六者区别

我用一张对比表讲清楚 👇

容器 是否键值对 底层结构 是否有序 是否允许 null 特点
HashMap 哈希表 ❌ 无序 ✅ key 可为 null 最快
TreeMap 红黑树 ✅ 按 key 排序 自动排序
LinkedHashMap 哈希 + 链表 ✅ 插入顺序 记录顺序
HashSet HashMap 去重
TreeSet TreeMap ✅ 排序 去重 + 排序
LinkedHashSet 哈希 + 链表 ✅ 插入顺序 去重 + 顺序

记忆口诀 🧠

HashMap 快,TreeMap 有序,HashSet 去重,TreeSet 又去重又有序

此处lsq插播一个问题,map是不是hashmap缩写了


一、结论先行 ✅

Map不是 HashMap的缩写

Map是接口,HashMap是实现类

它们是 父子关系(设计上的),不是简称关系。


二、用一句话解释

名字 是什么
Map **接口(规定能干嘛)**​
HashMap **具体实现(实际干活的类)**​

就像:

现实世界 Java 世界
"车" Map
"丰田 / 宝马" HashMap / TreeMap

三、看你的代码就明白了

复制代码
Map<String, Integer> hash1 = new HashMap<>();

拆解来看:

复制代码
Map<K, V>        ← 接口(你"用谁")
      ↓
HashMap<>()     ← 实现类(你"怎么干")

✅ 好处:

以后你想换实现,一行代码都不用改

复制代码
Map<String, Integer> map = new TreeMap<>(); // 照样能用

四、为什么不直接写 HashMap

你也可以写:

复制代码
HashMap<String, Integer> map = new HashMap<>();

❌ 但不推荐,原因是:

写法 问题
HashMap声明 被"锁死"在 HashMap
Map声明 可以随时换实现

面向接口编程(面试必考点)


五、同样的套路,你早就见过 👇

1️⃣ List / ArrayList

复制代码
List<Integer> list = new ArrayList<>();
接口 实现
List ArrayList

2️⃣ Set / HashSet

复制代码
Set<Integer> set = new HashSet<>();
接口 实现
Set HashSet

六、对照表(非常重要)✅

接口(Interface) 常见实现类
Map HashMap, TreeMap, LinkedHashMap
List ArrayList, LinkedList
Set HashSet, TreeSet, LinkedHashSet

七、记忆口诀 🧠

**接口是"能干嘛"**​

**实现类是"怎么干"**​

Map ≠ HashMap,就像 车 ≠ 丰田


八、结合你现在的水平,我建议你记住这一条就够了 ✅

写代码时:

复制代码
Map<K, V> map = new HashMap<>();
List<T> list = new ArrayList<>();
Set<T> set = new HashSet<>();

等你哪天想换 TreeMap,直接改右边就行。


Java接口继承图

Pasted image 20260429114024.png

四、List<Integer> ret = new ArrayList<>();

vs

List<Integer> ret = new ArrayList<Integer>();

完全等价,没有任何性能或行为差异

区别只是 Java 版本写法

写法 Java 版本
new ArrayList<Integer>() Java 5--6
new ArrayList<>() Java 7+(菱形语法)

编译器会自动推断 <Integer>

📌 推荐写法

复制代码
List<Integer> ret = new ArrayList<>();

五、容器常用内置方法(重点!结合你的代码)

我按 你代码里出现的 + 高频必会​ 来讲。


✅ 1️⃣ List常用方法(ArrayList)

方法 作用
add(e) 添加元素
get(i) 取第 i 个
size() 元素个数
clear() 清空
contains(e) 是否包含

你的代码:

复制代码
List<Integer> ret = new ArrayList<>();
ret.add(left);

✅ 2️⃣ Map常用方法(HashMap)

方法 作用
put(k, v) 插入
get(k) 取值
getOrDefault(k, def) 有就取,没有给默认值
containsKey(k) 是否含 key
size() 键值对数
clear() 清空
你代码里的关键方法👇
复制代码
hash1.getOrDefault(str, 0) + 1

意思是:

如果有这个 key,就拿它的值;没有就从 0 开始


✅ 3️⃣ Set常用方法(HashSet)

方法 作用
add(e) 添加
contains(e) 是否存在
remove(e) 删除
size() 元素个数

✅ 4️⃣ 数组 / 字符串相关(你代码里混用的)

方法 作用
toCharArray() String → char[]
substring(l, r) 截取字符串
s.length() 字符串长度
arr.length 数组长度

⚠️ 注意:

复制代码
s.length();   // String
arr.length;   // 数组
list.size();  // List

六、你这两段代码里"真正在用的容器能力"

findAnagrams

  • ✅ 数组当哈希表
  • ✅ O(1) 统计频率
  • ✅ 滑动窗口 + 计数器

findSubstring

  • HashMap<String, Integer>
  • getOrDefault
  • ✅ 多起点滑动窗口

七、下一步我可以怎么帮你?

你现在的问题已经非常"对路"了 👍

接下来你可以选一个继续:

1️⃣ 我帮你 findSubstring改成正确版本,并逐行手推

2️⃣ 专门练 **Map / Set 高频 API(配小练习)**​

3️⃣ 总结一份 **Java 容器速查表(面试用)**​

4️⃣ 用 数组 vs HashMap​ 对比讲一道新题

你想先走哪一步?我陪你慢慢啃 😊

总结

1. 将算法思想变成代码的能力很重要(自己要去实现一遍)
2. 对于内置方法,要掌握

相关推荐
jimy11 小时前
C语言历史版本和gnu扩展版本
c语言·算法·gnu
探序基因1 小时前
单细胞转录组Seurat去批次-FastMNN算法及大细胞量评测
linux·算法
阿Y加油吧1 小时前
二刷 LeetCode:300. 最长递增子序列 & 152. 乘积最大子数组 复盘笔记
笔记·算法·leetcode
我是大聪明.1 小时前
大模型Tokenizer原理:BPE、WordPiece与子词编码的核心机制深度解析
人工智能·线性代数·算法·机器学习·矩阵
自我意识的多元宇宙1 小时前
数据结构----希尔排序
数据结构·算法·排序算法
惊鸿一博1 小时前
深度学习特征匹配算法 LoFTR、DKM、RoMa 介绍
人工智能·深度学习·算法
炽烈小老头1 小时前
【每天学习一点算法 2026/04/29】最长连续序列
学习·算法
一只数据集1 小时前
柏林道路路面图像数据集-971张沥青与鹅卵石路面图片-训练测试集划分-支持道路材质识别与自动驾驶视觉算法训练
算法·自动驾驶·材质
我不是懒洋洋1 小时前
【数据结构】二叉树OJ(单值二叉树、检查两棵树是否相同、对称二叉树、二叉树的前序遍历、另一颗树的子树)
c语言·数据结构·c++·经验分享·算法·leetcode·visual studio