leetcode剑指Offer专项版1-20

4、只出现一次的数字

1、题目:给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。

  • 示例 1:
    输入:nums = [2,2,3,2]
    输出:3
  • 示例 2:
    输入:nums = [0,1,0,1,0,1,100]
    输出:100
  • 提示:
    1 <= nums.length <= 3 * \(10^4\)
    -\(2^{31}\) <= nums[i] <= \(2^{31}\) - 1
    nums 中,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次
  • 进阶:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

2、思路:如果我们可以将所有的数字都转化为二进制,然后根据每一位对3其余,如图所示:

leercode剑指offer专项版_字符串

最方便的是我们创建一个32位长度的数组,然后针对每个数字的二进制位对该数组对应下标进行累加,最终计算结果。
但为了严格按照题目要求,我们通过左位移配合二进制加法来实现累加操作,具体如图:

leercode剑指offer专项版_数组_02

class Solution {
    public int singleNumber(int[] nums) {
        /*
        所有的值>>2,输出1的数量并%3*2^0,
        所有值>>2,输出1的数量并%3*2^1
         */
        int res=0;
        for(int i=0;i<32;i++){
            int num=0;
            for(int j=0;j<nums.length;j++){
                num+=(nums[j]&1);
                nums[j]>>=1;
            }
            num%=3;
            if(num!=0)res=res|1<<i;
        }
        return res;
    }
}

5. 单词长度的最大乘积

1、题目给定一个字符串数组 words,请计算当两个字符串 words[i] 和 words[j] 不包含相同字符时,它们长度的乘积的最大值。假设字符串中只包含英语的小写字母。如果没有不包含相同字符的一对字符串,返回 0。

  • 示例 1
    输入: words = ["abcw","baz","foo","bar","fxyz","abcdef"]
    输出: 16
    解释: 这两个单词为 "abcw", "fxyz"。它们不包含相同字符,且长度的乘积最大。
  • 示例 2:
    输入: words = ["a","ab","abc","d","cd","bcd","abcd"]
    输出: 4
    解释: 这两个单词为 "ab", "cd"。
  • 示例 3:

输入: words = ["a","aa","aaa","aaaa"]
输出: 0
解释: 不存在这样的两个单词。

  • 提示:
    2 <= words.length <= 1000
    1 <= words[i].length <= 1000
    words[i] 仅包含小写字母

2、思路:位运算

我们可以用二进制来表示出现的字母,出现的字母记作1。

a可以用1左移0位表示,
b用1左移2位表示...
z用1左移25位表示。

例如:100101,表示出现过a、c和f

如果两个字符串不存在相同字母,那么这两个int值的且值 & 必定是0;

class Solution {
    public static int maxProduct(String[] words) {
        int[] num=new int[1000];
        int res=0;
        for (int i=0 ;i<words.length;i++) {
            for (int j = 0; j < words[i].length(); j++) {
                num[i]|=1<<(words[i].charAt(j)-'a');
            }
        }
        for (int i=0 ;i<words.length;i++) {
            for (int j=0 ;j<words.length;j++) {
                if ((num[i]&num[j])==0)
                    res=Math.max(words[i].length()*words[j].length(),res);
                }
            }
        return res;
        }
}

7. 数组中和为 0 的三个数

1、题目

给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a ,b ,c ,使得 a + b + c = 0 ?
请找出所有和为 0 且 不重复 的三元组。

  • 提示:
    0 <= nums.length <= 3000
    \(-10 ^ 5\) <= nums[i] <= \(10 ^ 5\)
  • 示例 1:
    输入:nums = [-1,0,1,2,-1,-4]
    输出:[[-1,-1,2],[-1,0,1]]
  • 示例 2:
    输入:nums = []
    输出:[]
  • 示例 3:
    输入:nums = [0]
    输出:[]

2、思路

  • 数组排序
  • 确定一个固定的指针
  • 使用双指针对数组进行遍历
  • 注意去重 while (left < right && nums[left] == nums[left + 1]) left++;
class Solution {
    public static List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> ans = new ArrayList();
        int lg = nums.length;
        Arrays.sort(nums);
        for (int i = 0; i < lg; i++) {
            if (nums[i] > 0) break; 
            if (i > 0 && nums[i] == nums[i - 1]) continue; 
            int left = i + 1;
            int right = lg - 1;
            while (left < right) {
                int total = nums[i] + nums[left] + nums[right];
                if (total == 0) {
                    ans.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    while (left < right && nums[left] == nums[left + 1]) left++; 
                    left++;
                } else if (total < 0) left++;
                else if (total > 0) right--;
            }
        }
        return ans;
    }
}

