class Solution {
public List<String> commonChars(String[] words) {
/**
分析:
一般来说使用哈希表用来表示 字符-数量关系,但是本题的字符是26个字母,完全可以使用数组来替代哈希表
这种类型的题目之前应该做过了,属于数组优化哈希的操作,使用int数组定义,数组值就是个数
题意表达的是求解每个字符串中的公共交集,按此交集输出对应字符串个数。所谓公共交集,不就是对应位置的最小值嘛~问题解决
*/
// 使用int数组替代哈希表 26代表字母个数
int [] res = new int[26];
// 初始化res,这里调用了字符串的api,toCharArray(),转换为了字符数组
for(char c:words[0].toCharArray()){
// 字符对应位置计数
res[ c - 'a']++;
}
// 遍历余下的字符串
for(int i = 1; i < words.length; i++){
// 定义一个临时数组
int[] temp = new int[26];
// 遍历字符串中的字符
for(char c : words[i].toCharArray()){
// 储存到临时数组中
temp[c - 'a']++;
}
// 对数组26个字母位置个数,取交集
for(int j = 0; j < 26; j++){
res[j] = Math.min(res[j],temp[j]);
}
}
List<String> ans = new ArrayList<>();
// 筛选出个数大于0的字符出来
for(int i = 0; i < 26; i++){
if(res[i] > 0){
// 输出字符,这里要主要有可能有重复的字符需要输出
for(int j = 0; j < res[i]; j++){
ans.add((char)(i+'a')+"");
}
}
}
return ans;
}
}
class Solution {
public String sortString(String s) {
/**
分析:
重构后的字符串所对应的字母个数是不变的,于原字符串中字母个数保持一致性。那么就可以使用一个数组用来保存对应位置的字符个数。
对于重构后字符串,是满足字符先上升再下降的特性,利用此特性进行构造
*/
// 定义一个26个字母的计数数组
int[] counts = new int[26];
for(char c:s.toCharArray()){
// 对应位置 储存个数
counts[ c - 'a']++;
}
// 使用StringBuild来动态拼接字符串
StringBuilder sb = new StringBuilder();
// 遍历原始数组
while(sb.length() < s.length()){
// 字符先上升(正序)
for(int i = 0; i < 26; i++){
if(counts[i] > 0){
// 储存进sb
sb.append((char)(i + 'a'));
// 对应个数-1
counts[i]--;
}
}
// 字符后下降(倒序)
for(int i = 25; i >= 0; i--){
if(counts[i] > 0){
// 储存进sb
sb.append((char)(i + 'a'));
// 对应个数-1
counts[i]--;
}
}
}
// 返回结果
return sb.toString();
}
}
class Solution {
public static boolean isValidSudoku(char[][] board) {
/**
分析:
本题看似是一个中等题,实际上是由三个简单题构成。重复问题的解法,首先要想到哈希表
1.遍历行,看是否有重复
2.遍历列,看是否有重复
3.遍历九宫格,看是否有重复
以上上三个操作都可以在一次遍历中完成,其中1和2只需要对调i,j即可。3呢需要进行推导,将i,j转换为第x个九宫格的第y个位置!
最后完成!!!重要的是思想(哈希表)和推导思路(i,j转换为x,y)。
*/
for (int i = 0; i < 9; i++) {
Set<Character> rowSet = new HashSet<>();
Set<Character> colSet = new HashSet<>();
Set<Character> squareSet = new HashSet<>();
for (int j = 0; j < 9; j++) {
if (board[i][j] != '.') {
if (rowSet.contains(board[i][j])) {
return false;
} else {
rowSet.add(board[i][j]);
}
}
if (board[j][i] != '.') {
if (colSet.contains(board[j][i])) {
return false;
} else {
colSet.add(board[j][i]);
}
}
// ⌊i/3⌋∗3+⌊j/3⌋
// 如何将ij转换为九宫格的下标
// x 代表的9*9中第几个九宫格(总共9个九宫格,下标编码0-8)
// y 代表的是在该九宫格下的下标(下标编码0-8),所以这里要用取余操作
// 上面两个的推导比较复杂,可以看下题解:https://leetcode-cn.com/problems/valid-sudoku/solution/36-jiu-an-zhao-cong-zuo-wang-you-cong-shang-wang-x/
int x = i / 3 * 3 + j / 3;
int y = i % 3 * 3 + j % 3;
if (board[x][y] != '.') {
if (squareSet.contains(board[x][y])) {
return false;
} else {
squareSet.add(board[x][y]);
}
}
}
}
return true;
}
}
class Solution {
public int firstMissingPositive(int[] nums) {
/***
分析:
题目限制了条件,时间复杂度为O(n),空间复杂度为O(1)。
若是空间复杂度没有限制,则可以使用哈希查找.
*/
// 使用哈希查找
Set<Integer> set = new HashSet<>();
for(int i:nums){
set.add(i);
}
for(int i = 1; i <= nums.length; i++){
if(!set.contains(i)){
return i;
}
}
return nums.length+1;
}
}
注意:其实哈希表本质就是数组+链表,jdk8中还引入了红黑树。那么我们要将空间复杂度降低为O(1),其实就是将数组哈希化(就是编写自己的哈希函数),这里的哈希函数也很简单,就是将数字为i的数隐射到索引为i-1上,每次都循环隐射(循环前,先判断两个位置是否已经相等了,隐射的方法采用交换位置法)
class Solution {
public int firstMissingPositive(int[] nums) {
/***
分析:
题目限制了条件,时间复杂度为O(n),空间复杂度为O(1)。
若是空间复杂度没有限制,则可以使用哈希查找.
*/
// 使用原地哈希算法
// 本质是交换座位,将数字为i的数隐射到索引为i-1的位置上(采用交换位置法)
int len = nums.length;
for(int i = 0; i < len; i++){
// 循环隐射,隐射前先判断两个下标位置的值是否相等
// nums[i] >= 1 && nums[i] <= len 是保证了数字在[1,len],在这个区间的数才应该放到正确位置上,其余的数,不用管!!!
while(nums[i] >= 1 && nums[i] <= len && nums[nums[i] - 1] != nums[i]){
// 交换下标
// 举个例子,索引为0的数为3,索引为2的数为-1,那么这个数3就要交换到索引为2的位置上
swap(nums,nums[i] - 1, i);
}
}
// 遍历
for(int i = 0; i < len; i++){
if( nums[i] != i + 1){
// 发现第一个隐射不一致的,直接返回
return i+1;
}
}
// 否则返回len + 1
return len + 1;
}
public void swap(int[] nums,int index1,int index2){
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
/**
分析:
解决本题其实并不难,关键就是读懂题意。
举个例子:
输入: ["eat", "tea", "tan", "ate", "nat", "bat"]
输出:
[
["bat"], --> abt
["eat", "tea", ate"], --> aet
["tan", "nat"] --> ant
]
这里的第二个列表的数据有 eat tea ate ,这三个数都是有字母 a e t 构成的,所以分为一组。
那么我就可以进行模式识别了,将一个字符串转换为char数组,然后按照字典排序,若数据经过字典排序后是一致的,储存到哈希表中。最后获取哈希表中所有的value值。
*/
// 定义哈希表,键为string类型,值为列表类型
Map<String,ArrayList<String>> map = new HashMap<>();
// 遍历strs
for(String str : strs){
// 将str转换为char数组
char[] ch = str.toCharArray();
// 按照字典排序
Arrays.sort(ch);
// 再将字典排序的ch转换为string
String s = String.valueOf(ch);
// 判断是否已经在map中
if(!map.containsKey(s)){
// 没有,就新建一个
map.put(s,new ArrayList<String>());
}
// 将str添加到map中键为s的值中
map.get(s).add(str);
}
// 最后返回map中的value值,记得外层再包裹一个list
return new ArrayList(map.values());
}
}
class Solution {
public int longestConsecutive(int[] nums) {
/**
分析:
按道理来说未排序,第一想法就是先排序,但是有时间复杂度的要求,很自然的想到使用空间换取时间。那就是使用哈希表。
先定义一个哈希表,然后将数字加入到哈希表去重,然后循环判断该数x是不是起始数(判断哈希表中是否存在x-1即可),是起始数,那么就要累加长度,最后不断更新最大长度。
*/
// 定义哈希表
Set<Integer> set = new HashSet<>();
// 去重
for(int num:nums){
set.add(num);
}
int res = 0;
// 遍历查找
for(int x : nums){
// 判断是否是起始数
if(!set.contains(x - 1)){
// 是起始数
// 那么定义一个变量记录当前数
int y = x;
// 遍历判断是否有 y + 1
while(set.contains(y + 1)){
// 数据累加
y++;
}
// 更新最新的res
res = Math.max(res,y - x + 1);
}
}
return res;
}
}
使用单哈希表,其实就是将题目条件转换成代码的边界条件
class Solution {
public boolean isIsomorphic(String s, String t) {
/**
分析:
构建一个哈希表,将s中的字符与t中的字符进行映射。
遍历哈希表,若取出的值与t相等,则就是同构字符串。
这里要注意边界条件:
不同字符不能映射到同一个字符上,相同字符只能映射到同一个字符上,字符可以映射到自己本身。
*/
Map<Character,Character> map = new HashMap<>();
for(int i = 0; i < s.length(); i++){
// 判断map的键是否包含字符 -- 没有包含s的字符,考虑添加
if(!map.containsKey(s.charAt(i))){
// map中的值包含了t的字符,直接return false
// 这句话实际上说明了:不同的字符隐射到了同一个字符上
if(map.containsValue(t.charAt(i))){
return false;
}
// 添加
map.put(s.charAt(i),t.charAt(i));
}else{
// 这句话说明了:同一个字符隐射得到的结果是不一样的字符
if(map.get(s.charAt(i)) != t.charAt(i)){
return false;
}
}
}
return true;
}
}
采用双射哈希表,思维跨度大,代码难度小
class Solution {
public boolean isIsomorphic(String s, String t) {
// 使用双射哈希表
// 题意中可以解读出,一个字符a隐射到另一个字符b,另一个字符b隐射到字符a上
// 以示例 2 为例,t 中的字符 a 和 r 虽然有唯一的映射 o,但对于 s 中的字符 o 来说其存在两个映射 {a,r},故不满足条件
Map<Character,Character> map1 = new HashMap<>();
Map<Character,Character> map2 = new HashMap<>();
for(int i = 0; i < s.length(); i++){
Character ch1 = s.charAt(i);
Character ch2 = t.charAt(i);
if(map1.containsKey(ch1) && map1.get(ch1) != ch2
|| map2.containsKey(ch2) && map2.get(ch2) != ch1){
return false;
}
map1.put(ch1,ch2);
map2.put(ch2,ch1);
}
return true;
}
}
使用排序法
class Solution {
public boolean isAnagram(String s, String t) {
/**
分析:
解法一:先排序,然后比较两个排序数组是否相等
解法二:使用一个哈希表记录键和个数,然后遍历两次,看值是否存在小于0
解法三:使用26位的数组来代替哈希表
*/
// 解法一:排序法
char[] s1 = s.toCharArray();
char[] t1 = t.toCharArray();
// 排序
Arrays.sort(s1);
Arrays.sort(t1);
// 比较
// 学到了一个新的api ,数组比较相等 Arrays.equals()
return Arrays.equals(s1,t1);
}
}
使用哈希表
class Solution {
public boolean isAnagram(String s, String t) {
// 解法二:哈希表
// 两个长度不相等,直接结束
if(s.length() != t.length()){
return false;
}
Map<Character,Integer> map = new HashMap<>();
for(char c : s.toCharArray()){
// 储存值
map.put(c,map.getOrDefault(c,0) + 1);
}
// 做减法
for(char c:t.toCharArray()){
if(!map.containsKey(c)){
return false;
}
// 要改变其值,只能用put方法
map.put(c,map.getOrDefault(c,0) - 1);
if(map.get(c) < 0){
return false;
}
}
return true;
}
}
使用数组
class Solution {
public boolean isAnagram(String s, String t) {
// 解法三:使用数组
if(s.length() != t.length()){
return false;
}
int[] res = new int[26];
for(char c : s.toCharArray()){
res[ c - 'a']++;
}
for(char c:t.toCharArray()){
res[ c - 'a']--;
if(res[c - 'a'] < 0){
return false;
}
}
return true;
}
}
单哈希表
class Solution {
public boolean wordPattern(String pattern, String s) {
Map<Character,String> map = new HashMap<>();
String[] strs = s.split(" ");
int len = strs.length;
if(pattern.length() != len){
return false;
}
for (int i = 0; i < len; i++) {
char c = pattern.charAt(i);
if (!map.containsKey(c)) {
if(!map.containsValue(strs[i])){
map.put(c,strs[i]);
}else{
return false;
}
}else if(!map.get(c).equals(strs[i])){
return false;
}
}
return true;
}
}
双哈希表
class Solution {
public boolean wordPattern(String pattern, String s) {
/**
分析:
第一想法是使用哈希表构建隐射关系.
这里还是使用双向隐射,才可以保证是1v1
*/
Map<Character,String> map1 = new HashMap<>();
Map<String,Character> map2 = new HashMap<>();
// 分割字符串
String[] words = s.split(" ");
// 判断长度
if(pattern.length() != words.length){
return false;
}
for(int i = 0; i < pattern.length(); i++){
char c = pattern.charAt(i);
String word = words[i];
// 判断隐射关系
if(map1.containsKey(c) && !map1.get(c).equals(word)
||map2.containsKey(word) && !map2.get(word).equals(c)){
return false;
}
map1.put(c,word);
map2.put(word,c);
}
return true;
}
}
使用哈希表
class Solution {
public char findTheDifference(String s, String t) {
/**
分析:
涉及重复问题,第一想法就是哈希表。t中做加法,s中做减法
*/
Map<Character,Integer> map = new HashMap<>();
for(int i = 0; i < t.length(); i++){
char ch = t.charAt(i);
map.put(ch,map.getOrDefault(ch,0) + 1);
}
for(int i = 0; i < s.length(); i++){
char ch = s.charAt(i);
map.put(ch,map.getOrDefault(ch,0) - 1);
}
char res = 'a';
for(Character c:map.keySet()){
if(map.get(c) > 0){
res = c;
}
}
return res;
}
}
使用数组
class Solution {
public char findTheDifference(String s, String t) {
// 明确是小写字母,那么就可以使用数组优化了
int[] count = new int[26];
for(char c : s.toCharArray()){
count[ c - 'a']++;
}
for(char c : t.toCharArray()){
count[ c - 'a']--;
if(count[ c - 'a'] < 0){
return c;
}
}
return ' ';
}
}
暴力法,从后往前推
class Solution {
public int subarraySum(int[] nums, int k) {
/**
分析:
题目要求是连续子数组,第一想法就是滑动窗口。但是这里不是有序数组...
*/
// 使用暴力法,即先固定左边界,然后枚举右边界
// 从后往前推
int res = 0;
for(int i = 0; i < nums.length ; i++){
int count = 0;
// 第二层循环相当于控制连续数组
for(int j = i; j >= 0; j--){
// 连续数组累加
count += nums[j];
// 发现相等,累加。后面有可能继续相等
if(count == k ){
res++;
}
}
}
return res;
}
}
前缀和+哈希表
class Solution {
public int subarraySum(int[] nums, int k) {
// 使用前缀和+哈希表
Map<Integer,Integer> map = new HashMap<>();
// put(0,1)存在的唯一意义就在于nums[i]本身就等于k的情况
// 建立map表用于存储每个连续子数组sum求和出现的次数,初始化为(0,1),表示和为0的连续子数组出现1次。
map.put(0,1);
int prefixSum = 0;
int res = 0;
for(int i = 0; i < nums.length; i++){
prefixSum += nums[i];
if(map.containsKey(prefixSum - k)){
res += map.get(prefixSum - k);
}
map.put(prefixSum,map.getOrDefault(prefixSum,0) + 1);
}
return res;
}
}
class Solution {
public int numJewelsInStones(String jewels, String stones) {
/**
分析:
解法一:J中字母储存到哈希表中,S遍历。
解法二:题目限定了J是小写字母和大写字母,且不重复,那么就可以使用52个数组储存
*/
int count = 0;
Set<Character> set = new HashSet<>();
for(Character c:jewels.toCharArray()){
set.add(c);
}
for(int i = 0; i < stones.length(); i++){
if(set.contains(stones.charAt(i))){
count++;
}
}
return count;
// 解法二。 A 65 z 122 122 - 65 + 1 = 58
int [] count = new int[58];
for(Character c :jewels.toCharArray()){
count[c - 'A'] = 1;
}
int res = 0;
for(Character c:stones.toCharArray()){
if(count[c - 'A'] == 1){
res++;
}
}
return res;
}
}
class Solution {
public int[] fairCandySwap(int[] aliceSizes, int[] bobSizes) {
/**
分析:
这是一个数学问题,交换后,A和B的总和是相等的。
解法一:使用双指针法。
问题可以理解成 sumA - xA + yB = sumB - yB + xA
那么 xA = (sumA - sumB) / 2 + yB
将数组排好序,定义好双指针,若 xA - yB > (sumA - sumB) / 2,则yB指针移动
直到找到答案。
由于排序了,时间复杂度是O(nlogn)
解法二:
xA = (sumA - sumB) / 2 + yB
那么就将xA储存哈希表中,然后去判断 yB + (sumA - sumB) / 2是否存在哈希表,有的话就直接返回呗
*/
int[] res = new int[2];
int sumA = 0, sumB = 0;
for(int num:aliceSizes){
sumA += num;
}
for(int num:bobSizes){
sumB += num;
}
int p1 = 0, p2 = 0;
int delta = (sumA - sumB) / 2;
Arrays.sort(aliceSizes);
Arrays.sort(bobSizes);
while(p1 < aliceSizes.length && p2 < bobSizes.length){
if(aliceSizes[p1] - bobSizes[p2] > delta ){
p2++;
}else if(aliceSizes[p1] - bobSizes[p2] < delta){
p1++;
}else{
res[0] = aliceSizes[p1];
res[1] = bobSizes[p2];
return res;
}
}
return null;
}
}
哈希表
class Solution {
public int[] fairCandySwap(int[] aliceSizes, int[] bobSizes) {
/**
分析:
这是一个数学问题,交换后,A和B的总和是相等的。
解法一:使用双指针法。
问题可以理解成 sumA - xA + yB = sumB - yB + xA
那么 xA = (sumA - sumB) / 2 + yB
将数组排好序,定义好双指针,若 xA - yB > (sumA - sumB) / 2,则yB指针移动
直到找到答案。
由于排序了,时间复杂度是O(nlogn)
解法二:
xA = (sumA - sumB) / 2 + yB
那么就将xA储存哈希表中,然后去判断 yB + (sumA - sumB) / 2是否存在哈希表,有的话就直接返回呗
*/
int sumA = 0, sumB = 0;
Set<Integer> set = new HashSet<>();
for(int num:aliceSizes){
sumA += num;
set.add(num);
}
for(int num:bobSizes){
sumB += num;
}
int[] res = new int[2];
int delta = (sumA - sumB) / 2;
for(int y:bobSizes){
int x = y + delta;
if(set.contains(x)){
res[0] = x;
res[1] = y;
break;
}
}
return res;
}
}