哈希表设计、选取与实践

记录自己的哈希表学习
参考地址

一、理论基础

哈希表
hash table。 它是根据关键码的值而直接进行访问的数据结构。

那么,设计它是用来做什么?
一般哈希表都是用来快速判断一个元素是否出现在集合里
然后,该选取什么形式的哈希表呢?
当我们想到可以使用哈希表解决问题的时候,可以考虑如下三种数据结构:

  • 数组: 适用于经过哈希映射后,比较紧凑的类型
  • set(集合): 可以去重
  • map(映射):比较灵活,当上述2中不合适时,考虑。

二、实践

242. 有效的字母异位词

力扣链接
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

注意:若== s 和 t 中每个字符出现的次数都相同==,则称 s 和 t 互为字母异位词。

示例1:

	输入: s = "anagram", t = "nagaram"
	输出: true

示例2:

	输入: s = "rat", t = "car"
	输出: false

提示:

  • 1 <= s.length, t.length <= 5 ∗ 1 0 4 5 * 10^4 5104
  • s 和 t 仅包含小写字母

分析题意:

  1. 判断s中字符是否出现在t中,且出现次数相同。
  2. s 和 t 仅包含小写字母。即26位

综上所述,可以通过哈希映射h(c) = c - 'a’作为大小为26的数组的索引,其值用来存储对应字母在s中出现的次数。

Java代码如下:

class Solution {
    public boolean isAnagram(String s, String t) {
        int[] alphaCount = new int[26];

        for (int i = 0; i < s.length(); i++) {
            alphaCount[s.charAt(i) - 'a']++;
        }
        for (int i = 0; i < t.length(); i++) {
            alphaCount[t.charAt(i) - 'a']--;
        }
        for (int i = 0; i < alphaCount.length; i++) {
            if (alphaCount[i] != 0) {
                return false;
            }
        }
        return true;
    }
}

JavaScript代码如下:

/**
 * @param {string} s
 * @param {string} t
 * @return {boolean}
 */
var isAnagram = function(s, t) {
    if (s.length !== t.length) {
        return false;
    }
    let alphaCount = new Map();
    for (const c of s) {
        alphaCount.set(c, (alphaCount.get(c) || 0) + 1);
    }
    for (const c of t) {
        if (!alphaCount.get(c)) {
            return false;
        }
        alphaCount.set(c, alphaCount.get(c) - 1);
    }
    return true;
};

Python代码如下:

    def isAnagram(self, s: str, t: str) -> bool:
        if len(s) != len(t):
            return False
        record, base = [0] * 26, ord('a')
        
        for c in s:
            record[ord(c) - base] += 1
        for c in t:
            if not record[ord(c) - base]:
                return False
            record[ord(c) - base] -= 1
        return True
        
    def isAnagram2(self, s: str, t: str) -> bool:
        from collections import Counter
        if len(s) != len(t):
            return False
        return not Counter(s) - Counter(t)
        
    def isAnagram3(self, s: str, t: str) -> bool:
        if len(s) != len(t):
            return False
        from collections import defaultdict
        record = defaultdict(lambda : 0)
        # record = defaultdict(int)
        for c in s:
            record[c] += 1
        for c in t:
            if not record[c]:
                return False
            record[c] -= 1
        return True
49. 字母异位词分组

力扣链接
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表

字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例1

	输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
	输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

示例2

输入: strs = [""]
输出: [[""]]

示例3

	输入: strs = ["a"]
	输出: [["a"]]

提示:

  • 1 <= strs.length <= 1 0 4 10^4 104
  • 0 <= strs[i].length <= 100
  • strs[i] 仅包含小写字母

分析题意:

  1. 输出中的每一项都是数组
  2. 输出中每一项对应的是重新排列源单词后的新单词。

综上所述,使用map能为合理。key为新单词,value为对应数组。