9. 乘积小于 K 的子数组

1、题目给定一个正整数数组 nums和整数 k ,请找出该数组内乘积小于 k 的连续的子数组的个数。

  • 示例 1:
    输入: nums = [10,5,2,6], k = 100
    输出: 8
    解释: 8 个乘积小于 100 的子数组分别为: [10], [5], [2], [6], [10,5], [5,2], [2,6], [5,2,6]。
    需要注意的是 [10,5,2] 并不是乘积小于100的子数组。
  • 示例 2:
  • 输入: nums = [1,2,3], k = 0
    输出: 0
  • 提示:
    1 <= nums.length <= 3 *\(10^4\)
    1 <= nums[i] <= 1000
    0 <= k <= \(10^6\)

2、思路

本题可以使用「滑动窗口」的原因:

  • 关键 1:如果一个连续子数组的所有元素的乘积都严格小于 k,那么这个 连续子数组的子集(同样也得保证是连续子数组)的乘积也一定严格小于 k。
    原因我们在「关键字」里也向大家强调过,数组里的所有元素都是正整数。
  • 关键 2:如果某个连续子数组的乘积大于等于 k,包含它的更长的子数组一定也不满足。
  • 基于以上两点,我们可以设计「滑动窗口」算法。因此「滑动窗口」方法是「暴力解法」的优化。

leercode剑指offer专项版_子数组_03

public static int numSubarrayProductLessThanK(int[] nums, int k) {
  if (k < 2) return 0;
  int res = 0;
  int left = 0;
  int multi = 1;
  int right = 0;
  while (right<nums.length){
    multi*=nums[right++];
    while ( multi >= k) {
      multi /= nums[left++];
    }
    res += right - left ;

  }
  return res;
}

10. 和为 k 的子数组

1、题目

  • 给定一个整数数组和一个整数 k ,请找到该数组中和为 k 的连续子数组的个数。
  • 提示:
  • 1 <= nums.length <= 2 * 10 ^ 4
    -1000 <= nums[i] <= 1000
    -10 ^ 7 <= k <= 10 ^ 7
    示例
  • 示例 1 :
    输入:nums = [1,1,1], k = 2
    输出: 2
    解释: 此题 [1,1] 与 [1,1] 为两种不同的情况
  • 示例 2 :
    输入:nums = [1,2,3], k = 3
    输出: 2

2、分析:

滑动窗口的力所不及

在套模板的同时,大家是否考虑过,假设题目同样是求连续的子数组,但是在数组中出现了负数,那这种情况下还可以使用滑动窗口么?

答案是不行的,为什么?

我们窗口滑动的条件是什么,while窗口内元素超过或者不满足条件时移动,但如果数组存在负数,遇到不满足题意的时候,我们应该移动窗口左边界,还是扩大窗口右边界从而寻找到符合条件的情况呢?

3、如果这道题的取值没有负数,那就是标准的滑窗问题,但因为有了负数,滑窗思想不能用了。
通过分析,这道题应该属于我们上面列举四种情况的最后一种。具体思路如下:

  • 初始化一个空的哈希表和pre_sum=0的前缀和变量
  • 设置返回值ret = 0,用于记录满足题意的子数组数量
  • 循环数组的过程中,通过原地修改数组的方式,计算数组的累加和
  • 将当前累加和减去整数K的结果,在哈希表中查找是否存在
  • 如果存在该key值,证明以数组某一点为起点到当前位置满足题意,ret加等于将该key值对应的value
  • 判断当前的累加和是否在哈希表中,若存在value+1,若不存在value=1
  • 最终返回ret即可
  • 但在这里要注意刚才说到的前缀和边界问题。
  • 我们在计算这种场景时,需要考虑如果以数组nums[0]为开头的连续子数组就满足题意呢?
  • 此时候我们的哈希表还是空的,没办法计算前缀和!所以遇到这类题目,都需要在哈希表中默认插入一个{0:1}的键值对,
  • 用于解决从数组开头的连续子数组满足题意的特殊场景。
class Solution {
    public int subarraySum(int[] nums, int k) {
        int pre_sum = 0;
        int ret = 0;
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        for (int i : nums) {
            pre_sum += i;
            ret += map.getOrDefault(pre_sum - k, 0);
            map.put(pre_sum, map.getOrDefault(pre_sum, 0) + 1);
        }
        return ret;
    }
}

