1. 两数之和

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] result = new int[2];
        Map<Integer, Integer> map = new HashMap();
        for (int i=0;i<nums.length;i++) {
            map.put(nums[i], i);
        }
        for (int i=0;i<nums.length;i++) {
            Integer index = map.get(target-nums[i]);
            // 注意条件 index !=i,因为不能是两个自己
            if (index != null && index != i) {
                result[0] = i;
                result[1] = index;
                return result;
            }
        }
        return result;
    }
}

 

128. 最长连续序列

两种最朴素的解法之一:

  1. 先排序,从前往后找最长连续上升序列即可。该思路简单有效,但是复杂度已经至少有 O(nlogn)。实现起来也比较简单,在此不讨论该解法。
  2. 遍历数组中的每个元素num,然后以num为起点,每次+1向后遍历num+1,num+2,num+3...,判断这些元素是否存在于数组中。假设找到的最大的连续存在的元素为num+x,那么这个连续序列的长度即为x+1。最后将每个num所开始序列长度取个最大值即可。

解题思路1:哈希集合
方法 2 不用想,是肯定超时的。它的最坏时间复杂度已经达到了 O(n^3)
我们需要优化代码。优化的点主要有两个:

  1. 判断num+1,num+2,num+3...是否在数组中。上面的代码是用直接遍历的方式去查找的,时间复杂度为 O(n) 。我们可以改为哈希表查找,时间复杂度为 O(1)
  2. 遍历数组中每个元素num。逐一遍历每个元素会产生很多冗余工作,实际上我们无需一次针对每个元素num去判断num+1,num+2,num+3...是否在数组中。如果num-1已经在数组中的话,那么num-1肯定会进行相应的+1遍历,然后遍历到num,而且从num-1开始的+1遍历必定比从num开始的+1遍历得到的序列长度更长。因此,我们便可将在一个连续序列中的元素进行删减,让其只在最小的元素才开始+1遍历。比如,现有元素[1,2,4,3,5],当2,3,4,5发现均有比自己小1的元素存在,那么它们就不会开始+1遍历,而1是连续序列中最小的元素,没有比自己小1的元素存在,所以会开始+1遍历。通过上述方式便可将时间复杂度优化至O(n)。

解法描述:

1、先排序再找最长递增子序列

最长递增子序列在已排序 且 没有重复元素的情况下可用 dp 解决

  • dp[0] = 1
  • if(nums[i]==nums[i-1]+1) dp[i] = dp[i-1]+1
  • if(nums[i] !=nums[i-1]+1) dp[i] = 1
  • res = Max(res, dp[i])

但是对于有重复元素的情况,

2、哈希表

  • 先将所有元素都加入到 HashSet
  • 遍历 HashSet,当前数字记为 num:
    • 如果 Set 中不存在 num-1,那么往后找 num+1,num+2,num+3.... 直到没有,那么结果就是 每次 在这段连续最长里面 找最大的那个

参考题解: https://leetcode.cn/problems/longest-consecutive-sequence/solution/xiao-bai-lang-ha-xi-ji-he-ha-xi-biao-don-j5a2/

class Solution {

    //哈希表实现。哈希表的 插入、删除、查找的时间复杂度近似为 O(1)
    public int longestConsecutive(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }
        Set<Integer> set = new HashSet();
        // 每个加到哈希表里(去重)
        for (int i=0;i<nums.length;i++) {
            set.add(nums[i]);
        }

        int ans = 1;
        for (int num:set) {
            int cur = num;
            // 如果 num-1 存在,那么 num 就不用搜索了,搜索 num-1 的时候一直往后 +1 搜索会把 num 包括进去
            // 当 num-1 不存在,才开始往后搜索
            if (!set.contains(num-1)) {
                // 只有当num-1不存在时,才开始向后遍历num+1,num+2,num+3......
                while (set.contains(cur+1)) {
                    cur++;
                }
                // [num, cur] 是连续的区间
                ans = Math.max(ans, cur-num+1);
            }
        }
        return ans;
    }


    // 这样复杂度取决于排序的复杂度
    public int longestConsecutive2(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }
        // 排序
        Arrays.sort(nums);
        int longestLen = 1;
        int curLen = 1;
        for (int i=0;i<nums.length;i++) {
            if (i > 0) {
                //  0 1 1 1 2 这种,前面重复的1都跳过跳过
                if (nums[i] == nums[i-1]) {
                    continue;
                }
                // 0 1 1 1 2 这种,到 2 的时候,不能和前一个 1 比较,要一直跳到第一个 1 去比较
                int lastDiffIndex = i-1;
                while (lastDiffIndex > 0 && nums[lastDiffIndex] == nums[lastDiffIndex-1]) {
                    lastDiffIndex-=1;
                }
                // 已排序,与前一个的差值为 1 则说明是连续的
                if (nums[i] - nums[lastDiffIndex] == 1) {
                    longestLen = Math.max(longestLen, curLen+=1);
                }
                else {
                    // 不连续了,curLen 重新开始计算
                    curLen = 1;
                }
            }
        }
        return longestLen;
    }
}

 

 