Java代码如下:

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        Map<String, List<String>> d = new HashMap<String, List<String>>();

        for (String str: strs) {
            int[] count = new int[26];
            for (int i = 0; i < str.length(); i++) {
                count[str.charAt(i) - 'a']++;
            }
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < 26; i++) {
                if (count[i] != 0) {
                    sb.append((char) ('a' + i));
                    sb.append(count[i]);
                }
            }
            String key = sb.toString();
            // String key = Arrays.toString(count);
            List<String> list = d.getOrDefault(key, new ArrayList<String>());
            list.add(str);
            d.put(key, list);
        }
        return new ArrayList<List<String>>(d.values());
    }
}

JavaScript代码如下:

/**
 * @param {string[]} strs
 * @return {string[][]}
 */
var groupAnagrams = function(strs) {
    const d = new Map();
    for (let str of strs) {
        let counts = new Array(26).fill(0);
        for (let s of str) {
            counts[s.charCodeAt() - 'a'.charCodeAt()]++;
        }
        let s = counts.toString();
        d.get(s) ? d.get(s).push(str) : d.set(s, [str]);
    }
    return Array.from(d.values());
};

Python代码如下:

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        from collections import defaultdict
        d = defaultdict(list)

        for str in strs:
            s_count = [0] * 26
            for s in str:
                s_count[ord(s) - ord('a')] += 1
            d[tuple(s_count)].append(str)
        
        return list(d.values())
438. 找到字符串中所有字母异位词

力扣链接
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

示例1

	输入: s = "cbaebabacd", p = "abc"
	输出: [0,6]
	解释:
	起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
	起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。

示例2

	输入: s = "abab", p = "ab"
	输出: [0,1,2]
	解释:
	起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
	起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
	起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。

提示:

  • 1 <= s.length, p.length <= 3 ∗ 1 0 4 3 * 10^4 3104
  • s 和 p 仅包含小写字母

分析题意:

  1. 子串的长度一定与p的长度相等。
  2. 子串的长度不能小于p的长度。

综上所述:

  1. 每次移动窗口,比较各自的哈希表是否相同。
  2. 对1进行优化,单纯比较变化量。

Java代码如下:

class Solution {
    public List<Integer> findAnagrams2(String s, String p) {
        if (s.length() < p.length()) {
            return new ArrayList<Integer>();
        }
        int L = p.length(), differ = 0;
        int[] sRecord = new int[26];
        int[] pRecord = new int[26];
        List<Integer> result = new ArrayList<>();

        for (int i = 0; i < L; i++) {
            sRecord[s.charAt(i) - 'a']++;
            sRecord[p.charAt(i) - 'a']--;
        }

        // 单独判断从索引0开始的子串是否符合
        for (int i : sRecord) {
            if (i != 0) {
                // 只记录是否存在差异,不会关注某个字母出现次数
                differ++;
            }
        }
        
        if (differ == 0) {
            result.add(0);
        }

        for (int i = 0; i < s.length() - L; i++) {
            if (sRecord[s.charAt(i) - 'a'] == 1) {
                // 某个字母之间不存在差异
                differ--;
            } else if (sRecord[s.charAt(i) - 'a'] == 0) {
                // 增加差异
                differ++;
            }
            sRecord[s.charAt(i) - 'a']--;

            if (sRecord[s.charAt(i + L) - 'a'] == -1) {
                // 某个字母之间不存在差异
                differ--;
            } else if (sRecord[s.charAt(i + L) - 'a'] == 0) {
                differ++;
            }
            sRecord[s.charAt(i + L) - 'a']++;

            if (differ == 0) {
                result.add(i + 1);
            }
        }
        return result;
    }

