文章目录
1、两数之和
public int[] twoSum(int[] nums, int target) {
HashMap<Integer, Integer> numIndexMap = new HashMap();
int[] result = new int[2];
for(int i = 0;i < nums.length;i ++){
if(numIndexMap.containsKey(target - nums[i])){
result[0] = i;
result[1] = numIndexMap.get(target - nums[i]);
}
numIndexMap.put(nums[i], i);
}
return result;
}
用空间换时间,使时间复杂度降到 O(N)
2、字母异位词分组
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> strListMap = new HashMap<>();
for (String str : strs){
char[] charArray = str.toCharArray();
Arrays.sort(charArray);
String key = new String(charArray);
List<String> curList = strListMap.getOrDefault(key, new ArrayList<>());
curList.add(str);
strListMap.put(key, curList);
}
return new ArrayList<>(strListMap.values());
}
}
将字符串转为字符数组,利用Arrays.sort()实现字符串排序
3、最长连续序列
public static int longestConsecutive(int[] nums) {
if (nums.length < 2){
return nums.length;
}
Set<Integer> numSet = new HashSet();
for (Integer num : nums){
numSet.add(num);
}
int maxLength = 1;
for (Integer num : numSet) {
int curLength = 1;
int curNum = num;
/* 不含比当前数小1的数,则当前数可以作为计数起点 */
if (!numSet.contains(curNum - 1)){
/* 从当前数开始,依次向上+1统计 */
while (numSet.contains(curNum + 1)){
curNum ++;
curLength ++;
}
/* 取所有可能起点统计的最大值 */
maxLength = Math.max(curLength, maxLength);
}
}
return maxLength;
}
4、移动零
public static void moveZeroes(int[] nums) {
int startIndex = 0;
/* 双指针,startIndex指向数组前面不为0的后一个位置,第二个指针起遍历作用 */
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0){
nums[startIndex ++] = nums[i];
}
}
/* 将数组后面全部填充为0 */
for (int i = startIndex; i < nums.length; i++) {
nums[i] = 0;
}
}
5、盛最多水的容器
public int maxArea(int[] height) {
/* 最大值 */
int maxNum = 0;
/* 左指针 */
int startIndex = 0;
/* 右指针 */
int endIndex = height.length - 1;
while(startIndex < endIndex){
/* 取最大值 */
maxNum = Math.max(maxNum, Math.min(height[startIndex], height[endIndex]) * (endIndex - startIndex));
if (height[startIndex] <= height[endIndex]){
startIndex ++;
} else {
endIndex --;
}
}
return maxNum;
}
6、三数之和
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> resList = new ArrayList<>();
if (nums == null || nums.length < 2){
return resList;
}
Arrays.sort(nums);
for (int i = 0; i < nums.length - 2; i++) {
/* 最小的数大于0,直接结束 */
if (nums[i] > 0){
break;
}
/* 跳过重复值 */
if (i > 0 && nums[i] == nums[i - 1]){
continue;
}
/* 双指针 */
int startIndex = i + 1;
int endIndex = nums.length - 1;
while (startIndex < endIndex){
if (nums[startIndex] + nums[endIndex] + nums[i] == 0){
resList.add(Arrays.asList(nums[startIndex], nums[endIndex], nums[i]));
startIndex ++;
endIndex --;
/* 跳过重复值 */
while (startIndex < endIndex && nums[startIndex] == nums[startIndex - 1]){
startIndex ++;
}
/* 跳过重复值 */
while (startIndex < endIndex && nums[endIndex] == nums[endIndex + 1]){
endIndex --;
}
} else if (nums[startIndex] + nums[endIndex] + nums[i] < 0){
startIndex ++;
} else {
endIndex --;
}
}
}
return resList;
}
7、接雨水
1.能盛雨水的区间一定在最大值的两侧,所以找到最大值点,并分别从左和右遍历加和
class Solution {
public static int trap(int[] height) {
if (height.length < 3){
return 0;
}
/* 寻找最大值下标 */
int maxIndex = 0;
for (int i = 0; i < height.length; i++) {
if (height[i] > height[maxIndex]){
maxIndex = i;
}
}
int resNum = 0, startIndex = 0;
/* 从起点到最大值点遍历 */
for (int i = 0; i < maxIndex; i++) {
/* 当前点值小于边界值可计数 */
if (height[i] < height[startIndex]){
resNum += (height[startIndex] - height[i]);
} else {
/* 否则更新边界点 */
startIndex = i;
}
}
/* 从终点到最大值点遍历 */
startIndex = height.length - 1;
for (int i = height.length - 1; i > maxIndex ; i--) {
if (height[i] < height[startIndex]){
resNum += (height[startIndex] - height[i]);
} else {
startIndex = i;
}
}
return resNum;
}
}
2.将盛雨水区间纵向切分并加和,即找到当前下标两边比当前值大的最临近的两个值的小者差即为当前点可盛雨水量。
8、无重复字符的最长子串
class Solution {
public int lengthOfLongestSubstring(String s) {
if (s == null){
return 0;
}
if (s.length() < 2){
return s.length();
}
/* 字符和位置映射关系 */
Map<Character, Integer> positionMap = new HashMap<>();
int startIndex = 0, endIndex = 0, maxLength = 0;
while (endIndex < s.length()){
if (positionMap.containsKey(s.charAt(endIndex))){
startIndex = Math.max(startIndex, positionMap.get(s.charAt(endIndex)) + 1);
}
positionMap.put(s.charAt(endIndex), endIndex);
maxLength = Math.max(maxLength, endIndex - startIndex + 1);
endIndex ++;
}
return maxLength;
}
}
9、找到字符串中所有字母异位词
class Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> list = new ArrayList<>();
for(int i = 0;i <= s.length() - p.length();i ++){
if(isAnagram(s.substring(i,p.length() + i),p)){
list.add(i);
}
}
return list;
}
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()){
return false;
}
int[] arr = new int[26];
for (int i = 0; i < s.length(); i++) {
arr[s.charAt(i) - 'a'] ++;
}
for (int i = 0; i < t.length(); i++) {
arr[t.charAt(i) - 'a'] --;
if (arr[t.charAt(i) - 'a'] < 0){
return false;
}
}
return true;
}
}
10、和为k的子数组
1.暴力解法:时间复杂度O(N2),空间复杂度O(1)。
class Solution {
public int subarraySum(int[] nums, int k) {
int resSum = 0;
for (int i = 0; i < nums.length; i++) {
int sum = 0;
for (int j = i; j < nums.length; j++) {
sum += nums[j];
if (sum == k){
resSum ++;
}
}
}
return resSum;
}
}
2.前缀和+哈希:时间复杂度O(N),空间复杂度O(1)。
class Solution {
public int subarraySum(int[] nums, int k) {
/* 初始化总数量和前缀和为0 */
int resCount = 0, prefixSum = 0;
/* 前缀和为key,前缀和为此值的数量为value */
Map<Integer, Integer> prefixSumMap = new HashMap<>();
prefixSumMap.put(0, 1);
for (int i = 0; i < nums.length; i++) {
prefixSum += nums[i];
if (prefixSumMap.containsKey(prefixSum - k)){
resCount += prefixSumMap.get(prefixSum - k);
}
/* 更新前缀和map */
prefixSumMap.put(prefixSum, prefixSumMap.getOrDefault(prefixSum, 0) + 1);
}
return resCount;
}
}
11、滑动窗口最大值
1.暴力解法,会超时
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int[] resArray = new int[nums.length - k + 1];
for (int i = 0; i < nums.length - k + 1; i++) {
resArray[i] = this.getMaxNumFromArray(nums, i, i + k);
}
return resArray;
}
public int getMaxNumFromArray(int[] nums, int startIndex, int endIndex){
int maxNum = nums[startIndex];
for (int i = startIndex; i < endIndex; i++) {
maxNum = Math.max(maxNum, nums[i]);
}
return maxNum;
}
}
2.双端队列:队首保存当前滑动窗口内的最大值,如果待入队数字大于队尾数字,则依次将队尾数字移除,直到待入队数字小于等于队尾数字为止,目的是保证从队首到队尾呈递减趋势且队列中总是保存了当前滑动窗口内的最大值。
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> deque = new LinkedList<>();
int[] resArray = new int[nums.length - k + 1];
int index = 0;
for (int i = 0; i < nums.length; i++) {
/* 队首永远是最大值,将比待新增数字小的数字从队尾依次移除 */
while(!deque.isEmpty() && nums[i] > deque.getLast()){
deque.removeLast();
}
deque.offer(nums[i]);
if (i == k - 1){
resArray[index ++] = deque.peek();
}
if (i > k - 1){
/* 滑动窗口左边界外最近值 = 队首值,将队首值移除 */
if (!deque.isEmpty() && nums[i - k] == deque.peek()){
deque.poll();
}
resArray[index ++] = deque.peek();
}
}
return resArray;
}
}