11. 0 和 1 个数相同的子数组

1、问题:给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。

  • 示例 1:
    输入: nums = [0,1]
    输出: 2
    说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。
  • 示例 2:
    输入: nums = [0,1,0]
    输出: 2
    说明: [0, 1] (或 [1, 0]) 是具有相同数量 0 和 1 的最长连续子数组。
  • 提示:
    1 <= nums.length <= \(10^5\)
    nums[i] 不是 0 就是 1

2、分析

我们不妨将所有0转化为-1,那么如果遇到了相同数量的0和1,累加之后的结果就为0,不是就又转化为前缀和的思想了么

解题思路如下:

  • 初始化哈希表,并添加{0:-1}
  • 声明前缀和变量pre_sum = 0,最大子数组长度返回值ret = 0
  • 循环数组,若元素为0,pre_sum - 1,反之pre_sum + 1
  • 判断哈希表中是否存在值为pre_sum的key
  • 若存在pre_sum的key,使用max函数更新ret。
  • 最终返回ret即可
class Solution {
    public int findMaxLength(int[] nums) {
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, -1);
        int pre_sum = 0;
        int ret = 0;
        for (int i = 0; i < nums.length; i++) {
            pre_sum += nums[i] == 0 ? -1 : 1;
            if (map.containsKey(pre_sum)) {
                ret = Math.max(ret, i - map.get(pre_sum));
            } else {
                map.put(pre_sum, i);
            }
        }
        return ret;
    }
}

17. 含有所有字符的最短字符串

1、题目:

给定两个字符串 s 和 t 。返回 s 中包含 t 的所有字符的最短子字符串。如果 s 中不存在符合条件的子字符串,则返回空字符串 "" 。

如果 s 中存在多个符合条件的子字符串,返回任意一个。

注意: 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。

  • 示例 1:
    输入:s = "ADOBECODEBANC", t = "ABC"
    输出:"BANC"
    解释:最短子字符串 "BANC" 包含了字符串 t 的所有字符 'A'、'B'、'C'
  • 示例 2:
    输入:s = "a", t = "a"
    输出:"a"
  • 示例 3:
    输入:s = "a", t = "aa"
    输出:""
    解释:t 中两个字符 'a' 均应包含在 s 的子串中,因此没有符合条件的子字符串,返回空字符串。
  • 提示:
    1 <= s.length, t.length <= \(10^5\)
    s 和 t 由英文字母组成

2、分析

  • 滑动窗口+临界统计
  • 记录好每次符合条件的起始点和长度即可
    算法
    同样使用两个指针定位字符串 s 的子字符串,其中左指针指向子字符串的第 一个字符,右指针指向最后一个字符。若某一时刻两个指针之间的字符串还没有包含字符串 t 中所有的字符,则右指针右移一位添加字符,若该时刻两个指针之间的字符串已包含字符串 t 中所有的字符,因为目标需要求得符合要求的最短子字符串,则需要右移动左指针从子字符串中删去第一个字符。
class Solution {
    public String minWindow(String s, String t) {
        int lens = s.length();
        int lent = t.length();
        if (lent > lens) return "";
        int left = 0;
        int right = 0;
        int minstart = 0;
        int minend = lens;
        char[] s1 = s.toCharArray();
        char[] t1 = t.toCharArray();
        HashMap<Character, Integer> hashMap = new HashMap<>();
        for (char ch : t1) {
            hashMap.put(ch, hashMap.getOrDefault(ch, 0) + 1);
        }
        int numch = hashMap.size();//字符种类数目
        int flag=0;
        while (right < lens || numch == 0) {
            if (numch != 0) {
                if (hashMap.containsKey(s1[right])) {
                    hashMap.put(s1[right], hashMap.get(s1[right]) - 1);
                    if (hashMap.get(s1[right]) == 0) {
                        numch--;
                    }
                }
                right++;
            } else {
                flag=1;
                if (minend - minstart  > right - left ) {
                    minend = right;
                    minstart = left;
                }
                if (hashMap.containsKey(s1[left])) {
                    hashMap.put(s1[left], hashMap.get(s1[left]) + 1);
                    if (hashMap.get(s1[left]) == 1) {
                        numch++;
                    }
                }
                left++;
            }

        }
        if (flag==0)return "";
        return s.substring(minstart, minend);
    }
}