    public List<Integer> findAnagrams(String s, String p) {
        if (s.length() < p.length()) {
            return new ArrayList<Integer>();
        }
        int L = p.length();
        int[] sRecord = new int[26];
        int[] pRecord = new int[26];
        List<Integer> result = new ArrayList<>();

        for (int i = 0; i < L; i++) {
            sRecord[s.charAt(i) - 'a']++;
            pRecord[p.charAt(i) - 'a']++;
        }

        // 单独判断从索引0开始的子串是否符合
        if (Arrays.equals(sRecord, pRecord)) {
            result.add(0);
        }

        // 开始滑动
        for (int i = 0; i < s.length() - L; i++) {
            sRecord[s.charAt(i) - 'a']--;
            sRecord[s.charAt(i + L) - 'a']++;

            // 判断滑动后的子串是否符合
            if (Arrays.equals(sRecord, pRecord)) {
                result.add(i + 1);
            }
        }

        return result;
    }
}

JavaScript代码如下:

/**
 * @param {string} s
 * @param {string} p
 * @return {number[]}
 */
var findAnagrams = function(s, p) {
    const sLen = s.length, pLen = p.length;

    if (sLen < pLen) {
        return [];
    }

    const ans = [];
    const sCount = new Array(26).fill(0);
    const pCount = new Array(26).fill(0);
    for (let i = 0; i < pLen; ++i) {
        ++sCount[s[i].charCodeAt() - 'a'.charCodeAt()];
        ++pCount[p[i].charCodeAt() - 'a'.charCodeAt()];
    }

    if (sCount.toString() === pCount.toString()) {
        ans.push(0);
    }

    for (let i = 0; i < sLen - pLen; ++i) {
        --sCount[s[i].charCodeAt() - 'a'.charCodeAt()];
        ++sCount[s[i + pLen].charCodeAt() - 'a'.charCodeAt()];

        if (sCount.toString() === pCount.toString()) {
            ans.push(i + 1);
        }
    }

    return ans;
};

Python代码如下:

class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        s_len, p_len = len(s), len(p)

        if s_len < p_len:
            return []

        ans = []
        count = [0] * 26
        for i in range(p_len):
            count[ord(s[i]) - 97] += 1
            count[ord(p[i]) - 97] -= 1

        differ = [c != 0 for c in count].count(True)

        if differ == 0:
            ans.append(0)

        for i in range(s_len - p_len):
            if count[ord(s[i]) - 97] == 1:  # 窗口中字母 s[i] 的数量与字符串 p 中的数量从不同变得相同
                differ -= 1
            elif count[ord(s[i]) - 97] == 0:  # 窗口中字母 s[i] 的数量与字符串 p 中的数量从相同变得不同
                differ += 1
            count[ord(s[i]) - 97] -= 1

            if count[ord(s[i + p_len]) - 97] == -1:  # 窗口中字母 s[i+p_len] 的数量与字符串 p 中的数量从不同变得相同
                differ -= 1
            elif count[ord(s[i + p_len]) - 97] == 0:  # 窗口中字母 s[i+p_len] 的数量与字符串 p 中的数量从相同变得不同
                differ += 1
            count[ord(s[i + p_len]) - 97] += 1
            
            if differ == 0:
                ans.append(i + 1)

        return ans
349. 两个数组的交集

力扣链接
给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。

示例1:

	输入:nums1 = [1,2,2,1], nums2 = [2,2]
	输出:[2]

示例2:

	输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
	输出:[9,4]
	解释:[4,9] 也是可通过的

提示:

  • 1 <= nums1.length, nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 1000

Java代码如下:

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        Set<Integer> set = new HashSet<>();
        Set<Integer> result = new HashSet<>();

        for (int num : nums1) {
            set.add(num);
        }
        for (int num : nums2) {
            if (set.contains(num)) {
                result.add(num);
            }
        }

        return result.stream().mapToInt(x -> x).toArray();
    }
}

JavaScript代码如下:

/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number[]}
 */
var intersection = function(nums1, nums2) {
    return Array.from(new Set(nums1.filter(value => nums2.includes(value))));
};

Python代码如下:

class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        return list(set(nums1) & set(nums2))

202. 快乐数

力扣链接

编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