49. 字母异位词分组

字母异位的词排序之后都是一样的

所以对每个字符串先排序

排序后的字符串作为 HashMap 的 key

而 value 则是字母异位词的 List

注意对字符串排序的方法是

char[] orderStrArr = str.toCharArray();
Arrays.sort(orderStrArr);
String orderStr = String.valueOf(orderStrArr);
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {

        Map<String, List<String>> orderStr2StrListMap = new HashMap();
        for (String str : strs) {
            char[] orderStrArr = str.toCharArray();
            Arrays.sort(orderStrArr);
            String orderStr = String.valueOf(orderStrArr);
            List<String> strList = orderStr2StrListMap.get(orderStr);
            if (strList == null) {
                strList = new ArrayList();
                orderStr2StrListMap.put(orderStr, strList);
            }
            strList.add(str);
        } 

        List<List<String>> res = new ArrayList();
        for(Map.Entry<String, List<String>> entry : orderStr2StrListMap.entrySet()) {
            res.add(entry.getValue());
        }
        return res;
    }
}

 

 

560. 和为 K 的子数组

官方题解:https://leetcode.cn/problems/subarray-sum-equals-k/solution/he-wei-kde-zi-shu-zu-by-leetcode-solution/

假设 left 到 right 下标的子数组和为 k

  • nums[left...right] = k
  • preSum[right] - preSum[left] = k
  • preSum[left] = preSum[right] - k

所以要 每次到 right 的时候,找到等于 preSum[right] - k 的 preSum[left] 有多少个
用一个 map 来记录,前缀和的 count (见官方题解动画)
key: 前缀和的值
value: 前缀和为这个值的个数

注意!!!:

  • map 要放入一个初始值 {0,1}
  • 一定要先 getPreSumCount ,再把加上当前节点的 PreSum 放进 map +1(因为当前节点的和不属于当前节点的前缀和)

 

class Solution {
    public int subarraySum(int[] nums, int k) {
        Map<Integer, Integer> leftPreSum2CntMap = new HashMap();
        
        leftPreSum2CntMap.put(0, 1);
        int rightPreSum = 0;
        // 总的满足条件的子数组个数的计数,最后的结果
        int count = 0;
        for (int right=0;right<nums.length;right++) {
            rightPreSum += nums[right];
            // [...i]
            // left 到 right 的子数组和为 k
            // nums[left...right] = k
            // preSum[right] - preSum[left] = k
            // preSum[left] = preSum[right] - k
            // 所以每次到 right 的时候,找到等于 preSum[right] - k 的 preSum[left] 有多少个
            Integer leftPreSumCnt = leftPreSum2CntMap.get(rightPreSum - k);
            if (leftPreSumCnt != null) {
                count += leftPreSumCnt;
            }

            // 将这次的和放到 map 里
            Integer rightPreSumCnt = leftPreSum2CntMap.get(rightPreSum);
            if (rightPreSumCnt == null) {
                rightPreSumCnt = 0;
            }
            leftPreSum2CntMap.put(rightPreSum, ++rightPreSumCnt);
        }
        return count;
    }
}

 

437. 路径总和 III

Map 不是 Integer 那种每次都 new 一个新对象的无状态类型,所以 dfs 完后得恢复现场

用一个 map 来记录,前缀和的 count 

  • key: 前缀和的值
  • value: 前缀和为这个值的个数

注意!!!:

  • map 要放入一个初始值 {0,1}
  • 一定要先 getPreSumCount ,再把加上当前节点的 PreSum 放进 map +1(因为当前节点的和不属于当前节点的前缀和)
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {

    private static Integer res = 0;

    public Solution() {
        res = 0;
    }

    public int pathSum(TreeNode root, int targetSum) {
        // Long 是因为有极端用例
        Map<Long, Integer> preSum2CountMap = new HashMap(); 
        // 一定要提前放进去 (0, 1) 即不加前面任何数字的话,就是0
        preSum2CountMap.put(0L, 1);
        dfs(root, preSum2CountMap, 0L, targetSum);
        return res;
    }

    private void dfs(TreeNode root, Map<Long, Integer> preSum2CountMap, Long curSum, Integer targetSum) {
        if (root == null) {
            return;
        }
        curSum+=root.val;
        res += preSum2CountMap.getOrDefault(curSum - targetSum, 0);
        // 将现在的 curSum 放进 map 一定要在 map.get(preSum) 即 map.get(curSum-targetSum) 之后
        // 因为到自己这里的所有和,不属于自己的前缀和
        preSum2CountMap.put(curSum, preSum2CountMap.getOrDefault(curSum, 0)+1);
        dfs(root.left, preSum2CountMap, curSum, targetSum);
        dfs(root.right, preSum2CountMap, curSum, targetSum);
        preSum2CountMap.put(curSum, preSum2CountMap.getOrDefault(curSum, 0)-1);
    }
}