一、哈希
(一)两数之和
思路一:传统方法-双层循环遍历
时间复杂度:O(n^2)
空间复杂度:O(1)
class Solution {
public int[] twoSum(int[] nums, int target) {
// 两层循环求解 时间复杂度O(N^2) 空间复杂度O(1)
int[] goal = new int[2];
for (int i = 0; i < nums.length - 1; i++) {
for (int j = i+1; j < nums.length; j++) {
if ( (nums[i] + nums[j]) == target ) {
goal[0] = i;
goal[1] = j;
return goal;
}
}
}
throw new IllegalArgumentException("no such two nums.");
}
}
思路二:HashMap方法-一次遍历
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public int[] twoSum(int[] nums, int target) {
// HashMap求解 时间复杂度O(N) 空间复杂度O(N)
Map<Integer, Integer> numsMap = new HashMap();
numsMap.put(nums[0], 0);
for (int i = 1; i < nums.length; i++) {
// 计算当前值距离目标值的补数
int complement = target - nums[i];
// 查看当前补数是否存在numsMap中
if (numsMap.containsKey(complement)) {
return new int[] { numsMap.get(complement), i};
}
// 不存在,将当前值加入numsMap中
numsMap.put(nums[i], i);
}
throw new IllegalArgumentException("未找到符合要求的两个下标");
}
}
(二)字母异位词分组
思路:采用哈希+排序
时间复杂度:O(N*M log M) N为字符串数组长度,M为字符串长度
空间复杂度:O(N*M)
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
// 字母异位词分组
// 时间复杂度 O(N*MlogM) N为字符串数组长度,M为字符串长度
// 空间复杂度 O(N*M)
// 创建一个HashMap,键存储排序后的字符串,值存储字母异位词
Map<String, List<String>> anagramMap = new HashMap<>();
// 遍历字符串数组
for (String str : strs) {
// 对字符串重新进行排序
char[] chars = str.toCharArray();
Arrays.sort(chars);
String sortedStr = new String(chars);
// 如果哈希表不存在该字符串,则添加
if ( !anagramMap.containsKey(sortedStr) ) {
// 哈希表新增
anagramMap.put(sortedStr, new ArrayList<>());
}
// 哈希表value赋值
anagramMap.get(sortedStr).add(str);
}
return new ArrayList<>(anagramMap.values());
}
}
(三)最长连续序列
思路一:常规解法:数组排序;双层循环遍历,找出最大的length
时间复杂度为:O(N*logN) + O(N^2)
空间复杂度:O(1)
思路二:哈希解法 要去重->HashSet
时间复杂度:O(N)
空间复杂度:O(N)
class Solution {
public int longestConsecutive(int[] nums) {
// 哈希解法 要去重 -> HashSet 时间复杂度:O(N) 空间复杂度:O(N)
Set<Integer> numSet = new HashSet<>();
// 遍历nums,加入numSet
for (int num : nums) {
numSet.add(num);
}
// 最长序列长度
int longest = 0;
// 遍历numSet,查找最长连续序列
for (int num : numSet) {
// 仅当num-1不存在numSet中,才认定num是个最长序列的起点
if ( !numSet.contains(num - 1)) {
// 当前长度和当前currentNum
int length = 1;
int currentNum = num + 1;
// 查找numSet中currentNum的下一值
while ( numSet.contains(currentNum) ) {
length++;
currentNum++;
}
// 更新最长序列长度
longest = longest > length ? longest : length;
}
}
// 返回最长序列长度
return longest;
}
}
二、双指针
(一)移动零
思路:采用双指针法求解,i记录非零下标,j遍历数组nums,移动非零元素,j遍历完之后,i及之后的元素均为0。
时间复杂度:O(N)
空间复杂度:O(1)
class Solution {
public void moveZeroes(int[] nums) {
// 双指针法求解 时间复杂度O(N) 空间复杂度O(1)
// i记录非零元素的下标
int i = 0;
// 遍历nums
for (int j = 0; j < nums.length; j++) {
if (nums[j] != 0) {
nums[i] = nums[j];
i++;
}
}
// >=i之后的元素设置为0
for (int j = i; j < nums.length; j++) {
nums[j] = 0;
}
}
}
(二)盛最多水的容器
思路一:常规解法,采用双层循环。
时间复杂度:O(N^2)
空间复杂度:O(1)
实现代码:
class Solution {
public int maxArea(int[] height) {
// 双层循环解法
// 时间复杂度 O(N^2)
// 空间复杂度 O(1)
// maxArea表示容器的最大水量
int maxArea = 0;
// 外层循环遍历height
for (int i = 0; i < height.length-1; i++) {
// 记录当前的容器水量
int currentArea = 0;
// 内层循环 寻找盛最多水的下标
for (int j = i+1; j < height.length; j++) {
// 容器长度:j - i
int length = j - i;
// 容器宽度:Math.min(height[i], height[j])
int width = height[i] > height[j] ? height[j] : height[i];
currentArea = length * width;
maxArea = maxArea > currentArea ? maxArea : currentArea;
}
}
// 返回容器的最大水量
return maxArea;
}
}
思路二:双指针法。
时间复杂度:O(N)
空间复杂度:O(1)
实现代码:
class Solution {
public int maxArea(int[] height) {
// 双指针解法 容器面积:(j - i) * (Math.min(height[i], height[j]))
// 时间复杂度:O(N)
// 空间复杂度:O(1)
int i = 0, j = height.length-1;
// 容器最大水量
int maxArea = 0;
// 寻找容器最大水量
while (i < j ) {
// 记录当前的容器水量
int currentArea = ( j - i ) * ( height[i] > height[j] ? height[j] : height[i] );
// 更新容器最大水量
maxArea = maxArea > currentArea ? maxArea : currentArea;
// 指针移动判断,谁小移动谁
if ( height[i] < height[j] ) {
i++;
} else {
j--;
}
}
// 返回容器的最大水量
return maxArea;
}
}