示例1:

	输入:n = 19
	输出:true
	解释:
	12 + 92 = 82
	82 + 22 = 68
	62 + 82 = 100
	12 + 02 + 02 = 1

示例2:

	输入:n = 2
	输出:false

提示:

  • 1 <= n <= 2 31 − 1 2^{31} - 1 2311

关键思路

如何让循环停下来:

  • 平方和为1
  • 当平方和在哈希表中第二次出现,则说明该循环无法停止,直接break。

Java代码如下:

class Solution {
    public boolean isHappy(int n) {
        /**
        如何去判断循环中失败的终止条件呢?
            当某一项和重复出现的时候,便意味着失败了,因为它会一直在这个区间中循环。
        */ 
        Set<Integer> record = new HashSet<Integer>();
        while (n != 1) {
            record.add(n);
            // 如果重复出现,则说明不是快乐数
            n = getNextSum(n);
            if (record.contains(n)) {
                return false;
            }
        }
        return true;
    }
    
    private int getNextSum(int n) {
        int res = 0;
        while (n > 0) {
            res += Math.pow(n % 10, 2);
            n /= 10;
        }
        return res;
    }
}

JavaScript代码如下:

/**
 * @param {number} n
 * @return {boolean}
 */
var isHappy = function(n) {
    let record = new Set();
    let sum;

    while (sum !== 1) {
        let nums = ("" + (sum || n)).split("");
        sum = nums.reduce((prev, next) => {
            return prev + next * next;
        }, 0);
        if (record.has(sum)) {
            return false;
        }
        record.add(sum);
    }
    return true;
};

Python代码如下:

class Solution:
    def isHappy(self, n: int) -> bool:
        record = set()
        # 或者使用数组
        # record = []

        while n != 1:
            n = sum([int(i) ** 2 for i in str(n)])
            if n in record:
                return False
            record.add(n)
            # record.append(n)
        return True

1. 两数之和

力扣链接

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例1:

	输入:nums = [2,7,11,15], target = 9
	输出:[0,1]
	解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例2:

	输入:nums = [3,2,4], target = 6
	输出:[1,2]

示例3:

	输入:nums = [3,3], target = 6
	输出:[0,1]

提示:

  • 2 <= nums.length <= 1 0 4 10^4 104
  • − 1 0 9 -10^9 109 <= nums[i] <= 1 0 9 10^9 109
  • − 1 0 9 -10^9 109 <= target <= 1 0 9 10^9 109
  • 只会存在一个有效答案

Java代码如下:

class Solution {
    public int[] twoSum(int[] nums, int target) {
        /**
        1. 何时使用哈希表?
            当考虑某一元素是否出现在集合中的时候。
        2. 使用何种数据结构表示?
            1) 数组: 适用于元素比较紧凑的情况,(即有一个合理的哈希映射函数);否则,数据元素过于分散,会浪费空间。(对于本题,使用数组的函数查找下标,最坏情况O(n), 不理想)
            2) 集合: 去重。
            3) map或字典: 有清晰的映射关系。
         */

         Map<Integer, Integer> map = new HashMap<Integer, Integer>();
         for (int i = 0; i < nums.length; i++) {
            int subtract = target - nums[i];
            if (map.containsKey(subtract)) {
                return new int[] {i, map.get(subtract)};
            }
            map.put(nums[i], i);
         }
         // 按照题意,必有结果,所以这里直接返回空了
         return null;
    }
}

JavaScript代码如下:

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
    let map = new Map();

    for (let i = 0; i < nums.length; i++) {
        let sub = target - nums[i];
        if (map.has(sub)) {
            return [i, map.get(sub)];
        }
        map.set(nums[i], i);
    }
    return null;
};

Python代码如下:

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        map = {}
        for index, value in enumerate(nums):
            sub = target - value
            # if map.get(sub, -1) > -1:
            if sub in map:
                return  [map.get(sub), index]
            map[value] = index

454. 四数相加 II

力扣链接

