文章目录
哈希表
数组
1. 两数之和
从数组中找到和为target的两个数,并将它们的下标返回
https://leetcode.cn/problems/two-sum/
思路一:快排+双指针
需要先复制一份数组,因为排完序下标就乱了,跟数据并不对应。排序后,用两指针指向头i
尾j
,如果头尾元素的和大于target,则j--
,小于则i++
,等于则跳出循环,从复制的数组中找到原来的数据所在的位置。
链接:代码
思路二:哈希表
将元素的值和下标存到哈希表中,每次遍历下一个元素时,就使用target减去当前元素,然后判断哈希表中是否存在,存在则找到,反之继续。
class Solution {
public int[] twoSum(int[] nums, int target) {
//2.哈希表
HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
int l = nums.length;
for(int i = 0; i < l; i++){
if(map.containsKey(target-nums[i])){
return new int[]{map.get(target-nums[i]),i};
} else {
map.put(nums[i],i);
}
}
return null;
}
}
217. 存在重复元素
判断一个数组中是否存在重复元素
https://leetcode.cn/problems/contains-duplicate/
思路:HashSet不可重复集合
利用add方法,添加失败返回false,添加成功返回true。当然,也可以用contains进行判断。
class Solution {
public boolean containsDuplicate(int[] nums) {
HashSet<Integer> set = new HashSet<Integer>();
for(int num : nums){
if(!set.add(num)) return true;
}
return false;
}
}
*594. 最长和谐子序列
https://leetcode.cn/problems/longest-harmonious-subsequence/
class Solution {
public int findLHS(int[] nums) {
//HashMap
HashMap<Integer,Integer> map = new HashMap<>();
//HashMap存储
for(int n : nums){
map.put(n,map.getOrDefault(n,0)+1);
}
//求最大值
int max=0;//保存最大值
for(Map.Entry<Integer,Integer> e : map.entrySet()){
int key = e.getKey();
int value = e.getValue();
Integer ir = map.get(key+1);
if(ir!=null){
value+=ir;
if(max < value){
max = value;
}
}
}
return max;
}
}
法二:滑动窗口
28. 最长连续序列
https://leetcode.cn/problems/longest-consecutive-sequence/
思路一:快排
class Solution {
public int longestConsecutive(int[] nums) {
//1.快排
Arrays.sort(nums);
int l = nums.length;
int dp = 1;
int max = 0;
for (int i = 1 ; i < l ; i++) {
int tmp = nums[i] - nums[i-1];
if(tmp == 1) {
dp++;
} else if(tmp > 1) {
dp = 1;
}
if(dp>max) max = dp;
}
return max;
}
}
解释:这里的dp有点动态规划的味道,所以才如此命名,只不过一般想得出来就不需要去分析是不是动态规划了。dp[i]`表示以i结尾的连续的最长序列,注意相同元素,上面的处理是直接跳过,因为不需要处理
如果是求dp[i]的话,结果应该为dp[i-1]
综上,可得动态方程
nums[i]-nums[i-1]==0 dp[i]=dp[i-1]
nums[i]-nums[i-1]==1 dp[i]=dp[i-1]+1
nums[i]-nums[i-1] > 1 dp[i]=1
169. 多数元素
求数组中出现次数超过一半的数字
https://leetcode.cn/problems/majority-element/
思路一:快排
class Solution {
public int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length/2];
}
}
思路二:哈希
class Solution {
public int majorityElement(int[] nums) {
int l = nums.length / 2;
HashMap<Integer,Integer> map = new HashMap<>();
for(int num: nums) {
int count = map.getOrDefault(num, 0) + 1;
map.put(num, count);
if(count > l) {
return num;
}
}
return 0;
}
}
思路三:投票算法,实现O(N) + O(1)
力扣评论:从第一个数开始count=1,遇到相同的就加1,遇到不同的就减1,减到0就重新换个数开始计数,总能找到最多的那个
参考文章
class Solution {
public int majorityElement(int[] nums) {
int res = nums[0];
int count = 1;
int l = nums.length;
for(int i=1; i < l; i++) {
if(res == nums[i]) {
count++;
} else {
count--;
}
if(count == 0) {
res = nums[i];
count = 1;
}
}
return res;
}
}
137. 只出现一次的数字 II
https://leetcode.cn/problems/single-number-ii/
快排
class Solution {
public int singleNumber(int[] nums) {
//快排
Arrays.sort(nums);
int l = nums.length - 1;
int ans = nums[l];
int i = 0;
int j = i + 1;
while(true) {
while(j <= l && nums[i]==nums[j]) j++;
if(j == i + 1) {
ans = nums[i];
break;
}
i=j;
}
return ans;
}
}
哈希表
class Solution {
public int singleNumber(int[] nums) {
Map<Integer,Integer> map = new HashMap<>();
for(int num: nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
for(Map.Entry<Integer,Integer> entry : map.entrySet()) {
if(entry.getValue() == 1) {
return entry.getKey();
}
}
return 0;
}
}
41. 缺失的第一个正数
https://leetcode.cn/problems/first-missing-positive/
class Solution {
public int firstMissingPositive(int[] nums) {
HashSet<Integer> set = new HashSet<>();
int max = 0;
for(int num : nums) {
if(num > 0){
set.add(num);
if(num > max) {
max = num;
}
}
}
for(int i = 1; i < max; i++) {
if(!set.contains(i)) {
return i;
}
}
return max + 1;
}
}
优化:高级做法未实现
442. 数组中重复的数据
思路一:快排
数组中的元素要么出现一次,要么出现两次,所以可先使用Arrays.sort,在对数组进行遍历,即比较左右两元素是否相等,相等则说明出现了两次,添加到结果集。
链接:代码略
思路二:哈希表
以元素作为下标,统计元素的个数,个数为2则添加到结果集。这里不需要HashMap,因为数组元素的大小访问在1-n,只需要一个长度为n+1的数组。
class Solution {
public List<Integer> findDuplicates(int[] nums) {
List<Integer> res = new ArrayList<>();
int[] hash = new int[nums.length+1];
for(int num: nums) {
hash[num]++;
if(hash[num] == 2) {
res.add(num);
}
}
return res;
}
}
1394. 找出数组中的幸运数
思路:哈希表
第一次遍历,先统计各个元素出现的次数。
第二次遍历,比较元素和对应的计数是否相同,对于多个幸运数需要选取最大的,可以采用求最大值方法。
class Solution {
public int findLucky(int[] arr) {
int len=arr.length;
int[] hash = new int[501];
//哈希计数
for(int i=0;i<len;i++){
hash[arr[i]]++;
}
//查找幸运数
int max = -1;
for(int i=0; i<len; i++) {
if(hash[arr[i]] == arr[i] && max < arr[i]) {
max = arr[i];
}
}
return max;
}
}
1122. 数组的相对排序
思路:哈希表
对数组arr1的元素进行哈希统计,然后遍历arr2构造结果数组,由于arr2的元素均在arr1中,所以通过哈希表即可获得arr2中的元素在arr1中出现的次数,循环保存到结果数组即可。
class Solution {
public int[] relativeSortArray(int[] arr1, int[] arr2) {
int[] hash = new int[1001];
//哈希计数
for (int i = 0; i < arr1.length; i++) hash[arr1[i]]++;
//map根据arr2的元素赋值给arr1(此时arr1已经没用了,所以可以作为结果数组使用)
int idx = 0;
for (int i = 0; i < arr2.length; i++) {
while (hash[arr2[i]] > 0) {//循环保存
hash[arr2[i]]--;
arr1[idx++] = arr2[i];
}
}
//遍历map中剩余的元素
for (int i = 0; i < hash.length; i++){
while (hash[i] > 0) {
hash[i]--;
arr1[idx++] = i;
}
}
return arr1;
}
}
1338. 数组大小减半
思路:哈希表+快排
数组减半的同时,消耗的元素要最少,说明得按元素出现的次数从大到小删除(先删次数多的)。次数统计由哈希表解决,从大到小则需要排序解决。
class Solution {
public int minSetSize(int[] arr) {
int len=arr.length;
int[] v=new int[10001];//题目大小有限定,可以用数组
int max=0;
for(int i=0;i<len;i++){
v[arr[i]]++;
if(max<arr[i]) max=arr[i];
}
Arrays.sort(v,0,max+1);
int count=0;
len/=2;
for(int i=max;i>0;i--){
if(v[i]>=len) {
count++;
break;
}else{
len-=v[i];
count++;
}
}
return count;
}
}
1282. 用户分组
思路:双重for循环+访问标记
为每个元素构造一个组,然后与数组里面的元素进行比较,在组的大小内,添加相同的元素,并标记访问过,防止重复访问。
class Solution {
public List<List<Integer>> groupThePeople(int[] groupSizes) {
List<List<Integer>> res=new ArrayList<List<Integer>>(); //结果
int len=groupSizes.length;
for(int i=0;i<len;i++){
List<Integer> group = new ArrayList<Integer>(); //组
group.add(i); //组的第一个成员
if(groupSizes[i]==0) continue; //跳过加过组的
int size=groupSizes[i]; //组的大小
for(int j=i+1;j<len;j++){
if(groupSizes[i]==groupSizes[j]){
if(size<2) break;
groupSizes[j]=0; //标记加入组的
group.add(j);
size--;
}
}
res.add(group);
}
return res;
}
}
思路二:哈希表
以元素作为key,组作为value,由于相同元素可能有多个组,例如333333有两个组,所以需要判断当value数组的长度 == key时,保存结果,并移除该key。
class Solution {
public List<List<Integer>> groupThePeople(int[] groupSizes) {
List<List<Integer>> res = new ArrayList<>();
Map<Integer, List<Integer>> map = new HashMap<>(); //哈希表
int len = groupSizes.length;
for(int i=0; i<len; i++) {
List<Integer> group = map.get(groupSizes[i]);
if(group == null) {
//集合大小明确,new时指定size,可避免扩容(提高效率)
map.put(groupSizes[i], new ArrayList<>(groupSizes[i]));
group = map.get(groupSizes[i]);
}
group.add(i);
if(group.size() == groupSizes[i]) {
res.add(group);
map.remove(groupSizes[i]);
}
}
return res;
}
}
*560. 和为 K 的子数组
https://leetcode.cn/problems/subarray-sum-equals-k
暴力
遍历每个串
0-0、0-1……0-n
1-1、1-2……1-n
……
class Solution {
public int subarraySum(int[] nums, int k) {
int l = nums.length;
int count = 0;
for(int i = 0; i < l; i++) {
int sum = 0;
for(int j = i; j < l; j++) {
sum += nums[j];
// if(sum > k) break;不可以跳出去,因为后面可能更大
if(sum == k) count++;
}
}
return count;
}
}
数组预处理+哈希
转换一下,只需要计算0-0、0-1……0-n各个子数组之和
因为每个子数组都可以由上面的数组推出来
例如1-2等于0-n减去0-0。从而只需判断用两个子数组之和的差是否等于k即可,进一步转换就是用子数组减去k
但是这样不能表示0-0、0-1……0-n这些子数组,所以一开始需要map.put(0,1)
也就是表示自己减去自己是存在的
class Solution {
public int subarraySum(int[] nums, int k) {
int l = nums.length;
int[] dp = new int[l];
dp[0] = nums[0];
for(int i = 1; i < l; i++) {
dp[i] = dp[i-1] + nums[i];
}
Map<Integer,Integer> map = new HashMap<>();
map.put(0,1);
int count = 0;
for(int i = 0; i < l; i++) {
int tmp = dp[i] - k;
if(map.containsKey(tmp)) {
count += map.get(tmp);
}
map.put(dp[i], map.getOrDefault(dp[i],0) + 1);
}
return count;
}
}
字符串
更多
面试题 01.02. 判定是否互为字符重排
思路:哈希表
对字符串s1采用加法的方式统计字符的出现次数,对s2则采用减法的方式统计。例如a在s1中出现3次,在s2中也出现3次,那么最终哈希表中a的计数为0,如果中间存在计数小于0的字符,则说明结果为false
由于该题的测试用例仅仅只包含26字母,所以也可用数组作为哈希表
class Solution {
public boolean CheckPermutation(String s1, String s2) {
if (s1.length() != s2.length()) {
return false;
}
Map<Character, Integer> map = new HashMap<>();
//加法统计
for (int i = 0; i < s1.length(); i++) {
map.put(s1.charAt(i), map.getOrDefault(s1.charAt(i), 0) + 1);
}
//减法统计
for (int i = 0; i < s2.length(); i++) {
map.put(s2.charAt(i), map.getOrDefault(s2.charAt(i), 0) - 1);
if (map.getOrDefault(s2.charAt(i), -1) < 0) {
return false;
}
}
return true;
}
}
提示:getOrDefault跟get一样,只是当map中不存在时,它能返回自定义默认值,而不是null
438. 找到字符串中所有字母异位词
上一道的题的扩展,代码相对难写,但思路不难
class Solution {
public List<Integer> findAnagrams(String s, String p) {
int pl = p.length() - 1;
//需要遍历的起始点
int sl = s.length() - pl;
//特殊情况。。。sl比pl长
if(sl <= 0) return new ArrayList<Integer>();
int[] hashp = new int[26];
for(char ch : p.toCharArray()) {
hashp[ch - 'a']++;
}
List<Integer> res = new ArrayList<Integer>(sl);
int[] hashs = new int[26];
//初始化第一个字串
for(int i = 0; i < pl; i++) {
hashs[s.charAt(i) - 'a']++;
}
for(int i = 0; i < sl; i++) {
//新增加一个元素,i + pl代表每个串的最后一个元素
hashs[s.charAt(i + pl) - 'a']++;
//判断是否是异位字符串
if(judge(hashs, hashp)) {
res.add(i);
}
//减掉移除的元素
hashs[s.charAt(i) - 'a']--;
}
return res;
}
private boolean judge(int[] hashs, int[] hashp) {
int i = 26;
while(i-- > 0) {
if(hashs[i] != hashp[i]) {
return false;
}
}
return true;
}
}