给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

示例1:

	输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
	输出:2
	解释:
	两个元组如下:
	1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
	2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

示例2:

	输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
	输出:1

提示:

  • n = = n u m s 1. l e n g t h n == nums1.length n==nums1.length
  • n = = n u m s 2. l e n g t h n == nums2.length n==nums2.length
  • n = = n u m s 3. l e n g t h n == nums3.length n==nums3.length
  • n = = n u m s 4. l e n g t h n == nums4.length n==nums4.length
  • 1 < = n < = 200 1 <= n <= 200 1<=n<=200
  • − 2 2 8 -2^28 228 <= nums1[i], nums2[i], nums3[i], nums4[i] <= 2 2 8 2^28 228

Java代码如下:

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        Map<Integer, Integer> map = new HashMap<>();
        int count = 0;

        for (int a : nums1) {
            for (int b : nums2) {
                map.put(a + b, map.getOrDefault(a + b, 0) + 1);
            }
        }

        for (int c : nums3) {
            for (int d : nums4) {
                count += map.getOrDefault(0 - c - d, 0);
            }
        }

        return count;
    }
}

JavaScript代码如下:

/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @param {number[]} nums3
 * @param {number[]} nums4
 * @return {number}
 */
var fourSumCount = function(nums1, nums2, nums3, nums4) {
    let map = new Map();
    let count = 0;
    
    for (const a of nums1) {
        for (const b of nums2) {
            map.set(a + b, (map.get(a + b) || 0) + 1);
        }
    }

    for (const c of nums3) {
        for (const d of nums4) {
            count += map.get(0 - c - d) || 0;
        }
    }
    
    return count;
};

Python代码如下:

class Solution:
    def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
        from collections import defaultdict
        map = defaultdict(lambda : 0)
        # map = dict()
        count = 0

        for a in nums1:
            for b in nums2:
                # map[a + b] = map.get(a + b, 0) + 1
                map[a + b] += 1

        for c in nums3:
            for d in nums4:
                # count += map.get(0 - c - d, 0)
                count += map[0 - c - d]

        return count

383. 赎金信

力扣链接

给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false 。

magazine 中的每个字符只能在 ransomNote 中使用一次。

示例1:

	输入:ransomNote = "a", magazine = "b"
	输出:false

示例2:

	输入:ransomNote = "aa", magazine = "ab"
	输出:false

示例3:

	输入:ransomNote = "aa", magazine = "aab"
	输出:true

提示:

  • 1 <= ransomNote.length, magazine.length <= 1 0 5 10^5 105
  • ransomNote 和 magazine 由小写英文字母组成

Java代码如下:

class Solution {
    /**
     * ransom
     * v./n. 赎金、释放
     * */
    public boolean canConstruct(String ransomNote, String magazine) {
        int[] record = new int[26];

        for (char c : magazine.toCharArray()) {
            record[c - 'a']++;
        }

        for (int i = 0; i < ransomNote.length(); i++) {
            record[ransomNote.charAt(i) - 'a']--;
        }

        for (int x : record) {
            if (x < 0) {
                return false;
            }
        }
        return true;
    }
}

JavaScript代码如下:

/**
 * @param {string} ransomNote
 * @param {string} magazine
 * @return {boolean}
 */
var canConstruct = function(ransomNote, magazine) {
    let record = new Array(26).fill(0),
        base = 'a'.charCodeAt(0);

    for (const c of magazine) {
        record[c.charCodeAt(0) - base]++;
    }

    for (const c of ransomNote) {
        // if (record[c.charCodeAt(0) - base] === 0) {
        //     return false;
        // }
        record[c.charCodeAt(0) - base]--;
    }
    
    return record.every(value => value >= 0);
    // return true;
};

Python代码如下:

class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        # 2.
        # return all([ransomNote.count(c) <= magazine.count(c) for c in ransomNote])
        # 使用集合优化后,提高了运行速率
        return all([ransomNote.count(c) <= magazine.count(c) for c in set(ransomNote)])

        # # 1.利用Counter做差集,仅返回正数去处理
        # from collections import Counter
        # return not Counter(ransomNote) - Counter(magazine)

15. 三数之和

力扣链接

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组

注意:答案中不可以包含重复的三元组。

示例1:

	输入:nums = [-1,0,1,2,-1,-4]
	输出:[[-1,-1,2],[-1,0,1]]
	解释:
	nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
	nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
	nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
	不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。

示例2:

	输入:nums = [0,1,1]
	输出:[]
	解释:唯一可能的三元组和不为 0 。

示例3:

	输入:nums = [0,0,0]
	输出:[[0,0,0]]
	解释:唯一可能的三元组和为 0 。

提示:

  • 3 <= nums.length <= 3000
  • − 1 0 5 -10^5 105 <= nums[i] <= 1 0 5 10^5 105

Java代码如下:

class Solution {
public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        /**使用双指针法必须要先排序,且返回结果与下标无关*/
        Arrays.sort(nums);

        /**
         * 由于a + b + c  = 0
         * 因此:
         * nums[i] 代表 a
         * nums[left] 代表 b
         * nums[right] 代表 c
         * */
        for (int i = 0; i < nums.length; i++) {
            /**如果排序后, 在整个循环过程中,出现:
             * nums[i] > 0,即 a > 0, 那么 b 和 c无论什么情况下,都是大于0的,直接跳出
             * */
            if (nums[i] > 0) {
                break;
            }

            /**
             * 对 a 进行 去重, 跳过本次循环
             * 其实,它是在上一轮 b 和 c去重之后才去重的
             * 即 上一轮结果一定包含于它的所有结果
             * */
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }

            int left = i + 1, right = nums.length - 1;
            while (right > left) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum > 0) {
                    right--;
                } else if (sum < 0) {
                    left++;
                } else {
                    result.add(Arrays.asList(nums[i], nums[left], nums[right]));

                    /**对 b 和 c 去重*/
                    while (left < right && nums[right] == nums[right - 1]) {
                        right--;
                    }
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }

                    right--;
                    left++;
                }
            }
        }

        return result;
    }
}

JavaScript代码如下:

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
let threeSum = function(nums) {
    let result = [];
    /**
     * 这里有坑:
     *    JS中排序默认将 元素转换为的字符串的诸个字符的 Unicode 位点进行排序, 即会出现 11 < 2
     * */
    nums.sort(((a, b) => a - b));

    for (let i = 0; i < nums.length; i++) {
        if (nums[i] > 0) {
            break;
        }

        if (i > 0 && nums[i] == nums[i - 1]) {
            continue;
        }
        
        let left = i + 1,
            right = nums.length - 1;
        while (right > left) {
            let sum = nums[i] + nums[left] + nums[right];
            if (sum > 0) {
                right--;
            } else if (sum < 0) {
                left++;
            } else {
                result.push([nums[i], nums[left], nums[right]]);

                while (right > left && nums[right] == nums[right - 1]) {
                    right--;
                }
                while (right > left && nums[left] == nums[left + 1]) {
                    left++;
                }
                
                right--;
                left++;
            }
        }
    }


    return result;
};

Python代码如下:

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        result = []
        nums.sort()

        for i in range(len(nums)):
            if nums[i] > 0:
                break

            if i > 0 and nums[i] == nums[i - 1]:
                continue
            
            left, right = i + 1, len(nums) - 1
            while right > left:
                sum = nums[i] + nums[left] + nums[right]
                if sum > 0:
                    right -= 1
                elif sum < 0:
                    left += 1
                else:
                    result.append([nums[i], nums[left], nums[right]])

                    while right > left and nums[right] == nums[right - 1]:
                        right -= 1
                    while right > left and nums[left] == nums[left + 1]:
                        left += 1
                    
                    right -= 1
                    left += 1
        
        return result

18. 四数之和

力扣链接

给你==一个由 n 个整数组成的数组 nums ==,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。

示例1:

	输入:nums = [1,0,-1,0,-2,2], target = 0
	输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

示例1:

	输入:nums = [2,2,2,2,2], target = 8
	输出:[[2,2,2,2]]

提示:

  • 1 <= nums.length <= 200
  • − 1 0 9 -10^9 109 <= nums[i] <= 1 0 9 10^9 109
  • − 1 0 9 -10^9 109 <= target <= 1 0 9 10^9 109

特别需要注意:此题与三数之和的区别除了多一个数之外,还有target任意(可正,可负,可0)的区别。

Java代码如下:

class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(nums);

        for (int i = 0; i < nums.length; i++) {
            /**
             * 剪枝:
             * 一定主要target可能为负数
             * 所以直接 剪掉 num[i] > target 是不对的,比如[-4,-1,0,0], target = -5
             * */
            if (nums[i] > target && target >= 0) {
                break;
            }
            /**去重*/
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }

            for (int j = i + 1; j < nums.length; j++) {
                /**
                 * 继续上述的剪枝和去重操作
                 * */
                if (nums[i] + nums[j] > target && target >= 0) {
                    break;
                }
                if (j > i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }
                
                int left = j + 1, right = nums.length - 1;
                while (left < right) {
                    long sum = (long)nums[i] + nums[j] + nums[left] + nums[right];
                    // System.out.println(sum);
                    if (sum > target) {
                        right--;
                    } else if (sum < target) {
                        left++;
                    } else {
                        result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
                        
                        /**去重*/
                        while (left < right && nums[right] == nums[right - 1]) {
                            right--;
                        }
                        while (left < right && nums[left] == nums[left + 1]) {
                            left++;
                        }
                        
                        right--;
                        left++;
                    } 
                }
            }
        }

        return result;
    }
}

JavaScript代码如下:

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[][]}
 */
let fourSum = function(nums, target) {
    let result = [];
    nums.sort(((a, b) => a - b));

    for (let i = 0; i < nums.length; i++) {
        if (nums[i] > target && target >= 0) {
            break;
        }
        if (i > 0 && nums[i] == nums[i - 1]) {
            continue;
        }

        for (let j = i + 1; j < nums.length; j++) {
            if (nums[i] + nums[j] > target && target >= 0) {
                break;
            }
            if (j > i + 1 && nums[j] == nums[j - 1]) {
                continue;
            }
            
            let left = j + 1,
                right = nums.length - 1;
            while (left < right) {
                let sum = nums[i] + nums[j] + nums[left] + nums[right];
                if (sum > target) {
                    right--;
                } else if (sum < target) {
                    left++;
                } else {
                    result.push([nums[i], nums[j], nums[left], nums[right]]);

                    while (left < right && nums[right] == nums[right - 1]) {
                        right--;
                    }
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    
                    right--;
                    left++;
                }
            }
        }
    }

    return result;
};

Python代码如下:

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        result = []
        nums.sort()

        for i in range(len(nums)):
            if nums[i] > target and target >= 0:
                break
            if i > 0 and nums[i] == nums[i - 1]:
                continue
            
            for j in range(i + 1, len(nums)):
                if nums[i] + nums[j] > target and target >= 0:
                    break
                if j > i + 1 and nums[j] == nums[j - 1]:
                    continue
                
                left, right = j + 1, len(nums) - 1
                while left < right:
                    tsum = sum((nums[i], nums[j], nums[left], nums[right]))
                    if tsum > target:
                        right -= 1
                    elif tsum < target:
                        left += 1
                    else:
                        result.append([nums[i], nums[j], nums[left], nums[right]])

                        while left < right and nums[right] == nums[right - 1]:
                            right -= 1
                        while left < right and nums[left] == nums[left + 1]:
                            left += 1
                        
                        right -= 1
                        left += 1


        return result
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值